diff options
author | Alon Zakai <azakai@google.com> | 2019-07-01 19:23:52 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-07-01 19:23:52 -0700 |
commit | f154364c01603b0b8cfd0055951e8283f20fabd7 (patch) | |
tree | 85e5add66cefc3ce338ef52bb4733b7091cf4dd4 | |
parent | c3e45d4e5272486fae1ffb353c56e4d117fe4a21 (diff) | |
download | binaryen-f154364c01603b0b8cfd0055951e8283f20fabd7.tar.gz binaryen-f154364c01603b0b8cfd0055951e8283f20fabd7.tar.bz2 binaryen-f154364c01603b0b8cfd0055951e8283f20fabd7.zip |
Bysyncify: Assertion improvements (#2193)
Add assertions on stack overflow in all 4 Bysyncify API calls (previously only 2 did it).
Also add a check that those assertions are hit.
-rw-r--r-- | src/passes/Bysyncify.cpp | 73 | ||||
-rw-r--r-- | test/passes/bysyncify.txt | 138 | ||||
-rw-r--r-- | test/passes/bysyncify_optimize-level=1.txt | 46 | ||||
-rw-r--r-- | test/passes/bysyncify_pass-arg=bysyncify-ignore-imports.txt | 46 | ||||
-rw-r--r-- | test/passes/bysyncify_pass-arg=bysyncify-ignore-indirect.txt | 46 | ||||
-rw-r--r-- | test/passes/bysyncify_pass-arg=bysyncify-imports@env.import,env.import2.txt | 92 | ||||
-rw-r--r-- | test/unit/input/bysyncify-stackOverflow.wast | 22 | ||||
-rw-r--r-- | test/unit/input/bysyncify.js | 43 | ||||
-rw-r--r-- | test/unit/test_bysyncify.py | 1 |
9 files changed, 375 insertions, 132 deletions
diff --git a/src/passes/Bysyncify.cpp b/src/passes/Bysyncify.cpp index cb85f0b0a..0897375fe 100644 --- a/src/passes/Bysyncify.cpp +++ b/src/passes/Bysyncify.cpp @@ -174,6 +174,17 @@ // we are recreating the call stack. At that point you should call // bysyncify_stop_rewind and then execution can resume normally. // +// Note that the bysyncify_* API calls will verify that the data structure +// is valid, checking if the current location is past the end. If so, they +// will throw a wasm unreachable. No check is done during unwinding or +// rewinding, to keep things fast - in principle, from when unwinding begins +// and until it ends there should be no memory accesses aside from the +// bysyncify code itself (and likewise when rewinding), so there should be +// no chance of memory corruption causing odd errors. However, the low +// overhead of this approach does cause an error only to be shown when +// unwinding/rewinding finishes, and not at the specific spot where the +// stack end was exceeded. +// // By default this pass is very carefuly: it assumes that any import and // any indirect call may start an unwind/rewind operation. If you know more // specific information you can inform bysyncify about that, which can reduce @@ -1056,38 +1067,36 @@ private: void addFunctions(Module* module) { Builder builder(*module); - auto makeFunction = [&](Name name, - bool setData, - State state, - Expression* extra) { + auto makeFunction = [&](Name name, bool setData, State state) { std::vector<Type> params; if (setData) { params.push_back(i32); } auto* body = builder.makeBlock(); - if (setData) { - // Verify the data is valid. - auto* stackPos = builder.makeLoad(4, - false, - int32_t(DataOffset::BStackPos), - 4, - builder.makeLocalGet(0, i32), - i32); - auto* stackEnd = builder.makeLoad(4, - false, - int32_t(DataOffset::BStackEnd), - 4, - builder.makeLocalGet(0, i32), - i32); - body->list.push_back( - builder.makeIf(builder.makeBinary(GtUInt32, stackPos, stackEnd), - builder.makeUnreachable())); - } body->list.push_back(builder.makeGlobalSet( BYSYNCIFY_STATE, builder.makeConst(Literal(int32_t(state))))); - if (extra) { - body->list.push_back(extra); + if (setData) { + body->list.push_back( + builder.makeGlobalSet(BYSYNCIFY_DATA, builder.makeLocalGet(0, i32))); } + // Verify the data is valid. + auto* stackPos = + builder.makeLoad(4, + false, + int32_t(DataOffset::BStackPos), + 4, + builder.makeGlobalGet(BYSYNCIFY_DATA, i32), + i32); + auto* stackEnd = + builder.makeLoad(4, + false, + int32_t(DataOffset::BStackEnd), + 4, + builder.makeGlobalGet(BYSYNCIFY_DATA, i32), + i32); + body->list.push_back( + builder.makeIf(builder.makeBinary(GtUInt32, stackPos, stackEnd), + builder.makeUnreachable())); body->finalize(); auto* func = builder.makeFunction( name, std::move(params), none, std::vector<Type>{}, body); @@ -1095,18 +1104,10 @@ private: module->addExport(builder.makeExport(name, name, ExternalKind::Function)); }; - makeFunction( - BYSYNCIFY_START_UNWIND, - true, - State::Unwinding, - builder.makeGlobalSet(BYSYNCIFY_DATA, builder.makeLocalGet(0, i32))); - makeFunction(BYSYNCIFY_STOP_UNWIND, false, State::Normal, nullptr); - makeFunction( - BYSYNCIFY_START_REWIND, - true, - State::Rewinding, - builder.makeGlobalSet(BYSYNCIFY_DATA, builder.makeLocalGet(0, i32))); - makeFunction(BYSYNCIFY_STOP_REWIND, false, State::Normal, nullptr); + makeFunction(BYSYNCIFY_START_UNWIND, true, State::Unwinding); + makeFunction(BYSYNCIFY_STOP_UNWIND, false, State::Normal); + makeFunction(BYSYNCIFY_START_REWIND, true, State::Rewinding); + makeFunction(BYSYNCIFY_STOP_REWIND, false, State::Normal); } }; diff --git a/test/passes/bysyncify.txt b/test/passes/bysyncify.txt index fbbc09d45..a9a780972 100644 --- a/test/passes/bysyncify.txt +++ b/test/passes/bysyncify.txt @@ -297,52 +297,74 @@ (nop) ) (func $bysyncify_start_unwind (; 6 ;) (param $0 i32) + (global.set $__bysyncify_state + (i32.const 1) + ) + (global.set $__bysyncify_data + (local.get $0) + ) (if (i32.gt_u (i32.load - (local.get $0) + (global.get $__bysyncify_data) ) (i32.load offset=4 - (local.get $0) + (global.get $__bysyncify_data) ) ) (unreachable) ) - (global.set $__bysyncify_state - (i32.const 1) - ) - (global.set $__bysyncify_data - (local.get $0) - ) ) (func $bysyncify_stop_unwind (; 7 ;) (global.set $__bysyncify_state (i32.const 0) ) - ) - (func $bysyncify_start_rewind (; 8 ;) (param $0 i32) (if (i32.gt_u (i32.load - (local.get $0) + (global.get $__bysyncify_data) ) (i32.load offset=4 - (local.get $0) + (global.get $__bysyncify_data) ) ) (unreachable) ) + ) + (func $bysyncify_start_rewind (; 8 ;) (param $0 i32) (global.set $__bysyncify_state (i32.const 2) ) (global.set $__bysyncify_data (local.get $0) ) + (if + (i32.gt_u + (i32.load + (global.get $__bysyncify_data) + ) + (i32.load offset=4 + (global.get $__bysyncify_data) + ) + ) + (unreachable) + ) ) (func $bysyncify_stop_rewind (; 9 ;) (global.set $__bysyncify_state (i32.const 0) ) + (if + (i32.gt_u + (i32.load + (global.get $__bysyncify_data) + ) + (i32.load offset=4 + (global.get $__bysyncify_data) + ) + ) + (unreachable) + ) ) ) (module @@ -2808,52 +2830,74 @@ (nop) ) (func $bysyncify_start_unwind (; 19 ;) (param $0 i32) + (global.set $__bysyncify_state + (i32.const 1) + ) + (global.set $__bysyncify_data + (local.get $0) + ) (if (i32.gt_u (i32.load - (local.get $0) + (global.get $__bysyncify_data) ) (i32.load offset=4 - (local.get $0) + (global.get $__bysyncify_data) ) ) (unreachable) ) - (global.set $__bysyncify_state - (i32.const 1) - ) - (global.set $__bysyncify_data - (local.get $0) - ) ) (func $bysyncify_stop_unwind (; 20 ;) (global.set $__bysyncify_state (i32.const 0) ) - ) - (func $bysyncify_start_rewind (; 21 ;) (param $0 i32) (if (i32.gt_u (i32.load - (local.get $0) + (global.get $__bysyncify_data) ) (i32.load offset=4 - (local.get $0) + (global.get $__bysyncify_data) ) ) (unreachable) ) + ) + (func $bysyncify_start_rewind (; 21 ;) (param $0 i32) (global.set $__bysyncify_state (i32.const 2) ) (global.set $__bysyncify_data (local.get $0) ) + (if + (i32.gt_u + (i32.load + (global.get $__bysyncify_data) + ) + (i32.load offset=4 + (global.get $__bysyncify_data) + ) + ) + (unreachable) + ) ) (func $bysyncify_stop_rewind (; 22 ;) (global.set $__bysyncify_state (i32.const 0) ) + (if + (i32.gt_u + (i32.load + (global.get $__bysyncify_data) + ) + (i32.load offset=4 + (global.get $__bysyncify_data) + ) + ) + (unreachable) + ) ) ) (module @@ -2865,51 +2909,73 @@ (export "bysyncify_start_rewind" (func $bysyncify_start_rewind)) (export "bysyncify_stop_rewind" (func $bysyncify_stop_rewind)) (func $bysyncify_start_unwind (; 0 ;) (param $0 i32) + (global.set $__bysyncify_state + (i32.const 1) + ) + (global.set $__bysyncify_data + (local.get $0) + ) (if (i32.gt_u (i32.load - (local.get $0) + (global.get $__bysyncify_data) ) (i32.load offset=4 - (local.get $0) + (global.get $__bysyncify_data) ) ) (unreachable) ) - (global.set $__bysyncify_state - (i32.const 1) - ) - (global.set $__bysyncify_data - (local.get $0) - ) ) (func $bysyncify_stop_unwind (; 1 ;) (global.set $__bysyncify_state (i32.const 0) ) - ) - (func $bysyncify_start_rewind (; 2 ;) (param $0 i32) (if (i32.gt_u (i32.load - (local.get $0) + (global.get $__bysyncify_data) ) (i32.load offset=4 - (local.get $0) + (global.get $__bysyncify_data) ) ) (unreachable) ) + ) + (func $bysyncify_start_rewind (; 2 ;) (param $0 i32) (global.set $__bysyncify_state (i32.const 2) ) (global.set $__bysyncify_data (local.get $0) ) + (if + (i32.gt_u + (i32.load + (global.get $__bysyncify_data) + ) + (i32.load offset=4 + (global.get $__bysyncify_data) + ) + ) + (unreachable) + ) ) (func $bysyncify_stop_rewind (; 3 ;) (global.set $__bysyncify_state (i32.const 0) ) + (if + (i32.gt_u + (i32.load + (global.get $__bysyncify_data) + ) + (i32.load offset=4 + (global.get $__bysyncify_data) + ) + ) + (unreachable) + ) ) ) diff --git a/test/passes/bysyncify_optimize-level=1.txt b/test/passes/bysyncify_optimize-level=1.txt index b09368ec4..7b1093964 100644 --- a/test/passes/bysyncify_optimize-level=1.txt +++ b/test/passes/bysyncify_optimize-level=1.txt @@ -1564,51 +1564,73 @@ ) ) (func $bysyncify_start_unwind (; 19 ;) (param $0 i32) + (global.set $__bysyncify_state + (i32.const 1) + ) + (global.set $__bysyncify_data + (local.get $0) + ) (if (i32.gt_u (i32.load - (local.get $0) + (global.get $__bysyncify_data) ) (i32.load offset=4 - (local.get $0) + (global.get $__bysyncify_data) ) ) (unreachable) ) - (global.set $__bysyncify_state - (i32.const 1) - ) - (global.set $__bysyncify_data - (local.get $0) - ) ) (func $bysyncify_stop_unwind (; 20 ;) (global.set $__bysyncify_state (i32.const 0) ) - ) - (func $bysyncify_start_rewind (; 21 ;) (param $0 i32) (if (i32.gt_u (i32.load - (local.get $0) + (global.get $__bysyncify_data) ) (i32.load offset=4 - (local.get $0) + (global.get $__bysyncify_data) ) ) (unreachable) ) + ) + (func $bysyncify_start_rewind (; 21 ;) (param $0 i32) (global.set $__bysyncify_state (i32.const 2) ) (global.set $__bysyncify_data (local.get $0) ) + (if + (i32.gt_u + (i32.load + (global.get $__bysyncify_data) + ) + (i32.load offset=4 + (global.get $__bysyncify_data) + ) + ) + (unreachable) + ) ) (func $bysyncify_stop_rewind (; 22 ;) (global.set $__bysyncify_state (i32.const 0) ) + (if + (i32.gt_u + (i32.load + (global.get $__bysyncify_data) + ) + (i32.load offset=4 + (global.get $__bysyncify_data) + ) + ) + (unreachable) + ) ) ) diff --git a/test/passes/bysyncify_pass-arg=bysyncify-ignore-imports.txt b/test/passes/bysyncify_pass-arg=bysyncify-ignore-imports.txt index 515fa365a..96133d648 100644 --- a/test/passes/bysyncify_pass-arg=bysyncify-ignore-imports.txt +++ b/test/passes/bysyncify_pass-arg=bysyncify-ignore-imports.txt @@ -211,51 +211,73 @@ ) ) (func $bysyncify_start_unwind (; 7 ;) (param $0 i32) + (global.set $__bysyncify_state + (i32.const 1) + ) + (global.set $__bysyncify_data + (local.get $0) + ) (if (i32.gt_u (i32.load - (local.get $0) + (global.get $__bysyncify_data) ) (i32.load offset=4 - (local.get $0) + (global.get $__bysyncify_data) ) ) (unreachable) ) - (global.set $__bysyncify_state - (i32.const 1) - ) - (global.set $__bysyncify_data - (local.get $0) - ) ) (func $bysyncify_stop_unwind (; 8 ;) (global.set $__bysyncify_state (i32.const 0) ) - ) - (func $bysyncify_start_rewind (; 9 ;) (param $0 i32) (if (i32.gt_u (i32.load - (local.get $0) + (global.get $__bysyncify_data) ) (i32.load offset=4 - (local.get $0) + (global.get $__bysyncify_data) ) ) (unreachable) ) + ) + (func $bysyncify_start_rewind (; 9 ;) (param $0 i32) (global.set $__bysyncify_state (i32.const 2) ) (global.set $__bysyncify_data (local.get $0) ) + (if + (i32.gt_u + (i32.load + (global.get $__bysyncify_data) + ) + (i32.load offset=4 + (global.get $__bysyncify_data) + ) + ) + (unreachable) + ) ) (func $bysyncify_stop_rewind (; 10 ;) (global.set $__bysyncify_state (i32.const 0) ) + (if + (i32.gt_u + (i32.load + (global.get $__bysyncify_data) + ) + (i32.load offset=4 + (global.get $__bysyncify_data) + ) + ) + (unreachable) + ) ) ) diff --git a/test/passes/bysyncify_pass-arg=bysyncify-ignore-indirect.txt b/test/passes/bysyncify_pass-arg=bysyncify-ignore-indirect.txt index 64e516cf8..846e05035 100644 --- a/test/passes/bysyncify_pass-arg=bysyncify-ignore-indirect.txt +++ b/test/passes/bysyncify_pass-arg=bysyncify-ignore-indirect.txt @@ -517,51 +517,73 @@ (nop) ) (func $bysyncify_start_unwind (; 7 ;) (param $0 i32) + (global.set $__bysyncify_state + (i32.const 1) + ) + (global.set $__bysyncify_data + (local.get $0) + ) (if (i32.gt_u (i32.load - (local.get $0) + (global.get $__bysyncify_data) ) (i32.load offset=4 - (local.get $0) + (global.get $__bysyncify_data) ) ) (unreachable) ) - (global.set $__bysyncify_state - (i32.const 1) - ) - (global.set $__bysyncify_data - (local.get $0) - ) ) (func $bysyncify_stop_unwind (; 8 ;) (global.set $__bysyncify_state (i32.const 0) ) - ) - (func $bysyncify_start_rewind (; 9 ;) (param $0 i32) (if (i32.gt_u (i32.load - (local.get $0) + (global.get $__bysyncify_data) ) (i32.load offset=4 - (local.get $0) + (global.get $__bysyncify_data) ) ) (unreachable) ) + ) + (func $bysyncify_start_rewind (; 9 ;) (param $0 i32) (global.set $__bysyncify_state (i32.const 2) ) (global.set $__bysyncify_data (local.get $0) ) + (if + (i32.gt_u + (i32.load + (global.get $__bysyncify_data) + ) + (i32.load offset=4 + (global.get $__bysyncify_data) + ) + ) + (unreachable) + ) ) (func $bysyncify_stop_rewind (; 10 ;) (global.set $__bysyncify_state (i32.const 0) ) + (if + (i32.gt_u + (i32.load + (global.get $__bysyncify_data) + ) + (i32.load offset=4 + (global.get $__bysyncify_data) + ) + ) + (unreachable) + ) ) ) diff --git a/test/passes/bysyncify_pass-arg=bysyncify-imports@env.import,env.import2.txt b/test/passes/bysyncify_pass-arg=bysyncify-imports@env.import,env.import2.txt index b22e7d82a..02d353a7a 100644 --- a/test/passes/bysyncify_pass-arg=bysyncify-imports@env.import,env.import2.txt +++ b/test/passes/bysyncify_pass-arg=bysyncify-imports@env.import,env.import2.txt @@ -295,52 +295,74 @@ (nop) ) (func $bysyncify_start_unwind (; 6 ;) (param $0 i32) + (global.set $__bysyncify_state + (i32.const 1) + ) + (global.set $__bysyncify_data + (local.get $0) + ) (if (i32.gt_u (i32.load - (local.get $0) + (global.get $__bysyncify_data) ) (i32.load offset=4 - (local.get $0) + (global.get $__bysyncify_data) ) ) (unreachable) ) - (global.set $__bysyncify_state - (i32.const 1) - ) - (global.set $__bysyncify_data - (local.get $0) - ) ) (func $bysyncify_stop_unwind (; 7 ;) (global.set $__bysyncify_state (i32.const 0) ) - ) - (func $bysyncify_start_rewind (; 8 ;) (param $0 i32) (if (i32.gt_u (i32.load - (local.get $0) + (global.get $__bysyncify_data) ) (i32.load offset=4 - (local.get $0) + (global.get $__bysyncify_data) ) ) (unreachable) ) + ) + (func $bysyncify_start_rewind (; 8 ;) (param $0 i32) (global.set $__bysyncify_state (i32.const 2) ) (global.set $__bysyncify_data (local.get $0) ) + (if + (i32.gt_u + (i32.load + (global.get $__bysyncify_data) + ) + (i32.load offset=4 + (global.get $__bysyncify_data) + ) + ) + (unreachable) + ) ) (func $bysyncify_stop_rewind (; 9 ;) (global.set $__bysyncify_state (i32.const 0) ) + (if + (i32.gt_u + (i32.load + (global.get $__bysyncify_data) + ) + (i32.load offset=4 + (global.get $__bysyncify_data) + ) + ) + (unreachable) + ) ) ) (module @@ -1961,51 +1983,73 @@ (nop) ) (func $bysyncify_start_unwind (; 19 ;) (param $0 i32) + (global.set $__bysyncify_state + (i32.const 1) + ) + (global.set $__bysyncify_data + (local.get $0) + ) (if (i32.gt_u (i32.load - (local.get $0) + (global.get $__bysyncify_data) ) (i32.load offset=4 - (local.get $0) + (global.get $__bysyncify_data) ) ) (unreachable) ) - (global.set $__bysyncify_state - (i32.const 1) - ) - (global.set $__bysyncify_data - (local.get $0) - ) ) (func $bysyncify_stop_unwind (; 20 ;) (global.set $__bysyncify_state (i32.const 0) ) - ) - (func $bysyncify_start_rewind (; 21 ;) (param $0 i32) (if (i32.gt_u (i32.load - (local.get $0) + (global.get $__bysyncify_data) ) (i32.load offset=4 - (local.get $0) + (global.get $__bysyncify_data) ) ) (unreachable) ) + ) + (func $bysyncify_start_rewind (; 21 ;) (param $0 i32) (global.set $__bysyncify_state (i32.const 2) ) (global.set $__bysyncify_data (local.get $0) ) + (if + (i32.gt_u + (i32.load + (global.get $__bysyncify_data) + ) + (i32.load offset=4 + (global.get $__bysyncify_data) + ) + ) + (unreachable) + ) ) (func $bysyncify_stop_rewind (; 22 ;) (global.set $__bysyncify_state (i32.const 0) ) + (if + (i32.gt_u + (i32.load + (global.get $__bysyncify_data) + ) + (i32.load offset=4 + (global.get $__bysyncify_data) + ) + ) + (unreachable) + ) ) ) diff --git a/test/unit/input/bysyncify-stackOverflow.wast b/test/unit/input/bysyncify-stackOverflow.wast new file mode 100644 index 000000000..a36a06b40 --- /dev/null +++ b/test/unit/input/bysyncify-stackOverflow.wast @@ -0,0 +1,22 @@ +(module + (memory 1 2) + (import "env" "sleep" (func $sleep)) + (export "memory" (memory 0)) + (func "many_locals" (param $x i32) (result i32) + (local $y i32) + (local $z i32) + (local.set $y + (i32.add (local.get $x) (i32.const 10)) + ) + (local.set $z + (i32.add (local.get $y) (i32.const 20)) + ) + (call $sleep) + (select + (local.get $y) + (local.get $z) + (local.get $x) + ) + ) +) + diff --git a/test/unit/input/bysyncify.js b/test/unit/input/bysyncify.js index 2e1bb4895..be6d32903 100644 --- a/test/unit/input/bysyncify.js +++ b/test/unit/input/bysyncify.js @@ -252,10 +252,53 @@ function coroutineTests() { ]), 'check yielded values') } +function stackOverflowAssertTests() { + console.log('\nstack overflow assertion tests\n\n'); + + // Get and compile the wasm. + + var binary = fs.readFileSync('c.wasm'); + + var module = new WebAssembly.Module(binary); + + var DATA_ADDR = 4; + + var instance = new WebAssembly.Instance(module, { + env: { + sleep: function() { + console.log('sleep...'); + exports.bysyncify_start_unwind(DATA_ADDR); + view[DATA_ADDR >> 2] = DATA_ADDR + 8; + // The end of the stack will be reached as the stack is tiny. + view[DATA_ADDR + 4 >> 2] = view[DATA_ADDR >> 2] + 1; + } + } + }); + + var exports = instance.exports; + var view = new Int32Array(exports.memory.buffer); + exports.many_locals(); + assert(view[DATA_ADDR >> 2] > view[DATA_ADDR + 4 >> 2], 'should have wrote past the end of the stack'); + // All API calls should now fail, since we wrote past the end of the + // stack + var fails = 0; + ['bysyncify_stop_unwind', 'bysyncify_start_rewind', 'bysyncify_stop_rewind', 'bysyncify_start_unwind'].forEach(function(name) { + try { + exports[name](DATA_ADDR); + console.log('no fail on', name); + } catch (e) { + console.log('expected fail on', name); + fails++; + } + }); + assert(fails == 4, 'all 4 should have failed'); +} + // Main sleepTests(); coroutineTests(); +stackOverflowAssertTests(); console.log('\ntests completed successfully'); diff --git a/test/unit/test_bysyncify.py b/test/unit/test_bysyncify.py index 8d5a780e8..407f3f7f9 100644 --- a/test/unit/test_bysyncify.py +++ b/test/unit/test_bysyncify.py @@ -10,6 +10,7 @@ class BysyncifyTest(BinaryenTestCase): print(args) run_process(WASM_OPT + args + [self.input_path('bysyncify-sleep.wast'), '--bysyncify', '-o', 'a.wasm']) run_process(WASM_OPT + args + [self.input_path('bysyncify-coroutine.wast'), '--bysyncify', '-o', 'b.wasm']) + run_process(WASM_OPT + args + [self.input_path('bysyncify-stackOverflow.wast'), '--bysyncify', '-o', 'c.wasm']) print(' file size: %d' % os.path.getsize('a.wasm')) run_process([NODEJS, self.input_path('bysyncify.js')]) |