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