]> git.kaiwu.me - njs.git/commitdiff
QuickJS: fixed Buffer.from() and encoding error paths
authorDmitry Volyntsev <xeioex@nginx.com>
Thu, 21 May 2026 23:01:56 +0000 (16:01 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Tue, 26 May 2026 21:45:24 +0000 (14:45 -0700)
Freed the typed array constructor object after reading constructor.name
while detecting Float32Array in Buffer.from().  This keeps the exception
path from leaking the constructor object.

Handled JS_ToCString() failure for constructor.name before comparing the
name, avoiding a NULL dereference when the property converts to an
exception, such as a Symbol value.

Divided the source offset by the element size in qjs_buffer_from()
typed-array path so the offset addresses the right element for 2-, 4-,
and 8-byte element types (previously the offset was left in byte units
while size was already in element units).

Added a NULL check on JS_ToCStringLen() in qjs_buffer_encoding(), and
moved the JS_FreeCString() call after the JS_ThrowTypeError() so the
encoding name remains valid while the error message is formatted.

Routed array source errors in qjs_buffer_from_object() through a single
fail label so the destination buffer is freed once on every failure path
(previously leaked on three of them).

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

index 890b20283a17fd69820c0e312c9ae9b4da1f9f5a..90515f7443b6c5b8bda430d91c2cba86196846ba 100644 (file)
@@ -635,13 +635,18 @@ qjs_buffer_from(JSContext *ctx, JSValueConst this_val, int argc,
             }
 
             name = JS_GetPropertyStr(ctx, ctor, "name");
+            JS_FreeValue(ctx, ctor);
             if (JS_IsException(name)) {
                 JS_FreeValue(ctx, ret);
                 return name;
             }
 
-            JS_FreeValue(ctx, ctor);
             str = JS_ToCString(ctx, name);
+            if (str == NULL) {
+                JS_FreeValue(ctx, name);
+                JS_FreeValue(ctx, ret);
+                return JS_EXCEPTION;
+            }
 
             if (strncmp(str, "Float32Array", 12) == 0) {
                 float32 = 1;
@@ -1909,6 +1914,7 @@ qjs_buffer_from_typed_array(JSContext *ctx, JSValueConst arr_buf,
     njs_str_t  src, dst;
 
     size = size / bytes;
+    offset = offset / bytes;
     buffer = qjs_buffer_alloc(ctx, size);
     if (JS_IsException(buffer)) {
         JS_FreeValue(ctx, arr_buf);
@@ -2045,7 +2051,7 @@ reject:
 
     ret = qjs_typed_array_data(ctx, buffer, &dst);
     if (JS_IsException(ret)) {
-        return ret;
+        goto fail;
     }
 
     p = dst.start;
@@ -2053,11 +2059,12 @@ reject:
     for (i = 0; i < len; i++) {
         ret = JS_GetPropertyUint32(ctx, obj, i);
         if (njs_slow_path(JS_IsException(ret))) {
-            return ret;
+            goto fail;
         }
 
         if (njs_slow_path(JS_ToInt32(ctx, &v, ret))) {
-            return JS_EXCEPTION;
+            JS_FreeValue(ctx, ret);
+            goto fail;
         }
 
         JS_FreeValue(ctx, ret);
@@ -2066,6 +2073,12 @@ reject:
     }
 
     return buffer;
+
+fail:
+
+    JS_FreeValue(ctx, buffer);
+
+    return JS_EXCEPTION;
 }
 
 
@@ -2085,6 +2098,9 @@ qjs_buffer_encoding(JSContext *ctx, JSValueConst value, JS_BOOL thrw)
     }
 
     name.start = (u_char *) JS_ToCStringLen(ctx, &name.length, value);
+    if (name.start == NULL) {
+        return NULL;
+    }
 
     for (encoding = &qjs_buffer_encodings[0];
          encoding->name.length != 0;
@@ -2096,13 +2112,13 @@ qjs_buffer_encoding(JSContext *ctx, JSValueConst value, JS_BOOL thrw)
         }
     }
 
-    JS_FreeCString(ctx, (char *) name.start);
-
     if (thrw) {
         JS_ThrowTypeError(ctx, "\"%.*s\" encoding is not supported",
                           (int) name.length, name.start);
     }
 
+    JS_FreeCString(ctx, (char *) name.start);
+
     return NULL;
 }
 
index ee7831d9bb757a07b342204ba7fc7f2168079a93..71aa73a2ba273db8c816d01606692470898831d6 100644 (file)
@@ -290,6 +290,18 @@ let fill_tsuite = {
 };
 
 
+function typedArrayWithOffset(TypedArray, prefix, values) {
+    let bytes = TypedArray.BYTES_PER_ELEMENT;
+    let buffer = new ArrayBuffer((prefix.length + values.length) * bytes);
+    let view = new TypedArray(buffer, prefix.length * bytes, values.length);
+
+    new TypedArray(buffer, 0, prefix.length).set(prefix);
+    view.set(values);
+
+    return view;
+}
+
+
 let from_tsuite = {
     name: "Buffer.from() tests",
     skip: () => (!has_buffer()),
@@ -344,6 +356,19 @@ let from_tsuite = {
         { args: [new Float32Array([234.001, 123.11])], fmt: "hex", expected: 'ea7b' },
         { args: [new Uint32Array([234, 123])], fmt: "hex", expected: 'ea7b' },
         { args: [new Float64Array([234.001, 123.11])], fmt: "hex", expected: 'ea7b' },
+        { args: [typedArrayWithOffset(Uint16Array,
+                                      [0xaaaa, 0xbbbb, 0xcccc, 0xdddd],
+                                      [0x1234, 0x00ff, 0x0100, 0x017f])],
+          fmt: "hex", expected: '34ff007f' },
+        { args: [typedArrayWithOffset(Uint32Array,
+                                      [0xaaaaaaaa, 0xbbbbbbbb,
+                                       0xcccccccc, 0xdddddddd],
+                                      [0x12345678, 0x000000ff,
+                                       0x00000100, 0x0000017f])],
+          fmt: "hex", expected: '78ff007f' },
+        { args: [typedArrayWithOffset(Float64Array,
+                                      [1000.01, 1001.01], [234.001, 123.11])],
+          fmt: "hex", expected: 'ea7b' },
 
         { args: [(new Uint8Array(2)).buffer, -1],
           exception: 'RangeError: invalid index' },
@@ -369,6 +394,10 @@ let from_tsuite = {
           fmt: "hex", expected: '010203' },
         { args: [(function() {var a = [1,2,3,4]; a[1] = { valueOf() { a.length = 3; return 1; } }; return a})()],
           fmt: "hex", expected: '01010300' },
+        { args: [{length: 3, get 0() { throw Error('boom') }}],
+          exception: 'Error: boom' },
+        { args: [{length: 3, 0: { valueOf() { throw Error('boom') } }}],
+          exception: 'Error: boom' },
 
         { args: [{type: 'B'}],
           exception: 'TypeError: first argument is not a string or Buffer-like object' },