[njs] QuickJS: fixed potential heap-use-after-free.
noreply at nginx.com
noreply at nginx.com
Mon Sep 8 23:00:02 UTC 2025
details: https://github.com/nginx/njs/commit/4eeb4c527c8031b676c720c7ebb49ad356233a31
branches: master
commit: 4eeb4c527c8031b676c720c7ebb49ad356233a31
user: Dmitry Volyntsev <xeioex at nginx.com>
date: Thu, 28 Aug 2025 15:20:31 -0700
description:
QuickJS: fixed potential heap-use-after-free.
Previously in QuickJS engine, fields allocated from memory pool linked
to QuickJS engine lifetime were stored in nginx data structs.
This causes a heap-use-after-free if QuickJS engine is destroyed
earlier than a last access from nginx. For example, it becomes
visible when moving NJS cleanup handler from pool->cleanup to
r->cleanup.
The fix is to only store references in nginx objects allocated from
nginx memory pool.
---
nginx/ngx_http_js_module.c | 46 ++++++++++++++------------------------------
nginx/ngx_js.c | 7 ++++---
nginx/ngx_js.h | 3 ++-
nginx/ngx_qjs_fetch.c | 26 +++++++++++++++----------
nginx/ngx_stream_js_module.c | 2 +-
5 files changed, 37 insertions(+), 47 deletions(-)
diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c
index 3f38f86f..c73171b8 100644
--- a/nginx/ngx_http_js_module.c
+++ b/nginx/ngx_http_js_module.c
@@ -5173,7 +5173,7 @@ ngx_http_qjs_ext_internal_redirect(JSContext *cx, JSValueConst this_val,
"internalRedirect cannot be called while filtering");
}
- if (ngx_qjs_string(cx, argv[0], &ctx->redirect_uri) != NGX_OK) {
+ if (ngx_qjs_string(cx, r->pool, argv[0], &ctx->redirect_uri) != NGX_OK) {
return JS_EXCEPTION;
}
@@ -5452,7 +5452,7 @@ ngx_http_qjs_ext_return(JSContext *cx, JSValueConst this_val,
ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
if (status < NGX_HTTP_BAD_REQUEST || !JS_IsNullOrUndefined(argv[1])) {
- if (ngx_qjs_string(cx, argv[1], &body) != NGX_OK) {
+ if (ngx_qjs_string(cx, r->pool, argv[1], &body) != NGX_OK) {
return JS_ThrowOutOfMemory(cx);
}
@@ -5557,7 +5557,7 @@ ngx_http_qjs_ext_send(JSContext *cx, JSValueConst this_val,
ll = &out;
for (n = 0; n < (ngx_uint_t) argc; n++) {
- if (ngx_qjs_string(cx, argv[n], &s) != NGX_OK) {
+ if (ngx_qjs_string(cx, r->pool, argv[n], &s) != NGX_OK) {
return JS_ThrowTypeError(cx, "failed to convert arg");
}
@@ -5905,7 +5905,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val,
"the primary request");
}
- if (ngx_qjs_string(cx, argv[0], &uri) != NGX_OK) {
+ if (ngx_qjs_string(cx, r->pool, argv[0], &uri) != NGX_OK) {
return JS_ThrowTypeError(cx, "failed to convert uri arg");
}
@@ -5931,7 +5931,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val,
arg = argv[1];
if (JS_IsString(arg)) {
- if (ngx_qjs_string(cx, arg, &args) != NGX_OK) {
+ if (ngx_qjs_string(cx, r->pool, arg, &args) != NGX_OK) {
return JS_ThrowTypeError(cx, "failed to convert args");
}
@@ -5952,7 +5952,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val,
}
if (!JS_IsUndefined(value)) {
- rc = ngx_qjs_string(cx, value, &args);
+ rc = ngx_qjs_string(cx, r->pool, value, &args);
JS_FreeValue(cx, value);
if (rc != NGX_OK) {
@@ -5976,7 +5976,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val,
}
if (!JS_IsUndefined(value)) {
- rc = ngx_qjs_string(cx, value, &method_name);
+ rc = ngx_qjs_string(cx, r->pool, value, &method_name);
JS_FreeValue(cx, value);
if (rc != NGX_OK) {
@@ -6003,7 +6003,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val,
}
if (!JS_IsUndefined(value)) {
- rc = ngx_qjs_string(cx, value, &body_arg);
+ rc = ngx_qjs_string(cx, r->pool, value, &body_arg);
JS_FreeValue(cx, value);
if (rc != NGX_OK) {
@@ -6397,7 +6397,7 @@ ngx_http_qjs_variables_set_property(JSContext *cx, JSValueConst obj,
return -1;
}
- if (ngx_qjs_string(cx, value, &s) != NGX_OK) {
+ if (ngx_qjs_string(cx, r->pool, value, &s) != NGX_OK) {
return -1;
}
@@ -6876,7 +6876,7 @@ ngx_http_qjs_headers_out_handler(JSContext *cx, ngx_http_request_t *r,
}
}
- rc = ngx_qjs_string(cx, v, &s);
+ rc = ngx_qjs_string(cx, r->pool, v, &s);
if (qjs_is_array(cx, *value)) {
JS_FreeValue(cx, v);
@@ -6908,16 +6908,7 @@ ngx_http_qjs_headers_out_handler(JSContext *cx, ngx_http_request_t *r,
h->key.data = p;
h->key.len = name->len;
- p = ngx_pnalloc(r->pool, s.len);
- if (p == NULL) {
- h->hash = 0;
- (void) JS_ThrowOutOfMemory(cx);
- return -1;
- }
-
- ngx_memcpy(p, s.data, s.len);
-
- h->value.data = p;
+ h->value.data = s.data;
h->value.len = s.len;
h->hash = 1;
@@ -6977,7 +6968,7 @@ ngx_http_qjs_headers_out_special_handler(JSContext *cx, ngx_http_request_t *r,
setval = JS_UNDEFINED;
}
- rc = ngx_qjs_string(cx, setval, &s);
+ rc = ngx_qjs_string(cx, r->pool, setval, &s);
if (value != NULL && qjs_is_array(cx, *value)) {
JS_FreeValue(cx, setval);
@@ -7046,16 +7037,7 @@ done:
}
if (h != NULL) {
- p = ngx_pnalloc(r->pool, s.len);
- if (p == NULL) {
- h->hash = 0;
- (void) JS_ThrowOutOfMemory(cx);
- return -1;
- }
-
- ngx_memcpy(p, s.data, s.len);
-
- h->value.data = p;
+ h->value.data = s.data;
h->value.len = s.len;
h->hash = 1;
}
@@ -7212,7 +7194,7 @@ ngx_http_qjs_headers_out_content_type(JSContext *cx, ngx_http_request_t *r,
setval = *value;
}
- rc = ngx_qjs_string(cx, setval, &s);
+ rc = ngx_qjs_string(cx, r->pool, setval, &s);
if (qjs_is_array(cx, *value)) {
JS_FreeValue(cx, setval);
diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c
index 0678c3e6..d2bb781c 100644
--- a/nginx/ngx_js.c
+++ b/nginx/ngx_js.c
@@ -1462,7 +1462,8 @@ ngx_qjs_integer(JSContext *cx, JSValueConst val, ngx_int_t *n)
ngx_int_t
-ngx_qjs_string(JSContext *cx, JSValueConst val, ngx_str_t *dst)
+ngx_qjs_string(JSContext *cx, ngx_pool_t *pool, JSValueConst val,
+ ngx_str_t *dst)
{
size_t len, byte_offset, byte_length;
u_char *start;
@@ -1496,7 +1497,7 @@ ngx_qjs_string(JSContext *cx, JSValueConst val, ngx_str_t *dst)
start += byte_offset;
dst->len = byte_length;
- dst->data = njs_mp_alloc(e->pool, dst->len);
+ dst->data = ngx_pnalloc(pool, dst->len);
if (dst->data == NULL) {
return NGX_ERROR;
}
@@ -1513,7 +1514,7 @@ string:
return NGX_ERROR;
}
- start = njs_mp_alloc(e->pool, len);
+ start = ngx_pnalloc(pool, len);
if (start == NULL) {
JS_FreeCString(cx, str);
return NGX_ERROR;
diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h
index 0e217e90..032bd415 100644
--- a/nginx/ngx_js.h
+++ b/nginx/ngx_js.h
@@ -354,7 +354,8 @@ ngx_int_t ngx_qjs_call(JSContext *cx, JSValue function, JSValue *argv,
int argc);
ngx_int_t ngx_qjs_exception(ngx_engine_t *e, ngx_str_t *s);
ngx_int_t ngx_qjs_integer(JSContext *cx, JSValueConst val, ngx_int_t *n);
-ngx_int_t ngx_qjs_string(JSContext *cx, JSValueConst val, ngx_str_t *str);
+ngx_int_t ngx_qjs_string(JSContext *cx, ngx_pool_t *pool, JSValueConst val,
+ ngx_str_t *dst);
JSValue ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc,
JSValueConst *argv);
diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c
index d043bd5d..7130a0c2 100644
--- a/nginx/ngx_qjs_fetch.c
+++ b/nginx/ngx_qjs_fetch.c
@@ -622,7 +622,7 @@ ngx_qjs_request_ctor(JSContext *cx, ngx_js_request_t *request,
}
if (JS_IsString(input)) {
- rc = ngx_qjs_string(cx, input, &request->url);
+ rc = ngx_qjs_string(cx, pool, input, &request->url);
if (rc != NGX_OK) {
JS_ThrowInternalError(cx, "failed to convert url arg");
return NGX_ERROR;
@@ -694,7 +694,7 @@ ngx_qjs_request_ctor(JSContext *cx, ngx_js_request_t *request,
}
if (!JS_IsUndefined(value)) {
- rc = ngx_qjs_string(cx, value, &request->method);
+ rc = ngx_qjs_string(cx, pool, value, &request->method);
JS_FreeValue(cx, value);
if (rc != NGX_OK) {
@@ -774,7 +774,7 @@ ngx_qjs_request_ctor(JSContext *cx, ngx_js_request_t *request,
}
if (!JS_IsUndefined(value)) {
- if (ngx_qjs_string(cx, value, &request->body) != NGX_OK) {
+ if (ngx_qjs_string(cx, pool, value, &request->body) != NGX_OK) {
JS_FreeValue(cx, value);
JS_ThrowInternalError(cx, "invalid Request body");
return NGX_ERROR;
@@ -866,7 +866,7 @@ ngx_qjs_fetch_response_ctor(JSContext *cx, JSValueConst new_target, int argc,
}
if (!JS_IsUndefined(value)) {
- ret = ngx_qjs_string(cx, value, &response->status_text);
+ ret = ngx_qjs_string(cx, pool, value, &response->status_text);
JS_FreeValue(cx, value);
if (ret < 0) {
@@ -913,7 +913,7 @@ ngx_qjs_fetch_response_ctor(JSContext *cx, JSValueConst new_target, int argc,
body = argv[0];
if (!JS_IsNullOrUndefined(body)) {
- if (ngx_qjs_string(cx, body, &bd) != NGX_OK) {
+ if (ngx_qjs_string(cx, pool, body, &bd) != NGX_OK) {
return JS_ThrowInternalError(cx, "invalid Response body");
}
@@ -1054,16 +1054,19 @@ static ngx_int_t
ngx_qjs_headers_fill_header_free(JSContext *cx, ngx_js_headers_t *headers,
JSValue prop_name, JSValue prop_value)
{
- ngx_int_t rc;
- ngx_str_t name, value;
+ ngx_int_t rc;
+ ngx_str_t name, value;
+ ngx_pool_t *pool;
+
+ pool = ngx_qjs_external_pool(cx, JS_GetContextOpaque(cx));
- if (ngx_qjs_string(cx, prop_name, &name) != NGX_OK) {
+ if (ngx_qjs_string(cx, pool, prop_name, &name) != NGX_OK) {
JS_FreeValue(cx, prop_name);
JS_FreeValue(cx, prop_value);
return NGX_ERROR;
}
- if (ngx_qjs_string(cx, prop_value, &value) != NGX_OK) {
+ if (ngx_qjs_string(cx, pool, prop_value, &value) != NGX_OK) {
JS_FreeValue(cx, prop_name);
JS_FreeValue(cx, prop_value);
return NGX_ERROR;
@@ -1950,6 +1953,7 @@ ngx_qjs_ext_fetch_headers_set(JSContext *cx, JSValueConst this_val,
ngx_int_t rc;
ngx_str_t name, value;
ngx_uint_t i;
+ ngx_pool_t *pool;
ngx_list_part_t *part;
ngx_js_tb_elt_t *h, **ph, **pp;
ngx_js_headers_t *headers;
@@ -1965,7 +1969,9 @@ ngx_qjs_ext_fetch_headers_set(JSContext *cx, JSValueConst this_val,
return JS_EXCEPTION;
}
- rc = ngx_qjs_string(cx, argv[1], &value);
+ pool = ngx_qjs_external_pool(cx, JS_GetContextOpaque(cx));
+
+ rc = ngx_qjs_string(cx, pool, argv[1], &value);
if (rc != NGX_OK) {
JS_FreeCString(cx, (const char *) name.data);
return JS_EXCEPTION;
diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c
index cb44aa1f..5ccc08a2 100644
--- a/nginx/ngx_stream_js_module.c
+++ b/nginx/ngx_stream_js_module.c
@@ -2651,7 +2651,7 @@ ngx_stream_qjs_variables_set_property(JSContext *cx, JSValueConst obj,
return -1;
}
- if (ngx_qjs_string(cx, value, &val) != NGX_OK) {
+ if (ngx_qjs_string(cx, s->connection->pool, value, &val) != NGX_OK) {
return -1;
}
More information about the nginx-devel
mailing list