Skip to content

Commit f9bbe90

Browse files
committed
vm: fix property queries for proxy sandboxes
1 parent 619b38e commit f9bbe90

2 files changed

Lines changed: 130 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: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const vm = require('vm');
6+
7+
const sandbox = new Proxy({}, {});
8+
const ctx = vm.createContext(sandbox);
9+
10+
vm.runInContext(`
11+
Object.defineProperty(this, 'foo', {
12+
value: 42,
13+
writable: true,
14+
enumerable: true,
15+
configurable: true,
16+
});
17+
Object.defineProperty(this, 'hidden', {
18+
value: 1,
19+
enumerable: false,
20+
configurable: true,
21+
});
22+
Object.defineProperty(this, 'readonly', {
23+
value: 2,
24+
writable: false,
25+
enumerable: true,
26+
configurable: true,
27+
});
28+
`, ctx);
29+
30+
const result = vm.runInContext(`({
31+
value: foo,
32+
descriptor: Object.getOwnPropertyDescriptor(globalThis, 'foo'),
33+
ownNamesIncludes: Object.getOwnPropertyNames(globalThis).includes('foo'),
34+
hasOwnProperty:
35+
Object.prototype.hasOwnProperty.call(globalThis, 'foo'),
36+
objectHasOwn: Object.hasOwn(globalThis, 'foo'),
37+
inGlobalThis: 'foo' in globalThis,
38+
reflectHas: Reflect.has(globalThis, 'foo'),
39+
keysIncludesFoo: Object.keys(globalThis).includes('foo'),
40+
keysIncludesHidden: Object.keys(globalThis).includes('hidden'),
41+
hiddenIsEnumerable:
42+
Object.prototype.propertyIsEnumerable.call(globalThis, 'hidden'),
43+
readonlyDescriptor:
44+
Object.getOwnPropertyDescriptor(globalThis, 'readonly'),
45+
hasOwnToString: Object.hasOwn(globalThis, 'toString'),
46+
toStringInGlobalThis: 'toString' in globalThis,
47+
})`, ctx);
48+
49+
assert.strictEqual(result.value, 42);
50+
assert.deepStrictEqual({ ...result.descriptor }, {
51+
value: 42,
52+
writable: true,
53+
enumerable: true,
54+
configurable: true,
55+
});
56+
assert.strictEqual(result.ownNamesIncludes, true);
57+
assert.strictEqual(result.hasOwnProperty, true);
58+
assert.strictEqual(result.objectHasOwn, true);
59+
assert.strictEqual(result.inGlobalThis, true);
60+
assert.strictEqual(result.reflectHas, true);
61+
assert.strictEqual(result.keysIncludesFoo, true);
62+
assert.strictEqual(result.keysIncludesHidden, false);
63+
assert.strictEqual(result.hiddenIsEnumerable, false);
64+
assert.deepStrictEqual({ ...result.readonlyDescriptor }, {
65+
value: 2,
66+
writable: false,
67+
enumerable: true,
68+
configurable: true,
69+
});
70+
assert.strictEqual(result.hasOwnToString, false);
71+
assert.strictEqual(result.toStringInGlobalThis, true);

0 commit comments

Comments
 (0)