Skip to content

Commit 5f641d0

Browse files
committed
vm: fix property queries for proxy sandboxes
Signed-off-by: Brian Meek <55990082+brianathere@users.noreply.github.com>
1 parent 619b38e commit 5f641d0

2 files changed

Lines changed: 133 additions & 7 deletions

File tree

src/node_contextify.cc

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ using v8::Int32;
5858
using v8::Integer;
5959
using v8::Intercepted;
6060
using v8::Isolate;
61+
using v8::Just;
6162
using v8::JustVoid;
6263
using v8::KeyCollectionMode;
6364
using v8::Local;
@@ -475,6 +476,60 @@ bool ContextifyContext::IsStillInitializing(const ContextifyContext* ctx) {
475476
return ctx == nullptr || ctx->context_.IsEmpty();
476477
}
477478

479+
static Maybe<bool> GetOwnPropertyAttributes(Isolate* isolate,
480+
Local<Context> context,
481+
Local<Object> object,
482+
Local<Name> property,
483+
PropertyAttribute* attributes) {
484+
Local<Value> desc_value;
485+
if (!object->GetOwnPropertyDescriptor(context, property).ToLocal(&desc_value))
486+
return Nothing<bool>();
487+
if (desc_value->IsUndefined()) return Just(false);
488+
if (!desc_value->IsObject()) return Nothing<bool>();
489+
490+
Local<Object> desc = desc_value.As<Object>();
491+
int result = PropertyAttribute::None;
492+
493+
Local<String> enumerable_string =
494+
FIXED_ONE_BYTE_STRING(isolate, "enumerable");
495+
Local<Value> enumerable;
496+
if (!desc->Get(context, enumerable_string).ToLocal(&enumerable)) {
497+
return Nothing<bool>();
498+
}
499+
if (!enumerable->IsTrue()) {
500+
result |= PropertyAttribute::DontEnum;
501+
}
502+
503+
Local<String> configurable_string =
504+
FIXED_ONE_BYTE_STRING(isolate, "configurable");
505+
Local<Value> configurable;
506+
if (!desc->Get(context, configurable_string).ToLocal(&configurable)) {
507+
return Nothing<bool>();
508+
}
509+
if (!configurable->IsTrue()) {
510+
result |= PropertyAttribute::DontDelete;
511+
}
512+
513+
Local<String> writable_string = FIXED_ONE_BYTE_STRING(isolate, "writable");
514+
Maybe<bool> maybe_has_writable =
515+
desc->HasOwnProperty(context, writable_string);
516+
if (maybe_has_writable.IsNothing()) {
517+
return Nothing<bool>();
518+
}
519+
if (maybe_has_writable.FromJust()) {
520+
Local<Value> writable;
521+
if (!desc->Get(context, writable_string).ToLocal(&writable)) {
522+
return Nothing<bool>();
523+
}
524+
if (!writable->IsTrue()) {
525+
result |= PropertyAttribute::ReadOnly;
526+
}
527+
}
528+
529+
*attributes = static_cast<PropertyAttribute>(result);
530+
return Just(true);
531+
}
532+
478533
// static
479534
Intercepted ContextifyContext::PropertyQueryCallback(
480535
Local<Name> property, const PropertyCallbackInfo<Integer>& args) {
@@ -490,18 +545,15 @@ Intercepted ContextifyContext::PropertyQueryCallback(
490545

491546
Local<Context> context = ctx->context();
492547
Local<Object> sandbox = ctx->sandbox();
548+
Isolate* isolate = args.GetIsolate();
493549

494550
PropertyAttribute attr;
495551

496-
Maybe<bool> maybe_has = sandbox->HasRealNamedProperty(context, property);
552+
Maybe<bool> maybe_has =
553+
GetOwnPropertyAttributes(isolate, context, sandbox, property, &attr);
497554
if (maybe_has.IsNothing()) {
498-
return Intercepted::kNo;
555+
return Intercepted::kYes;
499556
} else if (maybe_has.FromJust()) {
500-
Maybe<PropertyAttribute> maybe_attr =
501-
sandbox->GetRealNamedPropertyAttributes(context, property);
502-
if (!maybe_attr.To(&attr)) {
503-
return Intercepted::kNo;
504-
}
505557
args.GetReturnValue().Set(attr);
506558
return Intercepted::kYes;
507559
} else {
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('node:assert');
5+
const vm = require('node:vm');
6+
7+
// This test ensures that Proxy-backed vm sandboxes report own properties
8+
// consistently across descriptor and membership queries.
9+
10+
const sandbox = new Proxy({}, {});
11+
const ctx = vm.createContext(sandbox);
12+
13+
vm.runInContext(`
14+
Object.defineProperty(this, 'foo', {
15+
value: 42,
16+
writable: true,
17+
enumerable: true,
18+
configurable: true,
19+
});
20+
Object.defineProperty(this, 'hidden', {
21+
value: 1,
22+
enumerable: false,
23+
configurable: true,
24+
});
25+
Object.defineProperty(this, 'readonly', {
26+
value: 2,
27+
writable: false,
28+
enumerable: true,
29+
configurable: true,
30+
});
31+
`, ctx);
32+
33+
const result = vm.runInContext(`({
34+
value: foo,
35+
descriptor: Object.getOwnPropertyDescriptor(globalThis, 'foo'),
36+
ownNamesIncludes: Object.getOwnPropertyNames(globalThis).includes('foo'),
37+
hasOwnProperty:
38+
Object.prototype.hasOwnProperty.call(globalThis, 'foo'),
39+
objectHasOwn: Object.hasOwn(globalThis, 'foo'),
40+
inGlobalThis: 'foo' in globalThis,
41+
reflectHas: Reflect.has(globalThis, 'foo'),
42+
keysIncludesFoo: Object.keys(globalThis).includes('foo'),
43+
keysIncludesHidden: Object.keys(globalThis).includes('hidden'),
44+
hiddenIsEnumerable:
45+
Object.prototype.propertyIsEnumerable.call(globalThis, 'hidden'),
46+
readonlyDescriptor:
47+
Object.getOwnPropertyDescriptor(globalThis, 'readonly'),
48+
hasOwnToString: Object.hasOwn(globalThis, 'toString'),
49+
toStringInGlobalThis: 'toString' in globalThis,
50+
})`, ctx);
51+
52+
assert.strictEqual(result.value, 42);
53+
assert.deepStrictEqual({ ...result.descriptor }, {
54+
value: 42,
55+
writable: true,
56+
enumerable: true,
57+
configurable: true,
58+
});
59+
assert.strictEqual(result.ownNamesIncludes, true);
60+
assert.strictEqual(result.hasOwnProperty, true);
61+
assert.strictEqual(result.objectHasOwn, true);
62+
assert.strictEqual(result.inGlobalThis, true);
63+
assert.strictEqual(result.reflectHas, true);
64+
assert.strictEqual(result.keysIncludesFoo, true);
65+
assert.strictEqual(result.keysIncludesHidden, false);
66+
assert.strictEqual(result.hiddenIsEnumerable, false);
67+
assert.deepStrictEqual({ ...result.readonlyDescriptor }, {
68+
value: 2,
69+
writable: false,
70+
enumerable: true,
71+
configurable: true,
72+
});
73+
assert.strictEqual(result.hasOwnToString, false);
74+
assert.strictEqual(result.toStringInGlobalThis, true);

0 commit comments

Comments
 (0)