]> git.kaiwu.me - njs.git/commitdiff
Buffer: reject oversized QJS alloc() and concat() sizes
authorDmitry Volyntsev <xeioex@nginx.com>
Thu, 24 Sep 2026 22:53:30 +0000 (15:53 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Fri, 25 Sep 2026 23:00:29 +0000 (16:00 -0700)
Buffer.alloc() and Buffer.concat() converted sizes with JS_ToUint32(),
which wraps values modulo 2^32: Buffer.alloc(2 ** 32 + 16) returned a
16-byte buffer.  Use JS_ToIndex() instead, so these sizes throw a
RangeError.

In Buffer.concat(), the total length of the list could also overflow the
32-bit accumulator.  Keep it in 64 bits and check it against
QJS_BUFFER_MAX_LENGTH.  The overflow now throws a RangeError instead of a
TypeError, as in Node.js.

src/qjs_buffer.c
test/buffer.t.js

index 9359a80e2fddc75a4e4f721feecb28a7dacda70e..65c572164ab6fda16c0553fcc3c287ceee5a7616 100644 (file)
@@ -347,14 +347,14 @@ qjs_bufferobj_alloc(JSContext *ctx, JSValueConst this_val, int argc,
     JSValueConst *argv, int ignored)
 {
     JSValue   buffer, ret;
-    uint32_t  size;
+    uint64_t  size;
 
     if (!JS_IsNumber(argv[0])) {
         return JS_ThrowTypeError(ctx, "The \"size\" argument must be of type"
                                  " number");
     }
 
-    if (JS_ToUint32(ctx, &size, argv[0])) {
+    if (JS_ToIndex(ctx, &size, argv[0])) {
         return JS_EXCEPTION;
     }
 
@@ -434,8 +434,9 @@ qjs_buffer_concat(JSContext *ctx, JSValueConst this_val, int argc,
 {
     u_char     *p;
     size_t     n;
+    uint64_t   len;
     JSValue    list, length, val, ret, buffer;
-    uint32_t   i, len, list_len;
+    uint32_t   i, list_len;
     njs_str_t  buf, dst;
 
     list = argv[0];
@@ -472,16 +473,16 @@ qjs_buffer_concat(JSContext *ctx, JSValueConst this_val, int argc,
                                         " instance of Buffer or Uint8Array", i);
             }
 
-            if ((SIZE_MAX - len) < buf.length) {
-                return JS_ThrowTypeError(ctx,
-                                         "Total size of buffers is too large");
+            if (buf.length > QJS_BUFFER_MAX_LENGTH - len) {
+                return JS_ThrowRangeError(ctx,
+                                          "Total size of buffers is too large");
             }
 
             len += buf.length;
         }
 
     } else {
-        if (JS_ToUint32(ctx, &len, argv[1])) {
+        if (JS_ToIndex(ctx, &len, argv[1])) {
             return JS_EXCEPTION;
         }
     }
index 1d749493b26b1f602b2fc6efc82dafb44faf955a..21fdedfd0cd4c67e87f0812aab628cacb6ce7bdc 100644 (file)
@@ -35,6 +35,30 @@ let alloc_tsuite = {
 };
 
 
+let oversized_tsuite = {
+    name: "Buffer allocation size tests",
+    skip: () => (!has_buffer() || !has_njs()),
+    T: async (params) => {
+        assert.throws(RangeError, () => {
+            if (params.method === 'alloc') {
+                Buffer.alloc(params.size);
+
+            } else {
+                Buffer.concat([], params.size);
+            }
+        });
+
+        return 'SUCCESS';
+    },
+    tests: [
+        { method: 'alloc', size: 0x100000000 },
+        { method: 'alloc', size: 0x100000010 },
+        { method: 'concat', size: 0x100000000 },
+        { method: 'concat', size: 0x100000010 },
+    ],
+};
+
+
 let fromObject_tsuite = {
     name: "Buffer.from() array-like length tests",
     skip: () => (!has_buffer()),
@@ -1158,6 +1182,7 @@ let writeGeneric_tsuite = {
 
 run([
     alloc_tsuite,
+    oversized_tsuite,
     fromObject_tsuite,
     fromObjectLarge_tsuite,
     byteLength_tsuite,