Skip to content

Commit 4216894

Browse files
authored
New fuzzer (#1126)
This adds a new method of fuzzing, "translate to fuzz" which means we consider the input to be a stream of data that we translate into a valid wasm module. It's sort of like a random seed for a process that creates a random wasm module. By using the input that way, we can explore the space of valid wasm modules quickly, and it makes afl-fuzz integration easy. Also adds a "fuzz binary" option which is similar to "fuzz execution". It makes wasm-opt not only execute the code before and after opts, but also write to binary and read from it, helping to fuzz the binary format.
1 parent 5295929 commit 4216894

18 files changed

+2900
-85
lines changed

auto_update_tests.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
cmd += ['--source-map', os.path.join('test', wasm + '.map'), '-o', 'a.wasm']
4646
run_command(cmd)
4747

48-
4948
for dot_s_dir in ['dot_s', 'llvm_autogenerated']:
5049
for s in sorted(os.listdir(os.path.join('test', dot_s_dir))):
5150
if not s.endswith('.s'): continue
@@ -102,6 +101,14 @@
102101
cmd = WASM_OPT + opts + ['split.wast', '--print']
103102
actual += run_command(cmd)
104103
with open(os.path.join('test', 'passes', passname + ('.bin' if binary else '') + '.txt'), 'w') as o: o.write(actual)
104+
if 'emit-js-wrapper' in t:
105+
with open('a.js') as i:
106+
with open(t + '.js', 'w') as o:
107+
o.write(i.read())
108+
if 'emit-spec-wrapper' in t:
109+
with open('a.wat') as i:
110+
with open(t + '.wat', 'w') as o:
111+
o.write(i.read())
105112

106113
print '\n[ checking wasm-opt -o notation... ]\n'
107114

check.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,15 @@
106106

107107
fail_if_not_identical(actual, open(os.path.join('test', 'passes', passname + ('.bin' if binary else '') + '.txt'), 'rb').read())
108108

109+
if 'emit-js-wrapper' in t:
110+
with open('a.js') as actual:
111+
with open(t + '.js') as expected:
112+
fail_if_not_identical(actual.read(), expected.read())
113+
if 'emit-spec-wrapper' in t:
114+
with open('a.wat') as actual:
115+
with open(t + '.wat') as expected:
116+
fail_if_not_identical(actual.read(), expected.read())
117+
109118
print '[ checking asm2wasm testcases... ]\n'
110119

111120
for asm in tests:

src/tools/execution-results.h

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright 2017 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
//
18+
// Shared execution result checking code
19+
//
20+
21+
#include "wasm.h"
22+
#include "shell-interface.h"
23+
24+
namespace wasm {
25+
26+
static bool areBitwiseEqual(Literal a, Literal b) {
27+
if (a == b) return true;
28+
// accept equal nans if equal in all bits
29+
if (a.type != b.type) return false;
30+
if (a.type == f32) {
31+
return a.reinterpreti32() == b.reinterpreti32();
32+
} else if (a.type == f64) {
33+
return a.reinterpreti64() == b.reinterpreti64();
34+
}
35+
return false;
36+
}
37+
38+
// gets execution results from a wasm module. this is useful for fuzzing
39+
//
40+
// we can only get results when there are no imports. we then call each method
41+
// that has a result, with some values
42+
struct ExecutionResults {
43+
std::map<Name, Literal> results;
44+
45+
// get results of execution
46+
void get(Module& wasm) {
47+
if (wasm.imports.size() > 0) {
48+
std::cout << "[fuzz-exec] imports, so quitting\n";
49+
return;
50+
}
51+
for (auto& func : wasm.functions) {
52+
if (func->result != none) {
53+
// this has a result
54+
results[func->name] = run(func.get(), wasm);
55+
std::cout << "[fuzz-exec] note result: " << func->name.str << " => " << results[func->name] << '\n';
56+
} else {
57+
// no result, run it anyhow (it might modify memory etc.)
58+
run(func.get(), wasm);
59+
std::cout << "[fuzz-exec] no result for void func: " << func->name.str << '\n';
60+
}
61+
}
62+
std::cout << "[fuzz-exec] " << results.size() << " results noted\n";
63+
}
64+
65+
// get current results and check them against previous ones
66+
void check(Module& wasm) {
67+
ExecutionResults optimizedResults;
68+
optimizedResults.get(wasm);
69+
if (optimizedResults != *this) {
70+
std::cout << "[fuzz-exec] optimization passes changed execution results";
71+
abort();
72+
}
73+
std::cout << "[fuzz-exec] " << results.size() << " results match\n";
74+
}
75+
76+
bool operator==(ExecutionResults& other) {
77+
for (auto& iter : results) {
78+
auto name = iter.first;
79+
if (other.results.find(name) != other.results.end()) {
80+
std::cout << "[fuzz-exec] comparing " << name << '\n';
81+
if (!areBitwiseEqual(results[name], other.results[name])) {
82+
std::cout << "not identical!\n";
83+
abort();
84+
}
85+
}
86+
}
87+
return true;
88+
}
89+
90+
bool operator!=(ExecutionResults& other) {
91+
return !((*this) == other);
92+
}
93+
94+
Literal run(Function* func, Module& wasm) {
95+
ShellExternalInterface interface;
96+
try {
97+
ModuleInstance instance(wasm, &interface);
98+
LiteralList arguments;
99+
// init hang support, if present
100+
if (wasm.getFunctionOrNull("hangLimitInitializer")) {
101+
instance.callFunction("hangLimitInitializer", arguments);
102+
}
103+
// call the method
104+
for (WasmType param : func->params) {
105+
// zeros in arguments TODO: more?
106+
arguments.push_back(Literal(param));
107+
}
108+
return instance.callFunction(func->name, arguments);
109+
} catch (const TrapException&) {
110+
// may throw in instance creation (init of offsets) or call itself
111+
return Literal();
112+
}
113+
}
114+
};
115+
116+
} // namespace wasm
117+

src/tools/js-wrapper.h

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2017 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
//
18+
// Emit a JavaScript wrapper to run a wasm module with some test
19+
// values, useful for fuzzing.
20+
//
21+
22+
namespace wasm {
23+
24+
static std::string generateJSWrapper(Module& wasm) {
25+
std::string ret;
26+
ret += "if (typeof console === 'undefined') {\n"
27+
" console = { log: print };\n"
28+
"}\n"
29+
"var binary;\n"
30+
"if (typeof process === 'object' && typeof require === 'function' /* node.js detection */) {\n"
31+
" var args = process.argv.slice(2);\n"
32+
" binary = require('fs').readFileSync(args[0]);\n"
33+
" if (!binary.buffer) binary = new Uint8Array(binary);\n"
34+
"} else {\n"
35+
" var args;\n"
36+
" if (typeof scriptArgs != 'undefined') {\n"
37+
" args = scriptArgs;\n"
38+
" } else if (typeof arguments != 'undefined') {\n"
39+
" args = arguments;\n"
40+
" }\n"
41+
" if (typeof readbuffer === 'function') {\n"
42+
" binary = new Uint8Array(readbuffer(args[0]));\n"
43+
" } else {\n"
44+
" binary = read(args[0], 'binary');\n"
45+
" }\n"
46+
"}\n"
47+
"var instance = new WebAssembly.Instance(new WebAssembly.Module(binary), {});\n";
48+
for (auto& exp : wasm.exports) {
49+
auto* func = wasm.getFunctionOrNull(exp->value);
50+
if (!func) continue; // something exported other than a function
51+
auto bad = false; // check for things we can't support
52+
for (WasmType param : func->params) {
53+
if (param == i64) bad = true;
54+
}
55+
if (func->result == i64) bad = true;
56+
if (bad) continue;
57+
ret += "if (instance.exports.hangLimitInitializer) instance.exports.hangLimitInitializer();\n";
58+
ret += "try {\n";
59+
ret += std::string(" console.log('calling: ") + exp->name.str + "');\n";
60+
if (func->result != none) {
61+
ret += " console.log(' result: ' + ";
62+
}
63+
ret += std::string("instance.exports.") + exp->name.str + "(";
64+
bool first = true;
65+
for (WasmType param : func->params) {
66+
WASM_UNUSED(param);
67+
// zeros in arguments TODO more?
68+
if (first) {
69+
first = false;
70+
} else {
71+
ret += ", ";
72+
}
73+
ret += "0";
74+
}
75+
ret += ")";
76+
if (func->result != none) {
77+
ret += ")"; // for console.log
78+
}
79+
ret += ";\n";
80+
ret += "} catch (e) {\n";
81+
ret += " console.log(' exception: ' + e);\n";
82+
ret += "}\n";
83+
}
84+
ret += "console.log('done.')\n";
85+
return ret;
86+
}
87+
88+
} // namespace wasm
89+

src/tools/spec-wrapper.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2017 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
//
18+
// Emit a wasm spec interpreter wrapper to run a wasm module with some test
19+
// values, useful for fuzzing.
20+
//
21+
22+
namespace wasm {
23+
24+
static std::string generateSpecWrapper(Module& wasm) {
25+
std::string ret;
26+
for (auto& exp : wasm.exports) {
27+
auto* func = wasm.getFunctionOrNull(exp->value);
28+
if (!func) continue; // something exported other than a function
29+
ret += std::string("(invoke \"hangLimitInitializer\") (invoke \"") + exp->name.str + "\" ";
30+
for (WasmType param : func->params) {
31+
// zeros in arguments TODO more?
32+
switch (param) {
33+
case i32: ret += "(i32.const 0)"; break;
34+
case i64: ret += "(i64.const 0)"; break;
35+
case f32: ret += "(f32.const 0)"; break;
36+
case f64: ret += "(f64.const 0)"; break;
37+
default: WASM_UNREACHABLE();
38+
}
39+
ret += " ";
40+
}
41+
ret += ") ";
42+
}
43+
return ret;
44+
}
45+
46+
} // namespace wasm
47+

0 commit comments

Comments
 (0)