Skip to content

Commit a653771

Browse files
authored
Fix fast array expansion overflow
1 parent da49a37 commit a653771

2 files changed

Lines changed: 37 additions & 7 deletions

File tree

quickjs.c

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6483,7 +6483,7 @@ static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref)
64836483
static void js_array_finalizer(JSRuntime *rt, JSValueConst val)
64846484
{
64856485
JSObject *p = JS_VALUE_GET_OBJ(val);
6486-
int i;
6486+
uint32_t i;
64876487

64886488
for(i = 0; i < p->u.array.count; i++) {
64896489
JS_FreeValueRT(rt, p->u.array.u.values[i]);
@@ -6495,7 +6495,7 @@ static void js_array_mark(JSRuntime *rt, JSValueConst val,
64956495
JS_MarkFunc *mark_func)
64966496
{
64976497
JSObject *p = JS_VALUE_GET_OBJ(val);
6498-
int i;
6498+
uint32_t i;
64996499

65006500
for(i = 0; i < p->u.array.count; i++) {
65016501
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)
99809980
size_t slack;
99819981
JSValue *new_array_prop;
99829982

9983+
if (unlikely(new_len > (uint32_t)INT32_MAX)) {
9984+
JS_ThrowOutOfMemory(ctx);
9985+
return -1;
9986+
}
9987+
99839988
old_size = p->u.array.u1.size;
9984-
new_size = old_size + old_size/2; // grow by 50%
9985-
if (new_size < old_size) { // integer overflow
9989+
new_size = old_size + old_size/2;
9990+
if (new_size < old_size) {
99869991
JS_ThrowOutOfMemory(ctx);
99879992
return -1;
99889993
}
9989-
new_size = max_int(new_len, new_size);
9994+
new_size = max_uint32(new_len, new_size);
99909995
new_array_prop = js_realloc2(ctx, p->u.array.u.values, sizeof(JSValue) * new_size, &slack);
99919996
if (!new_array_prop)
99929997
return -1;
@@ -42467,7 +42472,7 @@ static JSValue js_array_push(JSContext *ctx, JSValueConst this_val,
4246742472
(p->shape->prop->flags & JS_PROP_WRITABLE))) {
4246842473
array_len = JS_VALUE_GET_INT(p->prop[0].u.value);
4246942474
new_len = array_len + argc;
42470-
if (likely(new_len >= array_len)) { /* no overflow */
42475+
if (likely(new_len >= array_len && new_len <= (uint32_t)INT32_MAX)) { /* no overflow and within fast-array bounds */
4247142476
if (unlikely(new_len > p->u.array.u1.size)) {
4247242477
if (expand_fast_array(ctx, p, new_len))
4247342478
return JS_EXCEPTION;
@@ -42476,7 +42481,7 @@ static JSValue js_array_push(JSContext *ctx, JSValueConst this_val,
4247642481
p->u.array.u.values[array_len + i] = js_dup(argv[i]);
4247742482
}
4247842483
p->u.array.count = new_len;
42479-
p->prop[0].u.value = js_int32(new_len);
42484+
p->prop[0].u.value = js_uint32(new_len);
4248042485
return js_int32(new_len);
4248142486
}
4248242487
}

tests/bug1468.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Bug: expand_fast_array calls max_int(new_len, new_size) where new_len is uint32_t.
2+
// When new_len >= 0x80000000, cast to int makes it negative.
3+
// max_int returns the small grow-by-50% size. Buffer is underallocated.
4+
// add_fast_array_element then writes to values[new_len-1] — OOB write.
5+
//
6+
// Pre-fix behavior: process crashes with SIGBUS (OOB write to unmapped memory)
7+
// Post-fix behavior: InternalError or RangeError thrown and caught cleanly
8+
//
9+
// Run: qjs poc_test_fixed.js
10+
// Expected output: PASS: got graceful error: ...
11+
const arr = [1, 2, 3];
12+
arr.length = 0x7FFFFFFF;
13+
14+
try {
15+
const result = arr.push(4);
16+
// Succeeded via slow array path — no crash, no heap corruption
17+
print("PASS: push completed without crash, result:", result);
18+
19+
} catch(e) {
20+
if (e instanceof InternalError || e instanceof RangeError || e instanceof TypeError) {
21+
print("PASS: got graceful error:", e.message);
22+
} else {
23+
throw e; // unexpected — re-throw
24+
}
25+
}

0 commit comments

Comments
 (0)