From 8aeef5101a90c0b89851eaf5d646403d499993c5 Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Thu, 29 Oct 2026 18:44:24 -0700 Subject: [PATCH] QuickJS: copy body bytes into ArrayBuffer Previously, r.readRequestArrayBuffer() and the arrayBuffer() methods of fetch Request and Response objects returned an ArrayBuffer over memory owned by a pool: the request pool or the engine pool. The buffer was created without a free function, which caused the following problems: - with js_context_reuse, a buffer retained in the module scope outlived the pool and pointed to freed memory in the next request; - ArrayBuffer.prototype.transfer() called the NULL free function; - writes through the buffer modified the request body seen by subsequent r.requestText and r.readRequestText() calls, for a single buffer body directly in the nginx request buffers. Now, the bytes are copied into memory owned by the ArrayBuffer. For Response.arrayBuffer() the body chain is joined directly into the runtime memory, so the number of copies is unchanged. The issue was introduced in b7f76b71 (0.9.1) and 7684d702 (0.9.9). Based on a patch by Vadim Zhestikov. --- nginx/ngx_http_js_module.c | 4 ++-- nginx/ngx_qjs_fetch.c | 29 ++++++++++++++--------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 86399ec0..898e9db4 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -6690,8 +6690,8 @@ ngx_http_qjs_body_to_value(JSContext *cx, ngx_http_js_ctx_t *ctx, switch (type) { case NGX_JS_BODY_ARRAY_BUFFER: - return qjs_new_external_array_buffer(cx, ctx->body_read_data, - ctx->body_read_len, 0); + return JS_NewArrayBufferCopy(cx, ctx->body_read_data, + ctx->body_read_len); case NGX_JS_BODY_JSON: if (ctx->body_read_nul) { diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c index be3f299b..7149b976 100644 --- a/nginx/ngx_qjs_fetch.c +++ b/nginx/ngx_qjs_fetch.c @@ -2004,12 +2004,8 @@ ngx_qjs_ext_fetch_request_body(JSContext *cx, JSValueConst this_val, switch (magic) { case NGX_JS_BODY_ARRAY_BUFFER: - /* - * request->body is allocated from e->pool - * and will be freed when context is freed. - */ - result = qjs_new_external_array_buffer(cx, request->body.data, - request->body.len, 0); + result = JS_NewArrayBufferCopy(cx, request->body.data, + request->body.len); if (JS_IsException(result)) { return JS_ThrowOutOfMemory(cx); } @@ -2298,6 +2294,8 @@ static JSValue ngx_qjs_ext_fetch_response_body(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { + u_char *p; + int64_t size; JSValue result; njs_int_t ret; njs_str_t string; @@ -2325,21 +2323,22 @@ ngx_qjs_ext_fetch_response_body(JSContext *cx, JSValueConst this_val, break; case NGX_JS_BODY_ARRAY_BUFFER: - ret = njs_chb_join(&response->chain, &string); - if (ret != NJS_OK) { + size = njs_chb_size(&response->chain); + if (size < 0) { return JS_ThrowOutOfMemory(cx); } + p = js_malloc(cx, njs_max(size, 1)); + if (p == NULL) { + return JS_EXCEPTION; + } + + njs_chb_join_to(&response->chain, p); njs_chb_destroy(&response->chain); - /* - * string.start is allocated from e->pool - * and will be freed when context is freed. - */ - result = qjs_new_external_array_buffer(cx, string.start, - string.length, 0); + result = qjs_new_array_buffer(cx, p, size); if (JS_IsException(result)) { - return JS_ThrowOutOfMemory(cx); + js_free(cx, p); } break; -- 2.47.3