skip to content
==> include/wasm/reader.h <==
1/* @ajax/wasm-reader: a cursor over a WebAssembly binary. Every read is bounds-checked; the first
2 * failure is recorded as "<what> (at offset 0x<n>)", wasmparser's style, and every later read
3 * returns zeroes, so a parser can read straight through and check `failed` at the end.
4 * Header-only. */
5#ifndef WASM_READER_H
6#define WASM_READER_H
7
8#include "wasm/leb128.h"
9#include "wasm/types.h"
10
11#include <stdarg.h>
12#include <stdbool.h>
13#include <stddef.h>
14#include <stdint.h>
15#include <stdio.h>
16#include <string.h>
17
18typedef struct {
19    const uint8_t *base; /* the binary's first byte: offsets are from here */
20    const uint8_t *p;
21    const uint8_t *end;
22    bool failed;
23    char *err; /* the first failure's message, or NULL to keep none */
24    size_t errlen;
25} wasm_reader;
26
27static inline wasm_reader wasm_reader_at(const uint8_t *base, const uint8_t *p, const uint8_t *end, char *err,
28                                         size_t errlen) {
29    wasm_reader r = {base, p, end, false, err, errlen};
30    if (err && errlen) err[0] = 0;
31    return r;
32}
33
34static inline size_t wasm_rd_offset(const wasm_reader *r) { return (size_t)(r->p - r->base); }
35static inline bool wasm_rd_eof(const wasm_reader *r) { return r->p >= r->end; }
36static inline size_t wasm_rd_left(const wasm_reader *r) { return r->p < r->end ? (size_t)(r->end - r->p) : 0; }
37
38/* Records a failure at offset `at` (only the first one counts). Returns false. */
39static inline bool wasm_rd_fail_at(wasm_reader *r, size_t at, const char *fmt, ...) {
40    if (!r->failed) {
41        r->failed = true;
42        if (r->err && r->errlen) {
43            va_list ap;
44            va_start(ap, fmt);
45            int n = vsnprintf(r->err, r->errlen, fmt, ap);
46            va_end(ap);
47            if (n >= 0 && (size_t)n < r->errlen) snprintf(r->err + n, r->errlen - (size_t)n, " (at offset 0x%zx)", at);
48        }
49    }
50    r->p = r->end;
51    return false;
52}
53#define wasm_rd_fail(r, ...) wasm_rd_fail_at((r), wasm_rd_offset(r), __VA_ARGS__)
54
55static inline uint8_t wasm_rd_u8(wasm_reader *r) {
56    if (r->p >= r->end) {
57        wasm_rd_fail(r, "unexpected end-of-file");
58        return 0;
59    }
60    return *r->p++;
61}
62
63static inline uint32_t wasm_rd_u32(wasm_reader *r) {
64    uint32_t v = 0;
65    size_t n = r->p < r->end ? wasm_leb_u32(r->p, r->end, &v) : 0;
66    if (!n) {
67        wasm_rd_fail(r, r->p >= r->end ? "unexpected end-of-file" : "invalid var_u32");
68        return 0;
69    }
70    r->p += n;
71    return v;
72}
73
74static inline uint64_t wasm_rd_u64(wasm_reader *r) {
75    uint64_t v = 0;
76    size_t n = r->p < r->end ? wasm_leb_u64(r->p, r->end, &v) : 0;
77    if (!n) {
78        wasm_rd_fail(r, r->p >= r->end ? "unexpected end-of-file" : "invalid var_u64");
79        return 0;
80    }
81    r->p += n;
82    return v;
83}
84
85static inline int32_t wasm_rd_s32(wasm_reader *r) {
86    int32_t v = 0;
87    size_t n = r->p < r->end ? wasm_leb_s32(r->p, r->end, &v) : 0;
88    if (!n) {
89        wasm_rd_fail(r, r->p >= r->end ? "unexpected end-of-file" : "invalid var_i32");
90        return 0;
91    }
92    r->p += n;
93    return v;
94}
95
96static inline int64_t wasm_rd_s33(wasm_reader *r) {
97    int64_t v = 0;
98    size_t n = r->p < r->end ? wasm_leb_s33(r->p, r->end, &v) : 0;
99    if (!n) {
100        wasm_rd_fail(r, r->p >= r->end ? "unexpected end-of-file" : "invalid var_s33");
101        return 0;
102    }
103    r->p += n;
104    return v;
105}
106
107static inline int64_t wasm_rd_s64(wasm_reader *r) {
108    int64_t v = 0;
109    size_t n = r->p < r->end ? wasm_leb_s64(r->p, r->end, &v) : 0;
110    if (!n) {
111        wasm_rd_fail(r, r->p >= r->end ? "unexpected end-of-file" : "invalid var_i64");
112        return 0;
113    }
114    r->p += n;
115    return v;
116}
117
118/* n raw bytes: a pointer to them, or NULL (and a failure) past the end. */
119static inline const uint8_t *wasm_rd_bytes(wasm_reader *r, size_t n) {
120    if (wasm_rd_left(r) < n) {
121        wasm_rd_fail(r, "unexpected end-of-file");
122        return NULL;
123    }
124    const uint8_t *s = r->p;
125    r->p += n;
126    return s;
127}
128
129/* Little-endian fixed-width values (float constants). */
130static inline uint32_t wasm_rd_le32(wasm_reader *r) {
131    const uint8_t *b = wasm_rd_bytes(r, 4);
132    return b ? (uint32_t)b[0] | (uint32_t)b[1] << 8 | (uint32_t)b[2] << 16 | (uint32_t)b[3] << 24 : 0;
133}
134static inline uint64_t wasm_rd_le64(wasm_reader *r) {
135    uint64_t lo = wasm_rd_le32(r);
136    uint64_t hi = wasm_rd_le32(r);
137    return lo | hi << 32;
138}
139
140/* A string is valid UTF-8 (no overlongs, surrogates, or code points past U+10FFFF). */
141static inline bool wasm_utf8_valid(const uint8_t *s, size_t n) {
142    size_t i = 0;
143    while (i < n) {
144        uint8_t c = s[i];
145        size_t len;
146        uint32_t cp, min;
147        if (c < 0x80) {
148            i++;
149            continue;
150        } else if ((c & 0xe0) == 0xc0) {
151            len = 2, cp = c & 0x1f, min = 0x80;
152        } else if ((c & 0xf0) == 0xe0) {
153            len = 3, cp = c & 0x0f, min = 0x800;
154        } else if ((c & 0xf8) == 0xf0) {
155            len = 4, cp = c & 0x07, min = 0x10000;
156        } else {
157            return false;
158        }
159        if (i + len > n) return false;
160        for (size_t k = 1; k < len; k++) {
161            if ((s[i + k] & 0xc0) != 0x80) return false;
162            cp = cp << 6 | (s[i + k] & 0x3f);
163        }
164        if (cp < min || cp > 0x10ffff || (cp >= 0xd800 && cp <= 0xdfff)) return false;
165        i += len;
166    }
167    return true;
168}
169
170/* A name: u32 length, then that many bytes of UTF-8. */
171static inline const uint8_t *wasm_rd_name(wasm_reader *r, uint32_t *len) {
172    size_t at = wasm_rd_offset(r);
173    uint32_t n = wasm_rd_u32(r);
174    const uint8_t *s = wasm_rd_bytes(r, n);
175    if (s && !wasm_utf8_valid(s, n)) {
176        wasm_rd_fail_at(r, at, "malformed UTF-8 encoding");
177        s = NULL;
178    }
179    *len = s ? n : 0;
180    return s;
181}
182
183/* A vector's count, refused when it can't possibly fit in what is left (each element takes at
184 * least min_bytes), so a hostile count never makes us allocate. */
185static inline uint32_t wasm_rd_count(wasm_reader *r, size_t min_bytes, const char *what) {
186    uint32_t n = wasm_rd_u32(r);
187    if (!r->failed && min_bytes && (uint64_t)n * min_bytes > wasm_rd_left(r)) {
188        wasm_rd_fail(r, "%s count is out of bounds", what);
189        return 0;
190    }
191    return n;
192}
193
194#endif