Skip to content

Commit c061fbf

Browse files
committed
fix: heap-use-after-free in build_backtrace when dbuf OOM frees current_exception
If JS_NewError() during build_backtrace triggered dbuf OOM, JS_ThrowOutOfMemory freed the current exception (error_val from the caller's stack frame), then the rest of build_backtrace continued using the freed error_val for the prepareStackTrace call and the JS_DefinePropertyValue of the stack property. The fix duplicates error_val into a local error_obj at function entry, uses error_obj throughout the function, and frees it at exit. Fixes #1469
1 parent a653771 commit c061fbf

2 files changed

Lines changed: 38 additions & 4 deletions

File tree

api-test.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,37 @@ static void new_errors(void)
765765
JS_FreeRuntime(rt);
766766
}
767767

768+
static void backtrace_oom_current_exception(void)
769+
{
770+
static const char setup_code[] =
771+
"globalThis.f = function() { missing; };\n"
772+
"Object.defineProperty(f, 'name', { value: 'x'.repeat(2 * 1024 * 1024) });";
773+
JSMemoryUsage stats;
774+
JSValue ret, exception;
775+
JSRuntime *rt;
776+
JSContext *ctx;
777+
778+
rt = new_runtime();
779+
ctx = JS_NewContext(rt);
780+
781+
ret = eval(ctx, setup_code);
782+
assert(!JS_IsException(ret));
783+
JS_FreeValue(ctx, ret);
784+
785+
JS_ComputeMemoryUsage(rt, &stats);
786+
JS_SetMemoryLimit(rt, (size_t)stats.malloc_size + 128 * 1024);
787+
788+
ret = eval(ctx, "f()");
789+
assert(JS_IsException(ret));
790+
assert(JS_HasException(ctx));
791+
exception = JS_GetException(ctx);
792+
JS_FreeValue(ctx, exception);
793+
JS_SetMemoryLimit(rt, 0);
794+
795+
JS_FreeContext(ctx);
796+
JS_FreeRuntime(rt);
797+
}
798+
768799
static int gop_get_own_property(JSContext *ctx, JSPropertyDescriptor *desc,
769800
JSValueConst obj, JSAtom prop)
770801
{
@@ -1042,6 +1073,7 @@ int main(void)
10421073
promise_hook();
10431074
dump_memory_usage();
10441075
new_errors();
1076+
backtrace_oom_current_exception();
10451077
global_object_prototype();
10461078
slice_string_tocstring();
10471079
immutable_array_buffer();

quickjs.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7743,7 +7743,7 @@ static void build_backtrace(JSContext *ctx, JSValueConst error_val,
77437743
int line_num, int col_num, int backtrace_flags)
77447744
{
77457745
JSStackFrame *sf, *sf_start;
7746-
JSValue stack, prepare, saved_exception;
7746+
JSValue stack, prepare, saved_exception, error_obj;
77477747
DynBuf dbuf;
77487748
const char *func_name_str;
77497749
const char *str1;
@@ -7760,6 +7760,7 @@ static void build_backtrace(JSContext *ctx, JSValueConst error_val,
77607760
if (rt->in_build_stack_trace)
77617761
return;
77627762
rt->in_build_stack_trace = true;
7763+
error_obj = JS_DupValue(ctx, error_val);
77637764

77647765
// Save exception because conversion to double may fail.
77657766
saved_exception = JS_GetException(ctx);
@@ -7905,7 +7906,7 @@ static void build_backtrace(JSContext *ctx, JSValueConst error_val,
79057906
JS_FreeValue(ctx, csd[k].func_name);
79067907
}
79077908
JSValueConst args[] = {
7908-
error_val,
7909+
error_obj,
79097910
stack,
79107911
};
79117912
JSValue stack2 = JS_Call(ctx, prepare, ctx->error_ctor, countof(args), args);
@@ -7926,13 +7927,14 @@ static void build_backtrace(JSContext *ctx, JSValueConst error_val,
79267927

79277928
if (JS_IsUndefined(ctx->error_back_trace))
79287929
ctx->error_back_trace = js_dup(stack);
7929-
if (has_filter_func || can_add_backtrace(error_val)) {
7930-
JS_DefinePropertyValue(ctx, error_val, JS_ATOM_stack, stack,
7930+
if (has_filter_func || can_add_backtrace(error_obj)) {
7931+
JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_stack, stack,
79317932
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
79327933
} else {
79337934
JS_FreeValue(ctx, stack);
79347935
}
79357936

7937+
JS_FreeValue(ctx, error_obj);
79367938
rt->in_build_stack_trace = false;
79377939
}
79387940

0 commit comments

Comments
 (0)