1 /*
2  * Copyright (C) 2018 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 
17 #pragma once
18 
19 #include <stdlib.h>
20 #include <string.h>
21 #include <sys/mman.h>
22 #include <sys/types.h>
23 
24 #include <functional>
25 #include <string>
26 #include <vector>
27 
28 #include <android-base/file.h>
29 #include <android-base/strings.h>
30 
31 namespace android {
32 namespace procinfo {
33 
34 /*
35  * The populated fields of MapInfo corresponds to the following fields of an entry
36  * in /proc/<pid>/maps:
37  *
38  * <start>     -<end>         ...   <pgoff>        ...   <inode>    <name>
39  * 790b07dc6000-790b07dd9000  r--p  00000000       fe:09 21068208   /system/lib64/foo.so
40  *                               |
41  *                               |
42  *                               |___ p - private (!<shared>)
43  *                                    s - <shared>
44  */
45 struct MapInfo {
46   uint64_t start;
47   uint64_t end;
48   // NOTE: It should not be assumed the virtual addresses in range [start,end] all
49   //       correspond to valid offsets on the backing file.
50   //       See: MappedFileSize().
51   uint16_t flags;
52   uint64_t pgoff;
53   ino_t inode;
54   std::string name;
55   bool shared;
56 
MapInfoMapInfo57   MapInfo(uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff, ino_t inode,
58           const char* name, bool shared)
59       : start(start),
60         end(end),
61         flags(flags),
62         pgoff(pgoff),
63         inode(inode),
64         name(name),
65         shared(shared) {}
66 
MapInfoMapInfo67   MapInfo(const MapInfo& params)
68       : start(params.start),
69         end(params.end),
70         flags(params.flags),
71         pgoff(params.pgoff),
72         inode(params.inode),
73         name(params.name),
74         shared(params.shared) {}
75 };
76 
77 typedef std::function<void(const MapInfo&)> MapInfoCallback;
78 typedef std::function<void(uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff,
79                       ino_t inode, const char* name, bool shared)> MapInfoParamsCallback;
80 
PassSpace(char ** p)81 static inline bool PassSpace(char** p) {
82   if (**p != ' ') {
83     return false;
84   }
85   while (**p == ' ') {
86     (*p)++;
87   }
88   return true;
89 }
90 
PassXdigit(char ** p)91 static inline bool PassXdigit(char** p) {
92   if (!isxdigit(**p)) {
93     return false;
94   }
95   do {
96     (*p)++;
97   } while (isxdigit(**p));
98   return true;
99 }
100 
101 // Parses the given line p pointing at proc/<pid>/maps content buffer and returns true on success
102 // and false on failure parsing. The first new line character of line will be replaced by the
103 // null character and *next_line will point to the character after the null.
104 //
105 // Example of how a parsed line look line:
106 // 00400000-00409000 r-xp 00000000 fc:00 426998  /usr/lib/gvfs/gvfsd-http
ParseMapsFileLine(char * p,uint64_t & start_addr,uint64_t & end_addr,uint16_t & flags,uint64_t & pgoff,ino_t & inode,char ** name,bool & shared,char ** next_line)107 static inline bool ParseMapsFileLine(char* p, uint64_t& start_addr, uint64_t& end_addr, uint16_t& flags,
108                       uint64_t& pgoff, ino_t& inode, char** name, bool& shared, char** next_line) {
109   // Make the first new line character null.
110   *next_line = strchr(p, '\n');
111   if (*next_line != nullptr) {
112     **next_line = '\0';
113     (*next_line)++;
114   }
115 
116   char* end;
117   // start_addr
118   start_addr = strtoull(p, &end, 16);
119   if (end == p || *end != '-') {
120     return false;
121   }
122   p = end + 1;
123   // end_addr
124   end_addr = strtoull(p, &end, 16);
125   if (end == p) {
126     return false;
127   }
128   p = end;
129   if (!PassSpace(&p)) {
130     return false;
131   }
132   // flags
133   flags = 0;
134   if (*p == 'r') {
135     flags |= PROT_READ;
136   } else if (*p != '-') {
137     return false;
138   }
139   p++;
140   if (*p == 'w') {
141     flags |= PROT_WRITE;
142   } else if (*p != '-') {
143     return false;
144   }
145   p++;
146   if (*p == 'x') {
147     flags |= PROT_EXEC;
148   } else if (*p != '-') {
149     return false;
150   }
151   p++;
152   if (*p != 'p' && *p != 's') {
153     return false;
154   }
155   shared = *p == 's';
156 
157   p++;
158   if (!PassSpace(&p)) {
159     return false;
160   }
161   // pgoff
162   pgoff = strtoull(p, &end, 16);
163   if (end == p) {
164     return false;
165   }
166   p = end;
167   if (!PassSpace(&p)) {
168     return false;
169   }
170   // major:minor
171   if (!PassXdigit(&p) || *p++ != ':' || !PassXdigit(&p) || !PassSpace(&p)) {
172     return false;
173   }
174   // inode
175   inode = strtoull(p, &end, 10);
176   if (end == p) {
177     return false;
178   }
179   p = end;
180 
181   if (*p != '\0' && !PassSpace(&p)) {
182     return false;
183   }
184 
185   // Assumes that the first new character was replaced with null.
186   *name = p;
187 
188   return true;
189 }
190 
ReadMapFileContent(char * content,const MapInfoParamsCallback & callback)191 inline bool ReadMapFileContent(char* content, const MapInfoParamsCallback& callback) {
192   uint64_t start_addr;
193   uint64_t end_addr;
194   uint16_t flags;
195   uint64_t pgoff;
196   ino_t inode;
197   char* line_start = content;
198   char* next_line;
199   char* name;
200   bool shared;
201 
202   while (line_start != nullptr && *line_start != '\0') {
203     bool parsed = ParseMapsFileLine(line_start, start_addr, end_addr, flags, pgoff,
204                                     inode, &name, shared, &next_line);
205     if (!parsed) {
206       return false;
207     }
208 
209     line_start = next_line;
210     callback(start_addr, end_addr, flags, pgoff, inode, name, shared);
211   }
212   return true;
213 }
214 
ReadMapFileContent(char * content,const MapInfoCallback & callback)215 inline bool ReadMapFileContent(char* content, const MapInfoCallback& callback) {
216   uint64_t start_addr;
217   uint64_t end_addr;
218   uint16_t flags;
219   uint64_t pgoff;
220   ino_t inode;
221   char* line_start = content;
222   char* next_line;
223   char* name;
224   bool shared;
225 
226   while (line_start != nullptr && *line_start != '\0') {
227     bool parsed = ParseMapsFileLine(line_start, start_addr, end_addr, flags, pgoff,
228                                     inode, &name, shared, &next_line);
229     if (!parsed) {
230       return false;
231     }
232 
233     line_start = next_line;
234     callback(MapInfo(start_addr, end_addr, flags, pgoff, inode, name, shared));
235   }
236   return true;
237 }
238 
ReadMapFile(const std::string & map_file,const MapInfoCallback & callback)239 inline bool ReadMapFile(const std::string& map_file,
240                 const MapInfoCallback& callback) {
241   std::string content;
242   if (!android::base::ReadFileToString(map_file, &content)) {
243     return false;
244   }
245   return ReadMapFileContent(&content[0], callback);
246 }
247 
248 
ReadMapFile(const std::string & map_file,const MapInfoParamsCallback & callback,std::string & mapsBuffer)249 inline bool ReadMapFile(const std::string& map_file, const MapInfoParamsCallback& callback,
250                         std::string& mapsBuffer) {
251   if (!android::base::ReadFileToString(map_file, &mapsBuffer)) {
252     return false;
253   }
254   return ReadMapFileContent(&mapsBuffer[0], callback);
255 }
256 
ReadMapFile(const std::string & map_file,const MapInfoParamsCallback & callback)257 inline bool ReadMapFile(const std::string& map_file,
258                 const MapInfoParamsCallback& callback) {
259   std::string content;
260   return ReadMapFile(map_file, callback, content);
261 }
262 
ReadProcessMaps(pid_t pid,const MapInfoCallback & callback)263 inline bool ReadProcessMaps(pid_t pid, const MapInfoCallback& callback) {
264   return ReadMapFile("/proc/" + std::to_string(pid) + "/maps", callback);
265 }
266 
ReadProcessMaps(pid_t pid,const MapInfoParamsCallback & callback,std::string & mapsBuffer)267 inline bool ReadProcessMaps(pid_t pid, const MapInfoParamsCallback& callback,
268                             std::string& mapsBuffer) {
269   return ReadMapFile("/proc/" + std::to_string(pid) + "/maps", callback, mapsBuffer);
270 }
271 
ReadProcessMaps(pid_t pid,const MapInfoParamsCallback & callback)272 inline bool ReadProcessMaps(pid_t pid, const MapInfoParamsCallback& callback) {
273   std::string content;
274   return ReadProcessMaps(pid, callback, content);
275 }
276 
ReadProcessMaps(pid_t pid,std::vector<MapInfo> * maps)277 inline bool ReadProcessMaps(pid_t pid, std::vector<MapInfo>* maps) {
278   return ReadProcessMaps(pid, [&](const MapInfo& mapinfo) { maps->emplace_back(mapinfo); });
279 }
280 
281 // Reads maps file and executes given callback for each mapping
282 // Warning: buffer should not be modified asynchronously while this function executes
283 template <class CallbackType>
ReadMapFileAsyncSafe(const char * map_file,void * buffer,size_t buffer_size,const CallbackType & callback)284 inline bool ReadMapFileAsyncSafe(const char* map_file, void* buffer, size_t buffer_size,
285                                  const CallbackType& callback) {
286   if (buffer == nullptr || buffer_size == 0) {
287     return false;
288   }
289 
290   int fd = open(map_file, O_RDONLY | O_CLOEXEC);
291   if (fd == -1) {
292     return false;
293   }
294 
295   char* char_buffer = reinterpret_cast<char*>(buffer);
296   size_t start = 0;
297   size_t read_bytes = 0;
298   char* line = nullptr;
299   bool read_complete = false;
300   while (true) {
301     ssize_t bytes =
302         TEMP_FAILURE_RETRY(read(fd, char_buffer + read_bytes, buffer_size - read_bytes - 1));
303     if (bytes <= 0) {
304       if (read_bytes == 0) {
305         close(fd);
306         return bytes == 0;
307       }
308       // Treat the last piece of data as the last line.
309       char_buffer[start + read_bytes] = '\n';
310       bytes = 1;
311       read_complete = true;
312     }
313     read_bytes += bytes;
314 
315     while (read_bytes > 0) {
316       char* newline = reinterpret_cast<char*>(memchr(&char_buffer[start], '\n', read_bytes));
317       if (newline == nullptr) {
318         break;
319       }
320       *newline = '\0';
321       line = &char_buffer[start];
322       start = newline - char_buffer + 1;
323       read_bytes -= newline - line + 1;
324 
325       // Ignore the return code, errors are okay.
326       ReadMapFileContent(line, callback);
327     }
328 
329     if (read_complete) {
330       close(fd);
331       return true;
332     }
333 
334     if (start == 0 && read_bytes == buffer_size - 1) {
335       // The buffer provided is too small to contain this line, give up
336       // and indicate failure.
337       close(fd);
338       return false;
339     }
340 
341     // Copy any leftover data to the front  of the buffer.
342     if (start > 0) {
343       if (read_bytes > 0) {
344         memmove(char_buffer, &char_buffer[start], read_bytes);
345       }
346       start = 0;
347     }
348   }
349 }
350 
351 /**
352  * A file memory mapping can be created such that it is only partially
353  * backed by the underlying file. i.e. the mapping size is larger than
354  * the file size.
355  *
356  * On builds that support larger than 4KB page-size, the common assumption
357  * that a file mapping is entirely backed by the underlying file, is
358  * more likely to be false.
359  *
360  * If an access to a region of the mapping beyond the end of the file
361  * occurs, there are 2 situations:
362  *     1) The access is between the end of the file and the next page
363  *        boundary. The kernel will facilitate this although there is
364  *        no file here.
365  *        Note: That writing this region does not persist any data to
366  *        the actual backing file.
367  *     2) The access is beyond the first page boundary after the end
368  *        of the file. This will cause a filemap_fault which does not
369  *        correspond to a valid file offset and the kernel will return
370  *        a SIGBUS.
371  *        See return value SIGBUS at:
372  *        https://man7.org/linux/man-pages/man2/mmap.2.html#RETURN_VALUE
373  *
374  * Userspace programs that parse /proc/<pid>/maps or /proc/<pid>/smaps
375  * to determine the extent of memory mappings which they then use as
376  * arguments to other syscalls or directly access; should be aware of
377  * the second case above (2) and not assume that file mappings are
378  * entirely back by the underlying file.
379  *
380  * This is especially important for operations that would cause a
381  * page-fault on the range described in (2). In this case userspace
382  * should either handle the signal or use the range backed by the
383  * underlying file for the desired operation.
384  *
385  *
386  * MappedFileSize() - Returns the size of the memory map backed
387  *                    by the underlying file; or 0 if not file-backed.
388  * @start_addr   - start address of the memory map.
389  * @end_addr     - end address of the memory map.
390  * @file_offset  - file offset of the backing file corresponding to the
391  *                 start of the memory map.
392  * @file_size    - size of the file (<file_path>) in bytes.
393  *
394  * NOTE: The arguments corresponds to the following fields of an entry
395  * in /proc/<pid>/maps:
396  *
397  * <start_addr>-< end_addr >  ...   <file_offset>  ...   ...        <file_path>
398  * 790b07dc6000-790b07dd9000  r--p  00000000       fe:09 21068208   /system/lib64/foo.so
399  *
400  * NOTE: Clients of this API should be aware that, although unlikely,
401  * it is possible for @file_size to change under us and race with
402  * the checks in MappedFileSize().
403  * Users should avoid concurrent modifications of @file_size, or
404  * use appropriate locking according to the usecase.
405  */
MappedFileSize(uint64_t start_addr,uint64_t end_addr,uint64_t file_offset,uint64_t file_size)406 inline uint64_t MappedFileSize(uint64_t start_addr, uint64_t end_addr,
407                                uint64_t file_offset, uint64_t file_size) {
408     uint64_t len = end_addr - start_addr;
409 
410     // This VMA may have been split from a larger file mapping; or the
411     // file may have been resized since the mapping was created.
412     if (file_offset > file_size) {
413         return 0;
414     }
415 
416     // Mapping exceeds file_size ?
417     if ((file_offset + len) > file_size) {
418         return file_size - file_offset;
419     }
420 
421     return len;
422 }
423 
424 /*
425  * MappedFileSize() - Returns the size of the memory map backed
426  *                    by the underlying file; or 0 if not file-backed.
427  */
MappedFileSize(const MapInfo & map)428 inline uint64_t MappedFileSize(const MapInfo& map) {
429     // Anon mapping or device?
430     if (map.name.empty() || map.name[0] != '/' ||
431           android::base::StartsWith(map.name, "/dev/")) {
432         return 0;
433     }
434 
435     struct stat file_stat;
436     if (stat(map.name.c_str(), &file_stat) != 0) {
437         return 0;
438     }
439 
440     return MappedFileSize(map.start, map.end, map.pgoff, file_stat.st_size);
441 }
442 
443 } /* namespace procinfo */
444 } /* namespace android */
445