Skip to content
19 changes: 12 additions & 7 deletions quickjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
}
Expand Down
25 changes: 25 additions & 0 deletions tests/bug1468.js
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading