1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include <cassert>
17 #include <cstdio>
18 #include <cstdlib>
19 #include <cstring>
20 #include <memory>
21
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <linux/fiemap.h>
25 #include <linux/fs.h>
26 #include <sys/ioctl.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <unistd.h>
30
31 #include <android-base/unique_fd.h>
32
33 // This program modifies a file at given offset, but directly against the block
34 // device, purposely to bypass the filesystem. Note that the change on block
35 // device may not reflect the same way when read from filesystem, for example,
36 // when the file is encrypted on disk.
37 //
38 // Only one byte is supported for now just so that we don't need to handle the
39 // case when the range crosses different "extents".
40 //
41 // References:
42 // https://www.kernel.org/doc/Documentation/filesystems/fiemap.txt
43 // https://git.kernel.org/pub/scm/fs/xfs/xfsprogs-dev.git/tree/io/fiemap.c
44
45 #ifndef F2FS_IOC_SET_PIN_FILE
46 #ifndef F2FS_IOCTL_MAGIC
47 #define F2FS_IOCTL_MAGIC 0xf5
48 #endif
49 #define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32)
50 #define F2FS_IOC_GET_PIN_FILE _IOR(F2FS_IOCTL_MAGIC, 14, __u32)
51 #endif
52
53 struct Args {
54 const char* block_device;
55 const char* file_name;
56 uint64_t byte_offset;
57 bool use_f2fs_pinning;
58 };
59
60 class ScopedF2fsFilePinning {
61 public:
ScopedF2fsFilePinning(const char * file_path)62 explicit ScopedF2fsFilePinning(const char* file_path) {
63 fd_.reset(TEMP_FAILURE_RETRY(open(file_path, O_WRONLY | O_CLOEXEC, 0)));
64 if (fd_.get() == -1) {
65 perror("Failed to open");
66 return;
67 }
68 __u32 set = 1;
69 ioctl(fd_.get(), F2FS_IOC_SET_PIN_FILE, &set);
70 }
71
~ScopedF2fsFilePinning()72 ~ScopedF2fsFilePinning() {
73 __u32 set = 0;
74 ioctl(fd_.get(), F2FS_IOC_SET_PIN_FILE, &set);
75 }
76
77 private:
78 android::base::unique_fd fd_;
79 };
80
get_logical_block_size(const char * block_device)81 ssize_t get_logical_block_size(const char* block_device) {
82 android::base::unique_fd fd(open(block_device, O_RDONLY));
83 if (fd.get() < 0) {
84 fprintf(stderr, "open %s failed\n", block_device);
85 return -1;
86 }
87
88 int size;
89 if (ioctl(fd, BLKSSZGET, &size) < 0) {
90 fprintf(stderr, "ioctl(BLKSSZGET) failed: %s\n", strerror(errno));
91 return -1;
92 }
93 return size;
94 }
95
get_physical_offset(const char * file_name,uint64_t byte_offset)96 int64_t get_physical_offset(const char* file_name, uint64_t byte_offset) {
97 android::base::unique_fd fd(open(file_name, O_RDONLY));
98 if (fd.get() < 0) {
99 fprintf(stderr, "open %s failed\n", file_name);
100 return -1;
101 }
102
103 const int map_size = sizeof(struct fiemap) + sizeof(struct fiemap_extent);
104 char fiemap_buffer[map_size] = {0};
105 struct fiemap* fiemap = reinterpret_cast<struct fiemap*>(&fiemap_buffer);
106
107 fiemap->fm_flags = FIEMAP_FLAG_SYNC;
108 fiemap->fm_start = byte_offset;
109 fiemap->fm_length = 1;
110 fiemap->fm_extent_count = 1;
111
112 int ret = ioctl(fd.get(), FS_IOC_FIEMAP, fiemap);
113 if (ret < 0) {
114 fprintf(stderr, "ioctl(FS_IOC_FIEMAP) failed: %s\n", strerror(errno));
115 return -1;
116 }
117
118 if (fiemap->fm_mapped_extents != 1) {
119 fprintf(stderr, "fm_mapped_extents != 1 (is %d)\n",
120 fiemap->fm_mapped_extents);
121 return -1;
122 }
123
124 struct fiemap_extent* extent = &fiemap->fm_extents[0];
125 printf(
126 "logical offset: %llu, physical offset: %llu, length: %llu, "
127 "flags: %x\n",
128 extent->fe_logical, extent->fe_physical, extent->fe_length,
129 extent->fe_flags);
130 if (extent->fe_flags & (FIEMAP_EXTENT_UNKNOWN |
131 FIEMAP_EXTENT_UNWRITTEN)) {
132 fprintf(stderr, "Failed to locate physical offset safely\n");
133 return -1;
134 }
135
136 return extent->fe_physical + (byte_offset - extent->fe_logical);
137 }
138
read_block_from_device(const char * device_path,uint64_t block_offset,ssize_t block_size,char * block_buffer)139 int read_block_from_device(const char* device_path, uint64_t block_offset,
140 ssize_t block_size, char* block_buffer) {
141 assert(block_offset % block_size == 0);
142 android::base::unique_fd fd(open(device_path, O_RDONLY | O_DIRECT));
143 if (fd.get() < 0) {
144 fprintf(stderr, "open %s failed\n", device_path);
145 return -1;
146 }
147
148 ssize_t retval =
149 TEMP_FAILURE_RETRY(pread(fd, block_buffer, block_size, block_offset));
150 if (retval != block_size) {
151 fprintf(stderr, "read returns error or incomplete result (%zu): %s\n",
152 retval, strerror(errno));
153 return -1;
154 }
155 return 0;
156 }
157
write_block_to_device(const char * device_path,uint64_t block_offset,ssize_t block_size,char * block_buffer)158 int write_block_to_device(const char* device_path, uint64_t block_offset,
159 ssize_t block_size, char* block_buffer) {
160 assert(block_offset % block_size == 0);
161 android::base::unique_fd fd(open(device_path, O_WRONLY | O_DIRECT));
162 if (fd.get() < 0) {
163 fprintf(stderr, "open %s failed\n", device_path);
164 return -1;
165 }
166
167 ssize_t retval = TEMP_FAILURE_RETRY(
168 pwrite(fd.get(), block_buffer, block_size, block_offset));
169 if (retval != block_size) {
170 fprintf(stderr, "write returns error or incomplete result (%zu): %s\n",
171 retval, strerror(errno));
172 return -1;
173 }
174 return 0;
175 }
176
parse_args(int argc,const char ** argv)177 std::unique_ptr<Args> parse_args(int argc, const char** argv) {
178 if (argc != 4 && argc != 5) {
179 fprintf(stderr,
180 "Usage: %s [--use-f2fs-pinning] block_dev filename byte_offset\n"
181 "\n"
182 "This program bypasses filesystem and damages the specified byte\n"
183 "at the physical position on <block_dev> corresponding to the\n"
184 "logical byte location in <filename>.\n",
185 argv[0]);
186 return nullptr;
187 }
188
189 auto args = std::make_unique<Args>();
190 const char** arg = &argv[1];
191 args->use_f2fs_pinning = strcmp(*arg, "--use-f2fs-pinning") == 0;
192 if (args->use_f2fs_pinning) {
193 ++arg;
194 }
195 args->block_device = *(arg++);
196 args->file_name = *(arg++);
197 args->byte_offset = strtoull(*arg, nullptr, 10);
198 if (args->byte_offset == ULLONG_MAX) {
199 perror("Invalid byte offset");
200 return nullptr;
201 }
202 return args;
203 }
204
main(int argc,const char ** argv)205 int main(int argc, const char** argv) {
206 std::unique_ptr<Args> args = parse_args(argc, argv);
207 if (args == nullptr) {
208 return -1;
209 }
210
211 ssize_t block_size = get_logical_block_size(args->block_device);
212 if (block_size < 0) {
213 return -1;
214 }
215
216 std::unique_ptr<ScopedF2fsFilePinning> pinned_file;
217 if (args->use_f2fs_pinning) {
218 pinned_file = std::make_unique<ScopedF2fsFilePinning>(args->file_name);
219 }
220
221 int64_t physical_offset_signed = get_physical_offset(args->file_name, args->byte_offset);
222 if (physical_offset_signed < 0) {
223 return -1;
224 }
225
226 uint64_t physical_offset = static_cast<uint64_t>(physical_offset_signed);
227 uint64_t offset_within_block = physical_offset % block_size;
228 uint64_t physical_block_offset = physical_offset - offset_within_block;
229
230 // Direct I/O requires aligned buffer
231 std::unique_ptr<char> buf(static_cast<char*>(
232 aligned_alloc(block_size /* alignment */, block_size /* size */)));
233
234 if (read_block_from_device(args->block_device, physical_block_offset, block_size,
235 buf.get()) < 0) {
236 return -1;
237 }
238 char* p = buf.get() + offset_within_block;
239 printf("before: %hhx\n", *p);
240 *p ^= 0xff;
241 printf("after: %hhx\n", *p);
242 if (write_block_to_device(args->block_device, physical_block_offset, block_size,
243 buf.get()) < 0) {
244 return -1;
245 }
246
247 return 0;
248 }
249