Closed 0xbigshaq closed 4 days ago
Hi @0xbigshaq,
Regarding the first question, what you found is a bug and not an undefined-behaviour.
The 'subtle' property is not deleted, but becomes hidden, due to improper enumerated flag copied here.
For example
crypto.subtle;
crypto.subtle;
console.log(crypto.subtle.digest);
outputs:
[Function: digest]
2 functions are important here njs_external_add() and njs_external_prop_handler().
We have copy-on-read here: we create properties in the current context lazily and at the moment of the first access from a shared precompiled state. For example, 'subtle' property is stored as NJS_PROPERTY_HANDLER
in shared hash which at the moment of access creates a modifiable property by calling njs_external_prop_handler()
which puts the property in the local hash of an object.
diff --git a/src/njs_extern.c b/src/njs_extern.c
index df51f9b7..9ec1c1c9 100644
--- a/src/njs_extern.c
+++ b/src/njs_extern.c
@@ -236,11 +236,9 @@ njs_external_prop_handler(njs_vm_t *vm, njs_object_prop_t *self,
return NJS_ERROR;
}
- if (slots != NULL) {
- prop->writable = slots->writable;
- prop->configurable = slots->configurable;
- prop->enumerable = slots->enumerable;
- }
+ prop->writable = self->writable;
+ prop->configurable = self->configurable;
+ prop->enumerable = self->enumerable;
lhq.value = prop;
njs_string_get(&self->name, &lhq.key);
should fix the first problem.
Hi @xeioex You're right, it's hidden, not deleted. I applied the patch and it fixed the problem of the key being hidden. However, the segfault remains(of the last snippet). Perhaps this is a different bug? I wonder if I should've seperate it into two tickets. One for the disappearing key and one for the segfault(i thought it was the same root cause, at least that's the feeling I had)
I'll try to look at it in the next few days & update here if I find anything, hopefully I'll have an idea to why this happens.
Hi @0xbigshaq,
I agree there is a second bug, but I cannot find the root cause right away. I plan to look into it as time permits. One issue it enough I believe.
The bug is in Object.values() and Object.entries() which are implemented by njs_object_own_enumerate_object().
The function erroneously puts a shared object from a shared_hash directly to a local hash of the object.
Normally, to support lazy instantiation of properties, when accessing an object with shared
field set it should be copied and put to a local hash for all future references. As it is done during normal properties retrieval by njs_prop_private_copy(). The fix is to use normal njs_value_property()
in
njs_object_own_enumerate_object()
for props retrieval.
Here is the test, that shows the issue with ./configure --address-sanitizer=YES --debug-memory=YES
var crypto = Object.entries(global).filter((v) => v[0] == 'crypto')[0][1] // this makes a direct copy of the shared crypto object
crypto.populate_local_hash = 1 // this erroneously modified a local hash of the shared object
var second_crypto = global.crypto // it makes a copy of a shared object, and inherits a copy of local hash of crypto
second_crypto.subtle
// this reallocate local hash, the same as in crypto
// but update here is not reflected in crypto
// as a result hash in crypto points to freed memory
second_crypto.getRandomValues
console.log(crypto) // accesses freed memory though local hash
In case of your test, it can be simplified to:
let all = Object.entries(global);
let sus = all[3][1];
sus.abc = 1
try {
Uint8Array.prototype.map.call(1, undefined)
} catch (e) {
sus.abc
}
Here we have the same issue, but it is triggered differently.
sus
is the same as crypto
in my test
equivalent to second_crypto
appears during global object traversal which we do to find the names of the functions in a backtrace here. While looking for function names we recursively traverse all the properties in the global object. As a result we fetch the second copy of crypto
which is copied, as explained above.
Describe the bug
Upon the first access, the
global.crypto
object contains both thegetRandomValues
function and thesubtle
property, which itself contains multiple cryptographic methods. However, on the second access, thesubtle
property is missing, leaving only thegetRandomValues
function.To reproduce
Steps to reproduce the behavior:
or put the code in a gist and link it here.
or put the configuration in a gist and link it here.
or post the full log to a gist and link it here.
nginx -V
command if applicable.Expected behavior
The
global.crypto
object should consistently contain all its properties and methods, including thesubtle
property, regardless of how many times it is accessed or logged.Your environment
v0.8.5
/d34fcb03cf2378a644a3c7366d58cbddc2771cbd
Ubuntu:20.04
(WSL/Docker environment)Additional context
I found that it can lead to crashes too, try running the following JS script:
crash at src/njs_flathsh.c:340 at line
elt_num = njs_hash_cells_end(h)[-cell_num - 1];
becausecell_num
has a value that is too big(looks like a pointer rather than an offset/array index). gdb output:For the last few days I tried to find the root cause but couldn't. So I thought it's worth bringing this to your attention, hopefully you can shed some more light on this and share your insights on why it happens.