Skip to content

Commit 4e2d953

Browse files
authored
Merge pull request #57 from WebAssembly/trap-lockdown
Tweak may_enter rules to ensure lockdown-on-trap
2 parents 41a07b0 + 08a068f commit 4e2d953

File tree

2 files changed

+17
-31
lines changed

2 files changed

+17
-31
lines changed

design/mvp/CanonicalABI.md

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,7 @@ Given the above closure arguments, `canon_lift` is defined:
11341134
```python
11351135
def canon_lift(callee_opts, callee_instance, callee, functype, args):
11361136
trap_if(not callee_instance.may_enter)
1137+
callee_instance.may_enter = False
11371138

11381139
assert(callee_instance.may_leave)
11391140
callee_instance.may_leave = False
@@ -1145,12 +1146,11 @@ def canon_lift(callee_opts, callee_instance, callee, functype, args):
11451146
except CoreWebAssemblyException:
11461147
trap()
11471148

1148-
callee_instance.may_enter = False
11491149
[result] = lift(callee_opts, MAX_FLAT_RESULTS, ValueIter(flat_results), [functype.result])
11501150
def post_return():
1151-
callee_instance.may_enter = True
11521151
if callee_opts.post_return is not None:
11531152
callee_opts.post_return(flat_results)
1153+
callee_instance.may_enter = True
11541154

11551155
return (result, post_return)
11561156
```
@@ -1161,6 +1161,14 @@ boundaries. Thus, if a component wishes to signal an error, it must use some
11611161
sort of explicit type such as `expected` (whose `error` case particular
11621162
language bindings may choose to map to and from exceptions).
11631163

1164+
The clearing of `may_enter` for the entire duration of `canon_lift` and the
1165+
fact that `canon_lift` brackets all calls into a component ensure that
1166+
components cannot be reentered, which is a [component invariant]. Furthermore,
1167+
because `may_enter` is not cleared on the exceptional exit path taken by
1168+
`trap()`, if there is a trap during Core WebAssembly execution or
1169+
lifting/lowering, the component is left permanently un-enterable, ensuring the
1170+
lockdown-after-trap [component invariant].
1171+
11641172
The contract assumed by `canon_lift` (and ensured by `canon_lower` below) is
11651173
that the caller of `canon_lift` *must* call `post_return` right after lowering
11661174
`result`. This ordering ensures that the engine can reliably copy directly from
@@ -1170,22 +1178,12 @@ the callee's linear memory (read by `lift`) into the caller's linear memory
11701178
freed and so the engine would need to eagerly make an intermediate copy in
11711179
`lift`.
11721180

1173-
Even assuming this `post_return` contract, if the callee could be re-entered
1174-
by the caller in the middle of the caller's `lower` (e.g., via `realloc`), then
1175-
either the engine has to make an eager intermediate copy in `lift` *or* the
1176-
Canonical ABI would have to specify a precise interleaving of side effects
1177-
which is more complicated and would inhibit some optimizations. Instead, the
1178-
`may_enter` guard set before `lift` and cleared in `post_return` prevents this
1179-
re-entrance. Thus, it is the combination of `post_return` and the re-entrance
1180-
guard that ensures `lift` does not need to make an eager copy.
1181-
11821181
The `may_leave` guard wrapping the lowering of parameters conservatively
1183-
ensures that `realloc` calls during lowering do not accidentally call imports
1184-
that accidentally re-enter the instance that lifted the same parameters.
1185-
While the `may_enter` guards of *those* component instances would also prevent
1186-
this re-entrance, it would be an error that only manifested in certain
1187-
component linking configurations, hence the eager error helps ensure
1188-
compositionality.
1182+
ensures that `realloc` calls during lowering do not call imports that
1183+
indirectly re-enter the instance that lifted the same parameters. While the
1184+
`may_enter` guards of *those* component instances would also prevent this
1185+
re-entrance, it would be an error that only manifested in certain component
1186+
linking configurations, hence the eager error helps ensure compositionality.
11891187

11901188

11911189
### `lower`
@@ -1210,9 +1208,6 @@ and, when called from Core WebAssembly code, calls `canon_lower`, which is defin
12101208
def canon_lower(caller_opts, caller_instance, callee, functype, flat_args):
12111209
trap_if(not caller_instance.may_leave)
12121210

1213-
assert(caller_instance.may_enter)
1214-
caller_instance.may_enter = False
1215-
12161211
flat_args = ValueIter(flat_args)
12171212
args = lift(caller_opts, MAX_FLAT_PARAMS, flat_args, functype.params)
12181213

@@ -1224,15 +1219,10 @@ def canon_lower(caller_opts, caller_instance, callee, functype, flat_args):
12241219

12251220
post_return()
12261221

1227-
caller_instance.may_enter = True
12281222
return flat_results
12291223
```
12301224
The definitions of `canon_lift` and `canon_lower` are mostly symmetric (swapping
12311225
lifting and lowering), with a few exceptions:
1232-
* The calling instance cannot be re-entered over the course of the entire call,
1233-
not just while lifting the parameters. This ensures not just the needs of the
1234-
Canonical ABI, but the general non-re-entrance expectations outlined in the
1235-
[component invariants].
12361226
* The caller does not need a `post-return` function since the Core WebAssembly
12371227
caller simply regains control when `canon_lower` returns, allowing it to free
12381228
(or not) any memory passed as `flat_args`.

design/mvp/canonical-abi/definitions.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,7 @@ class Instance:
872872

873873
def canon_lift(callee_opts, callee_instance, callee, functype, args):
874874
trap_if(not callee_instance.may_enter)
875+
callee_instance.may_enter = False
875876

876877
assert(callee_instance.may_leave)
877878
callee_instance.may_leave = False
@@ -883,12 +884,11 @@ def canon_lift(callee_opts, callee_instance, callee, functype, args):
883884
except CoreWebAssemblyException:
884885
trap()
885886

886-
callee_instance.may_enter = False
887887
[result] = lift(callee_opts, MAX_FLAT_RESULTS, ValueIter(flat_results), [functype.result])
888888
def post_return():
889-
callee_instance.may_enter = True
890889
if callee_opts.post_return is not None:
891890
callee_opts.post_return(flat_results)
891+
callee_instance.may_enter = True
892892

893893
return (result, post_return)
894894

@@ -897,9 +897,6 @@ def post_return():
897897
def canon_lower(caller_opts, caller_instance, callee, functype, flat_args):
898898
trap_if(not caller_instance.may_leave)
899899

900-
assert(caller_instance.may_enter)
901-
caller_instance.may_enter = False
902-
903900
flat_args = ValueIter(flat_args)
904901
args = lift(caller_opts, MAX_FLAT_PARAMS, flat_args, functype.params)
905902

@@ -911,5 +908,4 @@ def canon_lower(caller_opts, caller_instance, callee, functype, flat_args):
911908

912909
post_return()
913910

914-
caller_instance.may_enter = True
915911
return flat_results

0 commit comments

Comments
 (0)