summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2022-04-06 20:22:16 +0200
committerJo-Philipp Wich <jo@mein.io>2022-04-07 15:13:02 +0200
commit2b59140b2c00987b9d8c2c908d2d44c67786e71e (patch)
treeab8093e9997a4c2b10772e3e8f600c27f9ff0858
parent7d7e95006fa70e3f348ce118e756207d57040d93 (diff)
vm: fix callframe double free on unhanded exceptions
When invoking a native function as toplevel VM call which indirectly triggers an unhandled exception in managed code, the callframes are completely reset before the C function returns, leading to invalid memory accesses when `uc_vm_call_native()` subsequently popped it's own callframe again. This issue did not surface by executing script code through the interpreter since in this case the VM will always execute a managed code as toplevel call, but it could be triggered by invoking a native function triggering an exception through the C API using `uc_vm_call()` on a fresh `uc_vm_t` context or by utilizing the CLI interpreters `-l` flag to preload a native code library triggering an exception. Signed-off-by: Jo-Philipp Wich <jo@mein.io>
-rw-r--r--tests/custom/04_bugs/35_vm_callframe_double_free36
-rw-r--r--vm.c6
2 files changed, 40 insertions, 2 deletions
diff --git a/tests/custom/04_bugs/35_vm_callframe_double_free b/tests/custom/04_bugs/35_vm_callframe_double_free
new file mode 100644
index 0000000..bb816eb
--- /dev/null
+++ b/tests/custom/04_bugs/35_vm_callframe_double_free
@@ -0,0 +1,36 @@
+When invoking a native function as toplevel VM call which indirectly
+triggers an unhandled exception in managed code, the callframes are
+completely reset before the C function returns, leading to invalid
+memory accesses when `uc_vm_call_native()` subsequently popped it's
+own callframe again.
+
+This issue did not surface by executing script code through the
+interpreter since in this case the VM will always execute a managed
+code as toplevel call, but it could be triggered by invoking a native
+function triggering an exception through the C API using `uc_vm_call()`
+on a fresh `uc_vm_t` context or by utilizing the CLI interpreters `-l`
+flag to preload a native code library triggering an exception.
+
+
+-- File ex.uc --
+die("Exception");
+-- End --
+
+-- Args --
+-L files/ -l ex
+-- End --
+
+-- Expect stderr --
+Exception
+In main(), file files/ex.uc, line 1, byte 16:
+ called from anonymous function ([C])
+
+ `die("Exception");`
+ Near here -----^
+
+
+-- End --
+
+-- Testcase --
+not reached
+-- End --
diff --git a/vm.c b/vm.c
index 0c756d7..45e6f42 100644
--- a/vm.c
+++ b/vm.c
@@ -435,8 +435,10 @@ uc_vm_call_native(uc_vm_t *vm, uc_value_t *ctx, uc_cfunction_t *fptr, bool mcall
res = fptr->cfn(vm, nargs);
- /* reset stack */
- ucv_put(uc_vm_callframe_pop(vm));
+ /* Reset stack, check for callframe depth since an uncatched exception in managed
+ * code executed by fptr->cfn() could've reset the callframe stack already. */
+ if (vm->callframes.count > 0)
+ ucv_put(uc_vm_callframe_pop(vm));
/* push return value */
if (!vm->exception.type)