Skip to content

Commit

Permalink
lib: save primordials during bootstrap and use it in builtins
Browse files Browse the repository at this point in the history
This patches changes the `safe_globals` internal module into a
script that gets run during bootstrap and saves JavaScript builtins
(primordials) into an object that is available for all other builtin
modules to access lexically later.

PR-URL: #25816
Refs: #18795
Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Gus Caplan <me@gus.host>
  • Loading branch information
joyeecheung authored and targos committed Feb 10, 2019
1 parent f63817f commit 92ca506
Show file tree
Hide file tree
Showing 17 changed files with 185 additions and 105 deletions.
1 change: 1 addition & 0 deletions lib/.eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,4 @@ globals:
DCHECK_LT: false
DCHECK_NE: false
internalBinding: false
primordials: false
1 change: 1 addition & 0 deletions lib/internal/bootstrap/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const cannotBeRequired = [

'internal/test/binding',

'internal/bootstrap/primordials',
'internal/bootstrap/loaders',
'internal/bootstrap/node'
];
Expand Down
76 changes: 26 additions & 50 deletions lib/internal/bootstrap/loaders.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,33 +40,20 @@
'use strict';

// This file is compiled as if it's wrapped in a function with arguments
// passed by node::LoadEnvironment()
// passed by node::RunBootstrapping()
/* global process, getBinding, getLinkedBinding, getInternalBinding */
/* global debugBreak, experimentalModules, exposeInternals */
/* global experimentalModules, exposeInternals, primordials */

if (debugBreak)
debugger; // eslint-disable-line no-debugger

const {
apply: ReflectApply,
deleteProperty: ReflectDeleteProperty,
get: ReflectGet,
getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor,
has: ReflectHas,
set: ReflectSet,
} = Reflect;
const {
prototype: {
hasOwnProperty: ObjectHasOwnProperty,
},
create: ObjectCreate,
defineProperty: ObjectDefineProperty,
keys: ObjectKeys,
} = Object;
Reflect,
Object,
ObjectPrototype,
SafeSet
} = primordials;

