1 /*
2  *
3  * honggfuzz - file operations
4  * -----------------------------------------
5  *
6  * Author: Robert Swiecki <swiecki@google.com>
7  *
8  * Copyright 2010-2015 by Google Inc. All Rights Reserved.
9  *
10  * Licensed under the Apache License, Version 2.0 (the "License"); you may
11  * not use this file except in compliance with the License. You may obtain
12  * a copy of the License at
13  *
14  * http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
19  * implied. See the License for the specific language governing
20  * permissions and limitations under the License.
21  *
22  */
23 
24 #include "common.h"
25 #include "files.h"
26 
27 #include <dirent.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <inttypes.h>
31 #include <stdint.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/mman.h>
36 #include <sys/stat.h>
37 #include <sys/types.h>
38 #include <unistd.h>
39 
40 #include "log.h"
41 #include "util.h"
42 
files_readFileToBufMax(char * fileName,uint8_t * buf,size_t fileMaxSz)43 size_t files_readFileToBufMax(char *fileName, uint8_t * buf, size_t fileMaxSz)
44 {
45     int fd = open(fileName, O_RDONLY);
46     if (fd == -1) {
47         PLOG_E("Couldn't open '%s' for R/O", fileName);
48         return 0UL;
49     }
50     DEFER(close(fd));
51 
52     size_t readSz = files_readFromFd(fd, buf, fileMaxSz);
53     if (readSz == 0) {
54         LOG_E("Couldn't read '%s' to a buf", fileName);
55         return 0UL;
56     }
57 
58     LOG_D("Read '%zu' bytes from '%s'", readSz, fileName);
59     return readSz;
60 }
61 
files_writeBufToFile(char * fileName,uint8_t * buf,size_t fileSz,int flags)62 bool files_writeBufToFile(char *fileName, uint8_t * buf, size_t fileSz, int flags)
63 {
64     int fd = open(fileName, flags, 0644);
65     if (fd == -1) {
66         PLOG_E("Couldn't open '%s' for R/O", fileName);
67         return false;
68     }
69     DEFER(close(fd));
70 
71     if (files_writeToFd(fd, buf, fileSz) == false) {
72         PLOG_E("Couldn't write '%zu' bytes to file '%s' (fd='%d')", fileSz, fileName, fd);
73         unlink(fileName);
74         return false;
75     }
76 
77     LOG_D("Written '%zu' bytes to '%s'", fileSz, fileName);
78     return true;
79 }
80 
files_writeToFd(int fd,uint8_t * buf,size_t fileSz)81 bool files_writeToFd(int fd, uint8_t * buf, size_t fileSz)
82 {
83     size_t writtenSz = 0;
84     while (writtenSz < fileSz) {
85         ssize_t sz = write(fd, &buf[writtenSz], fileSz - writtenSz);
86         if (sz < 0 && errno == EINTR)
87             continue;
88 
89         if (sz < 0)
90             return false;
91 
92         writtenSz += sz;
93     }
94     return true;
95 }
96 
files_writeStrToFd(int fd,char * str)97 bool files_writeStrToFd(int fd, char *str)
98 {
99     return files_writeToFd(fd, (uint8_t *) str, strlen(str));
100 }
101 
files_readFromFd(int fd,uint8_t * buf,size_t fileSz)102 size_t files_readFromFd(int fd, uint8_t * buf, size_t fileSz)
103 {
104     size_t readSz = 0;
105     while (readSz < fileSz) {
106         ssize_t sz = read(fd, &buf[readSz], fileSz - readSz);
107         if (sz < 0 && errno == EINTR)
108             continue;
109 
110         if (sz <= 0)
111             break;
112 
113         readSz += sz;
114     }
115     return readSz;
116 }
117 
files_exists(char * fileName)118 bool files_exists(char *fileName)
119 {
120     return (access(fileName, F_OK) != -1);
121 }
122 
files_writePatternToFd(int fd,off_t size,unsigned char p)123 bool files_writePatternToFd(int fd, off_t size, unsigned char p)
124 {
125     void *buf = malloc(size);
126     if (!buf) {
127         PLOG_W("Couldn't allocate memory");
128         return false;
129     }
130     DEFER(free(buf));
131 
132     memset(buf, p, (size_t) size);
133     int ret = files_writeToFd(fd, buf, size);
134 
135     return ret;
136 }
137 
files_readdir(honggfuzz_t * hfuzz)138 static bool files_readdir(honggfuzz_t * hfuzz)
139 {
140     DIR *dir = opendir(hfuzz->inputFile);
141     if (!dir) {
142         PLOG_E("Couldn't open dir '%s'", hfuzz->inputFile);
143         return false;
144     }
145     DEFER(closedir(dir));
146 
147     int count = 0;
148     for (;;) {
149         struct dirent de, *res;
150         if (readdir_r(dir, &de, &res) > 0) {
151             PLOG_E("Couldn't read the '%s' dir", hfuzz->inputFile);
152             return false;
153         }
154 
155         if (res == NULL && count > 0) {
156             LOG_I("%zu input files have been added to the list", hfuzz->fileCnt);
157             return true;
158         }
159 
160         if (res == NULL && count == 0) {
161             LOG_E("Directory '%s' doesn't contain any regular files", hfuzz->inputFile);
162             return false;
163         }
164 
165         char path[PATH_MAX];
166         snprintf(path, sizeof(path), "%s/%s", hfuzz->inputFile, res->d_name);
167         struct stat st;
168         if (stat(path, &st) == -1) {
169             LOG_W("Couldn't stat() the '%s' file", path);
170             continue;
171         }
172 
173         if (!S_ISREG(st.st_mode)) {
174             LOG_D("'%s' is not a regular file, skipping", path);
175             continue;
176         }
177 
178         if (st.st_size == 0ULL) {
179             LOG_D("'%s' is empty", path);
180             continue;
181         }
182 
183         if (st.st_size > (off_t) hfuzz->maxFileSz) {
184             LOG_W("File '%s' is bigger than maximal defined file size (-F): %" PRId64 " > %"
185                   PRId64, path, (int64_t) st.st_size, (int64_t) hfuzz->maxFileSz);
186             continue;
187         }
188 
189         if (!(hfuzz->files = realloc(hfuzz->files, sizeof(char *) * (count + 1)))) {
190             PLOG_E("Couldn't allocate memory");
191             return false;
192         }
193 
194         hfuzz->files[count] = strdup(path);
195         if (!hfuzz->files[count]) {
196             PLOG_E("Couldn't allocate memory");
197             return false;
198         }
199         hfuzz->fileCnt = ++count;
200         LOG_D("Added '%s' to the list of input files", path);
201     }
202 
203     abort();                    /* NOTREACHED */
204     return false;
205 }
206 
files_init(honggfuzz_t * hfuzz)207 bool files_init(honggfuzz_t * hfuzz)
208 {
209     hfuzz->files = util_Malloc(sizeof(char *));
210     if (hfuzz->externalCommand && !hfuzz->inputFile) {
211         hfuzz->fileCnt = 1;
212         hfuzz->files[0] = "CREATED";
213         LOG_I
214             ("No input file corpus specified, the external command '%s' is responsible for creating the fuzz files",
215              hfuzz->externalCommand);
216         return true;
217     }
218 
219     if (!hfuzz->inputFile) {
220         LOG_E("No input file/dir specified");
221         return false;
222     }
223 
224     struct stat st;
225     if (stat(hfuzz->inputFile, &st) == -1) {
226         PLOG_E("Couldn't stat the input file/dir '%s'", hfuzz->inputFile);
227         return false;
228     }
229 
230     if (S_ISDIR(st.st_mode)) {
231         return files_readdir(hfuzz);
232     }
233 
234     if (!S_ISREG(st.st_mode)) {
235         LOG_E("'%s' is not a regular file, nor a directory", hfuzz->inputFile);
236         return false;
237     }
238 
239     if (st.st_size > (off_t) hfuzz->maxFileSz) {
240         LOG_E("File '%s' is bigger than maximal defined file size (-F): %" PRId64 " > %" PRId64,
241               hfuzz->inputFile, (int64_t) st.st_size, (int64_t) hfuzz->maxFileSz);
242         return false;
243     }
244 
245     hfuzz->files[0] = hfuzz->inputFile;
246     hfuzz->fileCnt = 1;
247     return true;
248 }
249 
files_basename(char * path)250 char *files_basename(char *path)
251 {
252     char *base = strrchr(path, '/');
253     return base ? base + 1 : path;
254 }
255 
files_parseDictionary(honggfuzz_t * hfuzz)256 bool files_parseDictionary(honggfuzz_t * hfuzz)
257 {
258     FILE *fDict = fopen(hfuzz->dictionaryFile, "rb");
259     if (fDict == NULL) {
260         PLOG_E("Couldn't open '%s' - R/O mode", hfuzz->dictionaryFile);
261         return false;
262     }
263     DEFER(fclose(fDict));
264 
265     for (;;) {
266         char *lineptr = NULL;
267         size_t n = 0;
268         if (getdelim(&lineptr, &n, '\0', fDict) == -1) {
269             break;
270         }
271         if ((hfuzz->dictionary =
272              realloc(hfuzz->dictionary,
273                      (hfuzz->dictionaryCnt + 1) * sizeof(hfuzz->dictionary[0]))) == NULL) {
274             PLOG_E("Realloc failed (sz=%zu)",
275                    (hfuzz->dictionaryCnt + 1) * sizeof(hfuzz->dictionary[0]));
276             return false;
277         }
278         hfuzz->dictionary[hfuzz->dictionaryCnt] = lineptr;
279         LOG_D("Dictionary: loaded word: '%s' (len=%zu)",
280               hfuzz->dictionary[hfuzz->dictionaryCnt],
281               strlen(hfuzz->dictionary[hfuzz->dictionaryCnt]));
282         hfuzz->dictionaryCnt += 1;
283     }
284     LOG_I("Loaded %zu words from the dictionary", hfuzz->dictionaryCnt);
285     return true;
286 }
287 
288 /*
289  * dstExists argument can be used by caller for cases where existing destination
290  * file requires special handling (e.g. save unique crashes)
291  */
files_copyFile(const char * source,const char * destination,bool * dstExists)292 bool files_copyFile(const char *source, const char *destination, bool * dstExists)
293 {
294     if (dstExists)
295         *dstExists = false;
296     if (link(source, destination) == 0) {
297         return true;
298     } else {
299         if (errno == EEXIST) {
300             // Should kick-in before MAC, so avoid the hassle
301             if (dstExists)
302                 *dstExists = true;
303             return false;
304         } else {
305             PLOG_D("Couldn't link '%s' as '%s'", source, destination);
306             /*
307              * Don't fail yet as we might have a running env which doesn't allow
308              * hardlinks (e.g. SELinux)
309              */
310         }
311     }
312 
313     // Now try with a verbose POSIX alternative
314     int inFD, outFD, dstOpenFlags;
315     mode_t dstFilePerms;
316 
317     // O_EXCL is important for saving unique crashes
318     dstOpenFlags = O_CREAT | O_WRONLY | O_EXCL;
319     dstFilePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
320 
321     inFD = open(source, O_RDONLY);
322     if (inFD == -1) {
323         PLOG_D("Couldn't open '%s' source", source);
324         return false;
325     }
326     DEFER(close(inFD));
327 
328     struct stat inSt;
329     if (fstat(inFD, &inSt) == -1) {
330         PLOG_E("Couldn't fstat(fd='%d' fileName='%s')", inFD, source);
331         return false;
332     }
333 
334     outFD = open(destination, dstOpenFlags, dstFilePerms);
335     if (outFD == -1) {
336         if (errno == EEXIST) {
337             if (dstExists)
338                 *dstExists = true;
339         }
340         PLOG_D("Couldn't open '%s' destination", destination);
341         return false;
342     }
343     DEFER(close(outFD));
344 
345     uint8_t *inFileBuf = malloc(inSt.st_size);
346     if (!inFileBuf) {
347         PLOG_E("malloc(%zu) failed", (size_t) inSt.st_size);
348         return false;
349     }
350     DEFER(free(inFileBuf));
351 
352     size_t readSz = files_readFromFd(inFD, inFileBuf, (size_t) inSt.st_size);
353     if (readSz == 0) {
354         PLOG_E("Couldn't read '%s' to a buf", source);
355         return false;
356     }
357 
358     if (files_writeToFd(outFD, inFileBuf, readSz) == false) {
359         PLOG_E("Couldn't write '%zu' bytes to file '%s' (fd='%d')", (size_t) readSz,
360                destination, outFD);
361         unlink(destination);
362         return false;
363     }
364 
365     return true;
366 }
367 
files_parseBlacklist(honggfuzz_t * hfuzz)368 bool files_parseBlacklist(honggfuzz_t * hfuzz)
369 {
370     FILE *fBl = fopen(hfuzz->blacklistFile, "rb");
371     if (fBl == NULL) {
372         PLOG_E("Couldn't open '%s' - R/O mode", hfuzz->blacklistFile);
373         return false;
374     }
375     DEFER(fclose(fBl));
376 
377     char *lineptr = NULL;
378     size_t n = 0;
379     for (;;) {
380         if (getline(&lineptr, &n, fBl) == -1) {
381             break;
382         }
383         DEFER(free(lineptr));
384 
385         if ((hfuzz->blacklist =
386              realloc(hfuzz->blacklist,
387                      (hfuzz->blacklistCnt + 1) * sizeof(hfuzz->blacklist[0]))) == NULL) {
388             PLOG_E("realloc failed (sz=%zu)",
389                    (hfuzz->blacklistCnt + 1) * sizeof(hfuzz->blacklist[0]));
390             return false;
391         }
392 
393         hfuzz->blacklist[hfuzz->blacklistCnt] = strtoull(lineptr, 0, 16);
394         LOG_D("Blacklist: loaded %'" PRIu64 "'", hfuzz->blacklist[hfuzz->blacklistCnt]);
395 
396         // Verify entries are sorted so we can use interpolation search
397         if (hfuzz->blacklistCnt > 1) {
398             if (hfuzz->blacklist[hfuzz->blacklistCnt - 1] > hfuzz->blacklist[hfuzz->blacklistCnt]) {
399                 LOG_F
400                     ("Blacklist file not sorted. Use 'tools/createStackBlacklist.sh' to sort records");
401                 return false;
402             }
403         }
404         hfuzz->blacklistCnt += 1;
405     }
406 
407     if (hfuzz->blacklistCnt > 0) {
408         LOG_I("Loaded %zu stack hash(es) from the blacklist file", hfuzz->blacklistCnt);
409     } else {
410         LOG_F("Empty stack hashes blacklist file '%s'", hfuzz->blacklistFile);
411     }
412     return true;
413 }
414 
files_mapFile(char * fileName,off_t * fileSz,int * fd,bool isWritable)415 uint8_t *files_mapFile(char *fileName, off_t * fileSz, int *fd, bool isWritable)
416 {
417     int mmapProt = PROT_READ;
418     if (isWritable) {
419         mmapProt |= PROT_WRITE;
420     }
421 
422     if ((*fd = open(fileName, O_RDONLY)) == -1) {
423         PLOG_E("Couldn't open() '%s' file in R/O mode", fileName);
424         return NULL;
425     }
426 
427     struct stat st;
428     if (fstat(*fd, &st) == -1) {
429         PLOG_E("Couldn't stat() the '%s' file", fileName);
430         close(*fd);
431         return NULL;
432     }
433 
434     uint8_t *buf;
435     if ((buf = mmap(NULL, st.st_size, mmapProt, MAP_PRIVATE, *fd, 0)) == MAP_FAILED) {
436         PLOG_E("Couldn't mmap() the '%s' file", fileName);
437         close(*fd);
438         return NULL;
439     }
440 
441     *fileSz = st.st_size;
442     return buf;
443 }
444 
files_readPidFromFile(const char * fileName,pid_t * pidPtr)445 bool files_readPidFromFile(const char *fileName, pid_t * pidPtr)
446 {
447     FILE *fPID = fopen(fileName, "rb");
448     if (fPID == NULL) {
449         PLOG_E("Couldn't open '%s' - R/O mode", fileName);
450         return false;
451     }
452     DEFER(fclose(fPID));
453 
454     char *lineptr = NULL;
455     size_t lineSz = 0;
456     if (getline(&lineptr, &lineSz, fPID) == -1) {
457         if (lineSz == 0) {
458             LOG_E("Empty PID file (%s)", fileName);
459             return false;
460         }
461     }
462     DEFER(free(lineptr));
463 
464     *pidPtr = atoi(lineptr);
465     if (*pidPtr < 1) {
466         LOG_E("Invalid PID read from '%s' file", fileName);
467         return false;
468     }
469 
470     return true;
471 }
472