skip to content
==> tests/fuzz.c <==
1/* Hostile modules: random mutations of real modules, and random bytes. Every one must be refused or
2 * run to a result, a trap or an interrupt; none may crash or touch memory it shouldn't (run this
3 * under --profile asan). Deterministic: a fixed seed. */
4#define _XOPEN_SOURCE 700
5#include "mesh/check.h"
6#include "wasm/builder.h"
7#include "wasm/interp.h"
8
9#include <pthread.h>
10#include <stdlib.h>
11#include <string.h>
12#include <time.h>
13
14#include "fixtures/prog.h"
15
16static _Atomic uint64_t epoch;
17static _Atomic int stop;
18
19static void *ticker(void *arg) {
20    (void)arg;
21    struct timespec ms = {0, 1000000};
22    while (!atomic_load(&stop)) {
23        atomic_fetch_add(&epoch, 1);
24        nanosleep(&ms, NULL);
25    }
26    return NULL;
27}
28
29static uint64_t rng = 0x9e3779b97f4a7c15ull;
30static uint32_t next(void) {
31    rng ^= rng << 13;
32    rng ^= rng >> 7;
33    rng ^= rng << 17;
34    return (uint32_t)rng;
35}
36
37static int host_any(wasm_instance *I, void *ctx, uint64_t *io) {
38    (void)I, (void)ctx;
39    io[0] = 1;
40    return WASM_NUM_OK;
41}
42
43static long compiled, instantiated, calls, traps, by_trap[16];
44
45static void try_module(const uint8_t *b, size_t n) {
46    char err[256];
47    wasm_compiled *c = wasm_compile(b, n, WASM_F_INTERP, err, sizeof err);
48    if (!c) return;
49    compiled++;
50    /* Whatever it imports, offer a function of that name and signature. */
51    const wasm_module *m = wasm_compiled_module(c);
52    wasm_host_func hosts[8];
53    char sigs[8][40];
54    size_t nh = 0;
55    for (uint32_t i = 0; i < m->nimports && nh < 8; i++) {
56        const wasm_import *im = &m->imports[i];
57        if (im->kind != WASM_EXTERN_FUNC) continue;
58        const wasm_functype *t = &m->types[im->func_type];
59        if (t->nparams + t->nresults + 2 > sizeof sigs[0]) continue;
60        char *s = sigs[nh];
61        size_t k = 0;
62        for (uint32_t p = 0; p < t->nparams; p++) s[k++] = t->params[p] == WASM_I64 ? 'l' : t->params[p] == WASM_F32 ? 'f' : t->params[p] == WASM_F64 ? 'd' : 'i';
63        s[k++] = '>';
64        for (uint32_t p = 0; p < t->nresults; p++) s[k++] = t->results[p] == WASM_I64 ? 'l' : t->results[p] == WASM_F32 ? 'f' : t->results[p] == WASM_F64 ? 'd' : 'i';
65        s[k] = 0;
66        static char names[8][64], mods[8][64];
67        snprintf(mods[nh], sizeof mods[nh], "%.*s", (int)im->module_len, im->module);
68        snprintf(names[nh], sizeof names[nh], "%.*s", (int)im->name_len, im->name);
69        hosts[nh] = (wasm_host_func){mods[nh], names[nh], s, host_any, NULL};
70        nh++;
71    }
72    wasm_store_limits lim = {16u << 20, 4, 1, 1u << 14, 1000};
73    wasm_instance *I = wasm_instantiate(c, hosts, nh, &lim, NULL, err, sizeof err);
74    if (I) {
75        instantiated++;
76        for (uint32_t f = m->nfunc_imports; f < m->nfuncs && f < m->nfunc_imports + 16; f++) {
77            const wasm_functype *t = wasm_func_type(m, f);
78            uint64_t *io = calloc(t->nparams + t->nresults + 1, sizeof *io);
79            wasm_set_epoch(I, &epoch);
80            wasm_set_deadline(I, atomic_load(&epoch) + 3);
81            int r = wasm_call(I, f, io);
82            calls++;
83            free(io);
84            if (r != WASM_NUM_OK) {
85                traps++;
86                if (r >= 0 && r < 16) by_trap[r]++;
87                break; /* a trapped instance is not called again (as meshd) */
88            }
89        }
90        wasm_instance_free(I);
91    }
92    wasm_compiled_free(c);
93}
94
95/* A module that uses most instructions: the builder's, with a loop, memory, a table, calls. */
96static void seed_module(wasm_buf *out) {
97    wasm_builder w = {0};
98    uint32_t t = wasm_builder_type(&w, "ii>i");
99    wasm_builder_import_func(&w, "mesh", "info", wasm_builder_type(&w, "i>i"));
100    uint32_t f;
101    wasm_buf *b = wasm_builder_func_add(&w, t, "il", &f);
102    static const uint8_t body[] = {0x03, 0x40, 0x20, 2, 0x41, 1, 0x6a, 0x22, 2, 0x20, 0, 0x28, 2, 4, 0x20, 1, 0x36, 2, 8,
103                                   0x20, 1, 0x10, 0, 0x1a, 0x20, 2, 0x41, 10, 0x49, 0x0d, 0, 0x0b, 0x20, 0, 0x20, 1, 0x11,
104                                   0, 0, 0x42, 5, 0x21, 3, 0x20, 3, 0xa7, 0x6a};
105    wb_bytes(b, body, sizeof body);
106    wasm_builder_memory(&w, 1, 4, true);
107    wasm_builder_table(&w, 2, 2, true);
108    uint32_t el[] = {1, 1};
109    wasm_builder_elem(&w, 0, el, 2);
110    wasm_builder_data(&w, 0, "abcdefgh", 8);
111    wasm_builder_add_export(&w, "f", WASM_EXTERN_FUNC, f);
112    wasm_builder_finish(&w, out);
113    wasm_builder_free(&w);
114}
115
116int main(void) {
117    pthread_t th;
118    CHECK(pthread_create(&th, NULL, ticker, NULL) == 0);
119    wasm_buf seed;
120    seed_module(&seed);
121    const struct {
122        const uint8_t *b;
123        size_t n;
124    } seeds[] = {{seed.data, seed.len}, {prog_wasm, prog_wasm_len}};
125    uint8_t *buf = malloc(prog_wasm_len + seed.len + 64);
126    for (int round = 0; round < 6000; round++) {
127        const uint8_t *src = seeds[round & 1].b;
128        size_t n = seeds[round & 1].n;
129        memcpy(buf, src, n);
130        int edits = 1 + (int)(next() % 4);
131        for (int e = 0; e < edits; e++) {
132            uint32_t at = next() % (uint32_t)n;
133            switch (next() % 4) {
134            case 0: buf[at] ^= (uint8_t)(1u << (next() % 8)); break;
135            case 1: buf[at] = (uint8_t)next(); break;
136            case 2: buf[at] = (uint8_t)(next() % 2 ? 0xff : 0x00); break;
137            case 3: if (n > 9) n--; break; /* truncate */
138            }
139        }
140        try_module(buf, n);
141    }
142    for (int round = 0; round < 3000; round++) {
143        size_t n = 8 + next() % 64;
144        memcpy(buf, "\0asm\x01\0\0\0", 8);
145        for (size_t i = 8; i < n; i++) buf[i] = (uint8_t)next();
146        try_module(buf, n);
147    }
148    free(buf);
149    wasm_buf_free(&seed);
150    atomic_store(&stop, 1);
151    pthread_join(th, NULL);
152    /* The mutations must have reached every stage, not just the decoder. */
153    CHECK(compiled > 100);
154    CHECK(instantiated > 50);
155    CHECK(calls > 50);
156    fprintf(stderr, "fuzz: %ld compiled, %ld instantiated, %ld calls, %ld traps (", compiled, instantiated, calls, traps);
157    for (int k = 0; k < 16; k++)
158        if (by_trap[k]) fprintf(stderr, " %d:%ld", k, by_trap[k]);
159    fprintf(stderr, " )\n");
160    return mesh_check_done();
161}