diff --git a/quickjs.c b/quickjs.c index 43966b14a..0a3abac68 100644 --- a/quickjs.c +++ b/quickjs.c @@ -6483,7 +6483,7 @@ static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref) static void js_array_finalizer(JSRuntime *rt, JSValueConst val) { JSObject *p = JS_VALUE_GET_OBJ(val); - int i; + uint32_t i; for(i = 0; i < p->u.array.count; i++) { JS_FreeValueRT(rt, p->u.array.u.values[i]); @@ -6495,7 +6495,7 @@ static void js_array_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); - int i; + uint32_t i; for(i = 0; i < p->u.array.count; i++) { JS_MarkValue(rt, p->u.array.u.values[i], mark_func); @@ -9980,13 +9980,18 @@ static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len) size_t slack; JSValue *new_array_prop; + if (unlikely(new_len > (uint32_t)INT32_MAX)) { + JS_ThrowOutOfMemory(ctx); + return -1; + } + old_size = p->u.array.u1.size; - new_size = old_size + old_size/2; // grow by 50% - if (new_size < old_size) { // integer overflow + new_size = old_size + old_size/2; + if (new_size < old_size) { JS_ThrowOutOfMemory(ctx); return -1; } - new_size = max_int(new_len, new_size); + new_size = max_uint32(new_len, new_size); new_array_prop = js_realloc2(ctx, p->u.array.u.values, sizeof(JSValue) * new_size, &slack); if (!new_array_prop) return -1; @@ -42467,7 +42472,7 @@ static JSValue js_array_push(JSContext *ctx, JSValueConst this_val, (p->shape->prop->flags & JS_PROP_WRITABLE))) { array_len = JS_VALUE_GET_INT(p->prop[0].u.value); new_len = array_len + argc; - if (likely(new_len >= array_len)) { /* no overflow */ + if (likely(new_len >= array_len && new_len <= (uint32_t)INT32_MAX)) { /* no overflow and within fast-array bounds */ if (unlikely(new_len > p->u.array.u1.size)) { if (expand_fast_array(ctx, p, new_len)) return JS_EXCEPTION; @@ -42476,7 +42481,7 @@ static JSValue js_array_push(JSContext *ctx, JSValueConst this_val, p->u.array.u.values[array_len + i] = js_dup(argv[i]); } p->u.array.count = new_len; - p->prop[0].u.value = js_int32(new_len); + p->prop[0].u.value = js_uint32(new_len); return js_int32(new_len); } } diff --git a/tests/bug1468.js b/tests/bug1468.js new file mode 100644 index 000000000..904f2ca9d --- /dev/null +++ b/tests/bug1468.js @@ -0,0 +1,25 @@ +// Bug: expand_fast_array calls max_int(new_len, new_size) where new_len is uint32_t. +// When new_len >= 0x80000000, cast to int makes it negative. +// max_int returns the small grow-by-50% size. Buffer is underallocated. +// add_fast_array_element then writes to values[new_len-1] — OOB write. +// +// Pre-fix behavior: process crashes with SIGBUS (OOB write to unmapped memory) +// Post-fix behavior: InternalError or RangeError thrown and caught cleanly +// +// Run: qjs poc_test_fixed.js +// Expected output: PASS: got graceful error: ... +const arr = [1, 2, 3]; +arr.length = 0x7FFFFFFF; + +try { + const result = arr.push(4); + // Succeeded via slow array path — no crash, no heap corruption + print("PASS: push completed without crash, result:", result); + +} catch(e) { + if (e instanceof InternalError || e instanceof RangeError || e instanceof TypeError) { + print("PASS: got graceful error:", e.message); + } else { + throw e; // unexpected — re-throw + } +}