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 "input.h"
25
26#include <dirent.h>
27#include <errno.h>
28#include <fcntl.h>
29#include <inttypes.h>
30#include <stdint.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <sys/mman.h>
35#include <sys/socket.h>
36#include <sys/stat.h>
37#include <sys/types.h>
38#include <unistd.h>
39
40#include "libcommon/common.h"
41#include "libcommon/files.h"
42
43#if defined(_HF_ARCH_LINUX)
44#include <sys/syscall.h>
45#if defined(__NR_memfd_create)
46#include <linux/memfd.h>
47#endif /* defined(__NR_memfd_create) */
48#endif /* defined(_HF_ARCH_LINUX) */
49
50#include "libcommon/log.h"
51#include "libcommon/util.h"
52
53static bool input_getDirStatsAndRewind(honggfuzz_t* hfuzz) {
54    rewinddir(hfuzz->io.inputDirPtr);
55
56    size_t maxSize = 0U;
57    size_t fileCnt = 0U;
58    for (;;) {
59        errno = 0;
60        struct dirent* entry = readdir(hfuzz->io.inputDirPtr);
61        if (entry == NULL && errno == EINTR) {
62            continue;
63        }
64        if (entry == NULL && errno != 0) {
65            PLOG_W("readdir('%s')", hfuzz->io.inputDir);
66            return false;
67        }
68        if (entry == NULL) {
69            break;
70        }
71
72        char fname[PATH_MAX];
73        snprintf(fname, sizeof(fname), "%s/%s", hfuzz->io.inputDir, entry->d_name);
74        LOG_D("Analyzing file '%s'", fname);
75
76        struct stat st;
77        if (stat(fname, &st) == -1) {
78            LOG_W("Couldn't stat() the '%s' file", fname);
79            continue;
80        }
81        if (!S_ISREG(st.st_mode)) {
82            LOG_D("'%s' is not a regular file, skipping", fname);
83            continue;
84        }
85        if (hfuzz->maxFileSz != 0UL && st.st_size > (off_t)hfuzz->maxFileSz) {
86            LOG_W("File '%s' is bigger than maximal defined file size (-F): %" PRId64 " > %" PRId64,
87                fname, (int64_t)st.st_size, (int64_t)hfuzz->maxFileSz);
88        }
89        if ((size_t)st.st_size > maxSize) {
90            maxSize = st.st_size;
91        }
92        fileCnt++;
93    }
94
95    ATOMIC_SET(hfuzz->io.fileCnt, fileCnt);
96    if (hfuzz->maxFileSz == 0U) {
97        if (maxSize < 8192) {
98            hfuzz->maxFileSz = 8192;
99        } else {
100            hfuzz->maxFileSz = maxSize;
101        }
102    }
103    if (hfuzz->persistent && hfuzz->maxFileSz > (1024U * 128)) {
104        LOG_D("Persistent mode enabled, lowering maximum input size to 128KiB");
105        hfuzz->maxFileSz = 1024U * 128;
106    }
107
108    if (hfuzz->io.fileCnt == 0U) {
109        LOG_W("No usable files in the input directory '%s'", hfuzz->io.inputDir);
110        return false;
111    }
112
113    LOG_D("Re-read the '%s', maxFileSz:%zu, number of usable files:%zu", hfuzz->io.inputDir,
114        hfuzz->maxFileSz, hfuzz->io.fileCnt);
115
116    rewinddir(hfuzz->io.inputDirPtr);
117
118    return true;
119}
120
121bool input_getNext(run_t* run, char* fname, bool rewind) {
122    static pthread_mutex_t input_mutex = PTHREAD_MUTEX_INITIALIZER;
123    MX_SCOPED_LOCK(&input_mutex);
124
125    if (run->global->io.fileCnt == 0U) {
126        return false;
127    }
128
129    for (;;) {
130        errno = 0;
131        struct dirent* entry = readdir(run->global->io.inputDirPtr);
132        if (entry == NULL && errno == EINTR) {
133            continue;
134        }
135        if (entry == NULL && errno != 0) {
136            PLOG_W("readdir_r('%s')", run->global->io.inputDir);
137            return false;
138        }
139        if (entry == NULL && rewind == false) {
140            return false;
141        }
142        if (entry == NULL && rewind == true) {
143            if (input_getDirStatsAndRewind(run->global) == false) {
144                LOG_E("input_getDirStatsAndRewind('%s')", run->global->io.inputDir);
145                return false;
146            }
147            continue;
148        }
149
150        snprintf(fname, PATH_MAX, "%s/%s", run->global->io.inputDir, entry->d_name);
151
152        struct stat st;
153        if (stat(fname, &st) == -1) {
154            LOG_W("Couldn't stat() the '%s' file", fname);
155            continue;
156        }
157        if (!S_ISREG(st.st_mode)) {
158            LOG_D("'%s' is not a regular file, skipping", fname);
159            continue;
160        }
161        return true;
162    }
163}
164
165bool input_init(honggfuzz_t* hfuzz) {
166    hfuzz->io.fileCnt = 0U;
167
168    if (!hfuzz->io.inputDir) {
169        LOG_W("No input file/dir specified");
170        return false;
171    }
172
173    int dir_fd = open(hfuzz->io.inputDir, O_DIRECTORY | O_RDONLY | O_CLOEXEC);
174    if (dir_fd == -1) {
175        PLOG_W("open('%s', O_DIRECTORY|O_RDONLY|O_CLOEXEC)", hfuzz->io.inputDir);
176        return false;
177    }
178    if ((hfuzz->io.inputDirPtr = fdopendir(dir_fd)) == NULL) {
179        close(dir_fd);
180        PLOG_W("opendir('%s')", hfuzz->io.inputDir);
181        return false;
182    }
183    if (input_getDirStatsAndRewind(hfuzz) == false) {
184        hfuzz->io.fileCnt = 0U;
185        LOG_W("input_getDirStatsAndRewind('%s')", hfuzz->io.inputDir);
186        return false;
187    }
188
189    return true;
190}
191
192bool input_parseDictionary(honggfuzz_t* hfuzz) {
193    FILE* fDict = fopen(hfuzz->dictionaryFile, "rb");
194    if (fDict == NULL) {
195        PLOG_W("Couldn't open '%s' - R/O mode", hfuzz->dictionaryFile);
196        return false;
197    }
198    defer { fclose(fDict); };
199
200    char* lineptr = NULL;
201    size_t n = 0;
202    defer { free(lineptr); };
203    for (;;) {
204        ssize_t len = getdelim(&lineptr, &n, '\n', fDict);
205        if (len == -1) {
206            break;
207        }
208        if (len > 1 && lineptr[len - 1] == '\n') {
209            lineptr[len - 1] = '\0';
210            len--;
211        }
212        if (lineptr[0] == '#') {
213            continue;
214        }
215        if (lineptr[0] == '\n') {
216            continue;
217        }
218        if (lineptr[0] == '\0') {
219            continue;
220        }
221        char bufn[1025] = {0};
222        char bufv[1025] = {0};
223        if (sscanf(lineptr, "\"%1024s", bufv) != 1 &&
224            sscanf(lineptr, "%1024[^=]=\"%1024s", bufn, bufv) != 2) {
225            LOG_W("Incorrect dictionary entry: '%s'. Skipping", lineptr);
226            continue;
227        }
228
229        char* s = util_StrDup(bufv);
230        struct strings_t* str = (struct strings_t*)util_Malloc(sizeof(struct strings_t));
231        str->len = util_decodeCString(s);
232        str->s = s;
233        hfuzz->dictionaryCnt += 1;
234        TAILQ_INSERT_TAIL(&hfuzz->dictq, str, pointers);
235
236        LOG_D("Dictionary: loaded word: '%s' (len=%zu)", str->s, str->len);
237    }
238    LOG_I("Loaded %zu words from the dictionary", hfuzz->dictionaryCnt);
239    return true;
240}
241
242bool input_parseBlacklist(honggfuzz_t* hfuzz) {
243    FILE* fBl = fopen(hfuzz->blacklistFile, "rb");
244    if (fBl == NULL) {
245        PLOG_W("Couldn't open '%s' - R/O mode", hfuzz->blacklistFile);
246        return false;
247    }
248    defer { fclose(fBl); };
249
250    char* lineptr = NULL;
251    /* lineptr can be NULL, but it's fine for free() */
252    defer { free(lineptr); };
253    size_t n = 0;
254    for (;;) {
255        if (getline(&lineptr, &n, fBl) == -1) {
256            break;
257        }
258
259        if ((hfuzz->blacklist = util_Realloc(hfuzz->blacklist,
260                 (hfuzz->blacklistCnt + 1) * sizeof(hfuzz->blacklist[0]))) == NULL) {
261            PLOG_W(
262                "realloc failed (sz=%zu)", (hfuzz->blacklistCnt + 1) * sizeof(hfuzz->blacklist[0]));
263            return false;
264        }
265
266        hfuzz->blacklist[hfuzz->blacklistCnt] = strtoull(lineptr, 0, 16);
267        LOG_D("Blacklist: loaded %'" PRIu64 "'", hfuzz->blacklist[hfuzz->blacklistCnt]);
268
269        // Verify entries are sorted so we can use interpolation search
270        if (hfuzz->blacklistCnt > 1) {
271            if (hfuzz->blacklist[hfuzz->blacklistCnt - 1] > hfuzz->blacklist[hfuzz->blacklistCnt]) {
272                LOG_F(
273                    "Blacklist file not sorted. Use 'tools/createStackBlacklist.sh' to sort "
274                    "records");
275                return false;
276            }
277        }
278        hfuzz->blacklistCnt += 1;
279    }
280
281    if (hfuzz->blacklistCnt > 0) {
282        LOG_I("Loaded %zu stack hash(es) from the blacklist file", hfuzz->blacklistCnt);
283    } else {
284        LOG_F("Empty stack hashes blacklist file '%s'", hfuzz->blacklistFile);
285    }
286    return true;
287}
288