aboutsummaryrefslogtreecommitdiff
path: root/fuzzpuzz.c
blob: f4f3b92ff0a22bb3df7488a11b536223a4ec5772 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/*
 * fuzzpuzz.c: Fuzzing frontend to all puzzles.
 */

/*
 * The idea here is that this front-end supports all back-ends and can
 * feed them save files.  It then asks the back-end to draw the puzzle
 * (through a null drawing API) and reserialises the state.  This
 * tests the deserialiser, the code for loading game descriptions, the
 * processing of move strings, the redraw code, and the serialisation
 * routines, but is still pretty quick.
 *
 * To use AFL++ to drive fuzzpuzz, you can do something like:
 *
 * CC=afl-cc cmake -B build-afl
 * cmake --build build-afl --target fuzzpuzz
 * mkdir fuzz-in && ln icons/''*.sav fuzz-in
 * afl-fuzz -i fuzz-in -o fuzz-out -x fuzzpuzz.dict -- build-afl/fuzzpuzz
 *
 * Similarly with Honggfuzz:
 *
 * CC=hfuzz-cc cmake -B build-honggfuzz
 * cmake --build build-honggfuzz --target fuzzpuzz
 * mkdir fuzz-corpus && ln icons/''*.sav fuzz-corpus
 * honggfuzz -s -i fuzz-corpus -w fuzzpuzz.dict -- build-honggfuzz/fuzzpuzz
 */

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef __AFL_FUZZ_TESTCASE_LEN
# include <unistd.h> /* read() is used by __AFL_FUZZ_TESTCASE_LEN. */
#endif

#include "puzzles.h"

#ifdef __AFL_FUZZ_INIT
__AFL_FUZZ_INIT();
#endif

#ifdef HAVE_HF_ITER
extern int HF_ITER(unsigned char **, size_t *);
#endif

static const char *fuzz_one(bool (*readfn)(void *, void *, int), void *rctx,
                            void (*rewindfn)(void *),
                            void (*writefn)(void *, const void *, int),
                            void *wctx)
{
    const char *err;
    char *gamename;
    int i, w, h;
    const game *ourgame = NULL;
    static const drawing_api drapi = { NULL };
    midend *me;

    err = identify_game(&gamename, readfn, rctx);
    if (err != NULL) return err;

    for (i = 0; i < gamecount; i++)
        if (strcmp(gamename, gamelist[i]->name) == 0)
            ourgame = gamelist[i];
    sfree(gamename);
    if (ourgame == NULL)
        return "Game not recognised";

    me = midend_new(NULL, ourgame, &drapi, NULL);

    rewindfn(rctx);
    err = midend_deserialise(me, readfn, rctx);
    if (err != NULL) {
        midend_free(me);
        return err;
    }
    w = h = INT_MAX;
    midend_size(me, &w, &h, false, 1);
    midend_redraw(me);
    midend_serialise(me, writefn, wctx);
    midend_free(me);
    return NULL;
}

static bool savefile_read(void *wctx, void *buf, int len)
{
    FILE *fp = (FILE *)wctx;
    int ret;

    ret = fread(buf, 1, len, fp);
    return (ret == len);
}

static void savefile_rewind(void *wctx)
{
    FILE *fp = (FILE *)wctx;

    rewind(fp);
}

static void savefile_write(void *wctx, const void *buf, int len)
{
    FILE *fp = (FILE *)wctx;

    fwrite(buf, 1, len, fp);
}

int main(int argc, char **argv)
{
    const char *err;
    int ret = -1;
    FILE *in = NULL;

    if (argc != 1) {
        fprintf(stderr, "usage: %s\n", argv[0]);
        exit(1);
    }

#ifdef __AFL_HAVE_MANUAL_CONTROL
    __AFL_INIT();
#endif

#ifdef __AFL_FUZZ_TESTCASE_LEN
    /*
     * AFL persistent mode, where we fuzz from a RAM buffer provided
     * by AFL in a loop.  This version can still be run standalone if
     * necessary, for instance to diagnose a crash.
     */

    while (__AFL_LOOP(10000)) {
        if (in != NULL) fclose(in);
        in = fmemopen(__AFL_FUZZ_TESTCASE_BUF, __AFL_FUZZ_TESTCASE_LEN, "r");
        if (in == NULL) {
            fprintf(stderr, "fmemopen failed");
            ret = 1;
            continue;
        }
#elif defined(HAVE_HF_ITER)
    /*
     * Honggfuzz persistent mode.  Unlike AFL persistent mode, the
     * resulting executable cannot be run outside of Honggfuzz.
     */
    while (true) {
        unsigned char *testcase_buf;
        size_t testcase_len;
        if (in != NULL) fclose(in);
        HF_ITER(&testcase_buf, &testcase_len);
        in = fmemopen(testcase_buf, testcase_len, "r");
        if (in == NULL) {
            fprintf(stderr, "fmemopen failed");
            ret = 1;
            continue;
        }
#else
    in = stdin;
    while (ret == -1) {
#endif
        err = fuzz_one(savefile_read, in, savefile_rewind,
                       savefile_write, stdout);
        if (err == NULL) {
            ret = 0;
        } else {
            fprintf(stderr, "%s\n", err);
            ret = 1;
        }
    }
    return ret;
}