Skip to content

Commit 7fe07c0

Browse files
authored
Fuzzer: Interpose on existing exports (#7321)
If the initial wasm we are fuzzing with had an export, then some of the time add more code in that export, interposing before the usual code: (func $foo (export "bar") (result i32) (..code..) ) => (func $foo (export "bar") (result i32) (call $something) ;; new code (..code..) ) We interpose by inserting a call to another function. We already got something like this from modifying functions from initial content, but adding such calls gives a much better chance to execute an interesting amount of new code (calling one of the new functions we generated ourselves, which could contain anything).
1 parent 1a8fddc commit 7fe07c0

File tree

1 file changed

+41
-0
lines changed

1 file changed

+41
-0
lines changed

src/tools/fuzzing/fuzzing.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,6 +1336,8 @@ void TranslateToFuzzReader::processFunctions() {
13361336
}
13371337
}
13381338

1339+
auto numInitialExports = wasm.exports.size();
1340+
13391341
// Add invocations, which can help execute the code here even if the function
13401342
// was not exported (or was exported but with a signature that traps
13411343
// immediately, like receiving a non-nullable ref, that the fuzzer can't
@@ -1380,6 +1382,45 @@ void TranslateToFuzzReader::processFunctions() {
13801382
}
13811383
}
13821384

1385+
// Interpose on initial exports. When initial content contains exports, it can
1386+
// be useful to add new code that executes in them, rather than just adding
1387+
// new exports later. To some extent modifying the initially-exported function
1388+
// gives us that, but typically these are small changes, not calls to entirely
1389+
// new code (and this is especially important when preserveImportsAndExports,
1390+
// as in that mode we do not add new exports, so this interposing is our main
1391+
// chance to run new code using the existing exports).
1392+
//
1393+
// Interpose with a call before the old code. We do a call here so that we end
1394+
// up running a useful amount of new code (rather than just make(none) which
1395+
// would only emit something local in the current function, and which depends
1396+
// on its contents).
1397+
// TODO: We could also interpose after, either in functions without results,
1398+
// or by saving the results to a temp local as we call.
1399+
//
1400+
// Specifically, we will call functions, for simplicity, with no params or
1401+
// results. Such functions exist in abundance in general, because the
1402+
// invocations we add look exactly that way. First, find all such functions,
1403+
// and then find places to interpose calls to them.
1404+
std::vector<Name> noParamsOrResultFuncs;
1405+
for (auto& func : wasm.functions) {
1406+
if (func->getParams() == Type::none && func->getResults() == Type::none) {
1407+
noParamsOrResultFuncs.push_back(func->name);
1408+
}
1409+
}
1410+
if (!noParamsOrResultFuncs.empty()) {
1411+
for (Index i = 0; i < numInitialExports; i++) {
1412+
auto& exp = wasm.exports[i];
1413+
if (exp->kind == ExternalKind::Function && upTo(RESOLUTION) < chance) {
1414+
auto* func = wasm.getFunction(exp->value);
1415+
if (!func->imported()) {
1416+
auto* call =
1417+
builder.makeCall(pick(noParamsOrResultFuncs), {}, Type::none);
1418+
func->body = builder.makeSequence(call, func->body);
1419+
}
1420+
}
1421+
}
1422+
}
1423+
13831424
// At the very end, add hang limit checks (so no modding can override them).
13841425
if (fuzzParams->HANG_LIMIT > 0) {
13851426
for (auto& func : wasm.functions) {

0 commit comments

Comments
 (0)