// Set up process.moduleLoadList.
const moduleLoadList = [];
ObjectDefineProperty(process, 'moduleLoadList', {
Object.defineProperty(process, 'moduleLoadList', {
value: moduleLoadList,
configurable: true,
enumerable: true,
Expand All @@ -78,7 +65,7 @@ ObjectDefineProperty(process, 'moduleLoadList', {
// that are whitelisted for access via process.binding()... This is used
// to provide a transition path for modules that are being moved over to
// internalBinding.
const internalBindingWhitelist = [
const internalBindingWhitelist = new SafeSet([
'async_wrap',
'buffer',
'cares_wrap',
Expand Down Expand Up @@ -108,20 +95,17 @@ const internalBindingWhitelist = [
'uv',
'v8',
'zlib'
];
// We will use a lazy loaded SafeSet in internalBindingWhitelistHas
// for checking existence in this list.
let internalBindingWhitelistSet;
]);

// Set up process.binding() and process._linkedBinding().
{
const bindingObj = ObjectCreate(null);
const bindingObj = Object.create(null);

process.binding = function binding(module) {
module = String(module);
// Deprecated specific process.binding() modules, but not all, allow
// selective fallback to internalBinding for the deprecated ones.
if (internalBindingWhitelistHas(module)) {
if (internalBindingWhitelist.has(module)) {
return internalBinding(module);
}
let mod = bindingObj[module];
Expand All @@ -144,7 +128,7 @@ let internalBindingWhitelistSet;
// Set up internalBinding() in the closure.
let internalBinding;
{
const bindingObj = ObjectCreate(null);
const bindingObj = Object.create(null);
internalBinding = function internalBinding(module) {
let mod = bindingObj[module];
if (typeof mod !== 'object') {
Expand Down Expand Up @@ -245,8 +229,8 @@ NativeModule.requireWithFallbackInDeps = function(request) {
};

const getOwn = (target, property, receiver) => {
return ReflectApply(ObjectHasOwnProperty, target, [property]) ?
ReflectGet(target, property, receiver) :
return Reflect.apply(ObjectPrototype.hasOwnProperty, target, [property]) ?
Reflect.get(target, property, receiver) :
undefined;
};

Expand All @@ -255,12 +239,12 @@ const getOwn = (target, property, receiver) => {
// as the entire namespace (module.exports) and wrapped in a proxy such
// that APMs and other behavior are still left intact.
NativeModule.prototype.proxifyExports = function() {
this.exportKeys = ObjectKeys(this.exports);
this.exportKeys = Object.keys(this.exports);

const update = (property, value) => {
if (this.reflect !== undefined &&
ReflectApply(ObjectHasOwnProperty,
this.reflect.exports, [property]))
Reflect.apply(ObjectPrototype.hasOwnProperty,
this.reflect.exports, [property]))
this.reflect.exports[property].set(value);
};

Expand All @@ -269,12 +253,12 @@ NativeModule.prototype.proxifyExports = function() {
defineProperty: (target, prop, descriptor) => {
// Use `Object.defineProperty` instead of `Reflect.defineProperty`
// to throw the appropriate error if something goes wrong.
ObjectDefineProperty(target, prop, descriptor);
Object.defineProperty(target, prop, descriptor);
if (typeof descriptor.get === 'function' &&
!ReflectHas(handler, 'get')) {
!Reflect.has(handler, 'get')) {
handler.get = (target, prop, receiver) => {
const value = ReflectGet(target, prop, receiver);
if (ReflectApply(ObjectHasOwnProperty, target, [prop]))
const value = Reflect.get(target, prop, receiver);
if (Reflect.apply(ObjectPrototype.hasOwnProperty, target, [prop]))
update(prop, value);
return value;
};
Expand All @@ -283,15 +267,15 @@ NativeModule.prototype.proxifyExports = function() {
return true;
},
deleteProperty: (target, prop) => {
if (ReflectDeleteProperty(target, prop)) {
if (Reflect.deleteProperty(target, prop)) {
update(prop, undefined);
return true;
}
return false;
},
set: (target, prop, value, receiver) => {
const descriptor = ReflectGetOwnPropertyDescriptor(target, prop);
if (ReflectSet(target, prop, value, receiver)) {
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
if (Reflect.set(target, prop, value, receiver)) {
if (descriptor && typeof descriptor.set === 'function') {
for (const key of this.exportKeys) {
update(key, getOwn(target, key, receiver));
Expand Down Expand Up @@ -319,7 +303,7 @@ NativeModule.prototype.compile = function() {
NativeModule.require;

const fn = compileFunction(id);
fn(this.exports, requireFn, this, process, internalBinding);
fn(this.exports, requireFn, this, process, internalBinding, primordials);

if (experimentalModules && this.canBeRequiredByUsers) {
this.proxifyExports();
Expand All @@ -344,13 +328,5 @@ if (process.env.NODE_V8_COVERAGE) {
}
}

function internalBindingWhitelistHas(name) {
if (!internalBindingWhitelistSet) {
const { SafeSet } = NativeModule.require('internal/safe_globals');
internalBindingWhitelistSet = new SafeSet(internalBindingWhitelist);
}
return internalBindingWhitelistSet.has(name);
}

// This will be passed to internal/bootstrap/node.js.
return loaderExports;
84 changes: 84 additions & 0 deletions lib/internal/bootstrap/primordials.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use strict';

/* global breakAtBootstrap, primordials */

// This file subclasses and stores the JS builtins that come from the VM
// so that Node.js's builtin modules do not need to later look these up from
// the global proxy, which can be mutated by users.

// TODO(joyeecheung): we can restrict access to these globals in builtin
// modules through the JS linter, for example: ban access such as `Object`
// (which falls back to a lookup in the global proxy) in favor of
// `primordials.Object` where `primordials` is a lexical variable passed
// by the native module compiler.

if (breakAtBootstrap) {
debugger; // eslint-disable-line no-debugger
}

function copyProps(src, dest) {
for (const key of Reflect.ownKeys(src)) {
if (!Reflect.getOwnPropertyDescriptor(dest, key)) {
Reflect.defineProperty(
dest,
key,
Reflect.getOwnPropertyDescriptor(src, key));
}
}
}

function makeSafe(unsafe, safe) {
copyProps(unsafe.prototype, safe.prototype);
copyProps(unsafe, safe);
Object.setPrototypeOf(safe.prototype, null);
Object.freeze(safe.prototype);
Object.freeze(safe);
return safe;
}

// Subclass the constructors because we need to use their prototype
// methods later.
primordials.SafeMap = makeSafe(
Map,
class SafeMap extends Map {}
);
primordials.SafeWeakMap = makeSafe(
WeakMap,
class SafeWeakMap extends WeakMap {}
);
primordials.SafeSet = makeSafe(
Set,
class SafeSet extends Set {}
);
primordials.SafePromise = makeSafe(
Promise,
class SafePromise extends Promise {}
);

// Create copies of the namespace objects
[
'JSON',
'Math',
'Reflect'
].forEach((name) => {
const target = primordials[name] = Object.create(null);
copyProps(global[name], target);
});

// Create copies of intrinsic objects
[
'Array',
'Date',
'Function',
'Object',
'RegExp',
'String'
].forEach((name) => {
const target = primordials[name] = Object.create(null);
copyProps(global[name], target);
const proto = primordials[name + 'Prototype'] = Object.create(null);
copyProps(global[name].prototype, proto);
});

Object.setPrototypeOf(primordials, null);
Object.freeze(primordials);
14 changes: 10 additions & 4 deletions lib/internal/error-serdes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

const Buffer = require('buffer').Buffer;
const { serialize, deserialize } = require('v8');
const { SafeSet } = require('internal/safe_globals');
const {
SafeSet,
Object,
ObjectPrototype,
FunctionPrototype,
ArrayPrototype
} = primordials;

const kSerializedError = 0;
const kSerializedObject = 1;
Expand All @@ -14,9 +20,9 @@ const GetOwnPropertyNames = Object.getOwnPropertyNames;
const DefineProperty = Object.defineProperty;
const Assign = Object.assign;
const ObjectPrototypeToString =
Function.prototype.call.bind(Object.prototype.toString);
const ForEach = Function.prototype.call.bind(Array.prototype.forEach);
const Call = Function.prototype.call.bind(Function.prototype.call);
FunctionPrototype.call.bind(ObjectPrototype.toString);
const ForEach = FunctionPrototype.call.bind(ArrayPrototype.forEach);
const Call = FunctionPrototype.call.bind(FunctionPrototype.call);

const errors = {
Error, TypeError, RangeError, URIError, SyntaxError, ReferenceError, EvalError
Expand Down
6 changes: 5 additions & 1 deletion lib/internal/modules/esm/module_job.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
'use strict';

const { ModuleWrap } = internalBinding('module_wrap');
const { SafeSet, SafePromise } = require('internal/safe_globals');
const {
SafeSet,
SafePromise
} = primordials;

const { decorateErrorStack } = require('internal/util');
const assert = require('assert');
const resolvedPromise = SafePromise.resolve();
Expand Down
4 changes: 3 additions & 1 deletion lib/internal/modules/esm/module_map.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use strict';

const ModuleJob = require('internal/modules/esm/module_job');
const { SafeMap } = require('internal/safe_globals');
const {
SafeMap
} = primordials;
const debug = require('util').debuglog('esm');
const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes;
const { validateString } = require('internal/validators');
Expand Down
9 changes: 7 additions & 2 deletions lib/internal/modules/esm/translators.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ const createDynamicModule = require(
'internal/modules/esm/create_dynamic_module');
const fs = require('fs');
const { _makeLong } = require('path');
const { SafeMap } = require('internal/safe_globals');
const {
SafeMap,
JSON,
FunctionPrototype,
StringPrototype
} = primordials;
const { URL } = require('url');
const { debuglog, promisify } = require('util');
const esmLoader = require('internal/process/esm_loader');

const readFileAsync = promisify(fs.readFile);
const readFileSync = fs.readFileSync;
const StringReplace = Function.call.bind(String.prototype.replace);
const StringReplace = FunctionPrototype.call.bind(StringPrototype.replace);
const JsonParse = JSON.parse;

const debug = debuglog('esm');
Expand Down
17 changes: 11 additions & 6 deletions lib/internal/policy/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ const {
} = require('internal/errors').codes;
const debug = require('util').debuglog('policy');
const SRI = require('internal/policy/sri');
const { SafeWeakMap } = require('internal/safe_globals');
const {
SafeWeakMap,
FunctionPrototype,
Object,
RegExpPrototype
} = primordials;
const crypto = require('crypto');
const { Buffer } = require('buffer');
const { URL } = require('url');
const { createHash, timingSafeEqual } = crypto;
const HashUpdate = Function.call.bind(crypto.Hash.prototype.update);
const HashDigest = Function.call.bind(crypto.Hash.prototype.digest);
const BufferEquals = Function.call.bind(Buffer.prototype.equals);
const BufferToString = Function.call.bind(Buffer.prototype.toString);
const RegExpTest = Function.call.bind(RegExp.prototype.test);
const HashUpdate = FunctionPrototype.call.bind(crypto.Hash.prototype.update);
const HashDigest = FunctionPrototype.call.bind(crypto.Hash.prototype.digest);
const BufferEquals = FunctionPrototype.call.bind(Buffer.prototype.equals);
const BufferToString = FunctionPrototype.call.bind(Buffer.prototype.toString);
const RegExpTest = FunctionPrototype.call.bind(RegExpPrototype.test);
const { entries } = Object;
const kIntegrities = new SafeWeakMap();
const kReactions = new SafeWeakMap();
Expand Down
25 changes: 0 additions & 25 deletions lib/internal/safe_globals.js

This file was deleted.

Loading

0 comments on commit 92ca506

Please sign in to comment.