]> git.kaiwu.me - njs.git/commitdiff
Fix reentrant object-to-primitive conversion
authorDmitry Volyntsev <xeioex@nginx.com>
Fri, 4 Sep 2026 01:48:39 +0000 (18:48 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Fri, 4 Sep 2026 21:53:08 +0000 (14:53 -0700)
Previously, njs_value_to_primitive() retained a pointer to the caller's value
slot while valueOf(), toString(), or property getters executed user code.
Reentrant code could overwrite that slot, after which the conversion treated
the replacement primitive as the original object.

The fix is to snapshot the input value before the first observable property
lookup and use the stable copy throughout conversion.

The issue was introduced in 64dbf104 (0.3.4).

This closes #1094 issue on GitHub.

src/njs_value.c
src/test/njs_unit_test.c

index 7d77e341551f15ea4dc58e3b0cc585fda06c6c2d..ae2f206aa579f6f4718828efbcbac828a2b974fa 100644 (file)
@@ -37,7 +37,7 @@ njs_value_to_primitive(njs_vm_t *vm, njs_value_t *dst, njs_value_t *value,
 {
     njs_int_t            ret;
     njs_uint_t           tries, force_ordinary;
-    njs_value_t          method, retval, arguments[2];
+    njs_value_t          input, method, retval, arguments[2];
     njs_flathsh_query_t  fhq;
 
     static const uint32_t atoms[] = {
@@ -56,6 +56,9 @@ njs_value_to_primitive(njs_vm_t *vm, njs_value_t *dst, njs_value_t *value,
         return NJS_OK;
     }
 
+    input = *value;
+    value = &input;
+
     tries = 0;
     fhq.proto = &njs_object_hash_proto;
 
index d190182510e84c4422b47740ef8ad2c2ac371998..d7137c569bd18135a455f98dfbdd006dc7b9eb22 100644 (file)
@@ -14367,6 +14367,15 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("false.__proto__ === Boolean.prototype"),
       njs_str("true") },
 
+    { njs_str("var o = {valueOf() { o = 0; return {}; },"
+              "         toString() { return '7'; }}; +o"),
+      njs_str("7") },
+
+    { njs_str("var original = {toString() { return '7'; }}, o = original;"
+              "Object.defineProperty(original, 'valueOf', {get() {"
+              "  o = 0; return () => ({}); }}); +o"),
+      njs_str("7") },
+
     { njs_str("var b = Boolean(1); b.__proto__ === Boolean.prototype"),
       njs_str("true") },