Skip to content

Commit

Permalink
covering the case when HTMLAllCollection reports typeof equal undefined
Browse files Browse the repository at this point in the history
  • Loading branch information
caridy committed Mar 17, 2020
1 parent 3a3212c commit 527ad73
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 68 deletions.
75 changes: 38 additions & 37 deletions src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
apply,
assign,
isUndefined,
isNullish,
isNullOrUndefined,
ObjectCreate,
isFunction,
hasOwnProperty,
Expand Down Expand Up @@ -31,25 +31,13 @@ import {
RawValue,
SecureValue,
RawConstructor,
SecureConstructor,
MembraneBroker,
DistortionMap,
SecureProxy,
ReverseProxy,
ReverseProxyTarget,
} from './types';

// it means it does have identity and should be proxified.
function isProxyTarget(o: RawValue | SecureValue):
o is (RawFunction | RawConstructor | RawObject | SecureFunction | SecureConstructor | SecureObject) {
// hire-wired for the common case
if (isNullish(o)) {
return false;
}
const t = typeof o;
return t === 'object' || t === 'function';
}

interface SecureEnvironmentOptions {
// Base global object used by the raw environment
rawGlobalThis: RawObject & typeof globalThis;
Expand All @@ -72,7 +60,16 @@ export class SecureEnvironment implements MembraneBroker {
throw ErrorCreate(`Missing SecureEnvironmentOptions options bag.`);
}
const { rawGlobalThis, secureGlobalThis, distortionMap } = options;
this.distortionMap = WeakMapCreate(isUndefined(distortionMap) ? [] : distortionMap.entries());
this.distortionMap = WeakMapCreate();
// validating distortion entries
distortionMap?.forEach((value, key) => {
const o = typeof key;
const d = typeof value;
if (o !== d) {
throw ErrorCreate(`Invalid distortion ${value}.`);
}
WeakMapSet(this.distortionMap, key, value);
});
// getting proxy factories ready per environment so we can produce
// the proper errors without leaking instances into a sandbox
const secureEnvFactory = secureGlobalThis.eval(`(${serializedSecureEnvSourceText})`);
Expand Down Expand Up @@ -152,10 +149,6 @@ export class SecureEnvironment implements MembraneBroker {
let currentGetter = () => undefined;
if (isFunction(originalGetter)) {
const originalOrDistortedGetter: () => any = WeakMapGet(this.distortionMap, originalGetter) || originalGetter;
if (!isProxyTarget(originalOrDistortedGetter)) {
// TODO: needs to be resilient, cannot just throw, what should we do instead?
throw ErrorCreate(`Invalid distortion.`);
}
currentGetter = function(this: any): SecureValue {
const value: RawValue = apply(originalOrDistortedGetter, env.getRawValue(this), emptyArray);
return env.getSecureValue(value);
Expand All @@ -178,39 +171,47 @@ export class SecureEnvironment implements MembraneBroker {
if (!isUndefined(secureDescriptor) &&
hasOwnProperty(secureDescriptor, 'configurable') &&
secureDescriptor.configurable === false) {
const securePropertyValue = secureValue[key];
if (isNullOrUndefined(securePropertyValue)) {
continue;
}
const t = typeof securePropertyValue;
// this is the case where the secure env has a descriptor that was supposed to be
// overrule but can't be done because it is a non-configurable. Instead we try to
// fallback to some more advanced gymnastics
if (hasOwnProperty(secureDescriptor, 'value') && isProxyTarget(secureDescriptor.value)) {
const { value: secureDescriptorValue } = secureDescriptor;
if (!WeakMapHas(this.som, secureDescriptorValue)) {
// remapping the value of the secure object graph to the outer realm graph
const { value: rawDescriptorValue } = rawDescriptor;
if (secureValue !== rawDescriptorValue) {
if (this.getRawValue(secureValue) !== rawValue) {
console.error('need remapping: ', key, rawValue, rawDescriptor);
if (hasOwnProperty(secureDescriptor, 'value')) {
// valid proxy target (intentionally ignoring the case of document.all since it is not a value descriptor)
if (t === 'function' || t === 'object') {
if (!WeakMapHas(this.som, securePropertyValue)) {
// remapping the value of the secure object graph to the outer realm graph
const { value: rawDescriptorValue } = rawDescriptor;
if (secureValue !== rawDescriptorValue) {
if (this.getRawValue(secureValue) !== rawValue) {
console.error('need remapping: ', key, rawValue, rawDescriptor);
} else {
// it was already mapped
}
} else {
// it was already mapped
// window.top is the classic example of a descriptor that leaks access to the outer
// window reference, and there is no containment for that case yet.
console.error('leaking: ', key, rawValue, rawDescriptor);
}
} else {
// window.top is the classic example of a descriptor that leaks access to the outer
// window reference, and there is no containment for that case yet.
console.error('leaking: ', key, rawValue, rawDescriptor);
// an example of this is circular window.window ref
console.info('circular: ', key, rawValue, rawDescriptor);
}
} else {
// an example of this is circular window.window ref
console.info('circular: ', key, rawValue, rawDescriptor);
}
} else if (hasOwnProperty(secureDescriptor, 'get')) {
const secureDescriptorValue = secureValue[key];
if (isProxyTarget(secureDescriptorValue)) {
if (secureDescriptorValue === secureValue[key]) {
// internationally ignoring the case of (typeof document.all === 'undefined') because
// it is specified as configurable, you never get one of those exotic objects in this branch
if (t === 'function' || t === 'object') {
if (securePropertyValue === secureValue[key]) {
// this is the case for window.document which is identity preserving getter
// const rawDescriptorValue = rawValue[key];
// this.setRefMapEntries(secureDescriptorValue, rawDescriptorValue);
// this.installDescriptors(secureDescriptorValue, rawDescriptorValue, getOwnPropertyDescriptors(rawDescriptorValue));
console.error('need remapping: ', key, rawValue, rawDescriptor);
if (ReflectIsExtensible(secureDescriptorValue)) {
if (ReflectIsExtensible(securePropertyValue)) {
// remapping proto chain
// ReflectSetPrototypeOf(secureDescriptorValue, this.getSecureValue(ReflectGetPrototypeOf(secureDescriptorValue)));
console.error('needs prototype remapping: ', key, rawValue);
Expand Down
24 changes: 11 additions & 13 deletions src/raw-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
ReflectGet,
ReflectSet,
map,
isNullish,
isNullOrUndefined,
unconstruct,
ownKeys,
ReflectIsExtensible,
Expand All @@ -34,7 +34,6 @@ import {
RawArray,
SecureValue,
MembraneBroker,
SecureObject,
} from './types';

function renameFunction(provider: (...args: any[]) => any, receiver: (...args: any[]) => any) {
Expand All @@ -52,16 +51,6 @@ function renameFunction(provider: (...args: any[]) => any, receiver: (...args: a
}
}

// it means it does have identity and should be proxified.
function isProxyTarget(o: RawValue): o is (SecureFunction | SecureConstructor | SecureObject) {
// hire-wired for the common case
if (isNullish(o)) {
return false;
}
const t = typeof o;
return t === 'object' || t === 'function';
}

const ProxyRevocable = Proxy.revocable;
const ProxyCreate = unconstruct(Proxy);
const { isArray: isArrayOrNotOrThrowForRevoked } = Array;
Expand Down Expand Up @@ -285,6 +274,15 @@ export function reverseProxyFactory(env: MembraneBroker) {
ReflectSetPrototypeOf(ReverseProxyHandler.prototype, null);

function getRawValue(sec: SecureValue): RawValue {
if (isNullOrUndefined(sec)) {
return sec as RawValue;
}
const t = typeof sec;
// internationally ignoring the case of (typeof document.all === 'undefined') because
// in the reserve membrane, you never get one of those exotic objects
if (t === 'function') {
return getRawFunction(sec);
}
let isSecArray = false;
try {
isSecArray = isArrayOrNotOrThrowForRevoked(sec);
Expand All @@ -293,7 +291,7 @@ export function reverseProxyFactory(env: MembraneBroker) {
}
if (isSecArray) {
return getRawArray(sec);
} else if (isProxyTarget(sec)) {
} else if (t === 'object') {
const raw = env.getRawRef(sec);
if (isUndefined(raw)) {
return createReverseProxy(sec);
Expand Down
32 changes: 15 additions & 17 deletions src/secure-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
RawConstructor,
RawFunction,
RawValue,
RawObject,
RawArray,
TargetMeta,
MembraneBroker,
Expand Down Expand Up @@ -110,11 +109,24 @@ export const serializedSecureEnvSourceText = (function secureEnvFactory(rawEnv:
return typeof obj === 'function';
}

function isNullish(obj: any): obj is (null | undefined) {
function isNullOrUndefined(obj: any): obj is (null | undefined) {
return isNull(obj) || isUndefined(obj);
}

function getSecureValue(raw: RawValue): SecureValue {
if (isNullOrUndefined(raw)) {
return raw as SecureValue;
}
const t = typeof raw;
// NOTE: internationally checking for typeof 'undefined' for the case of
// `typeof document.all === 'undefined'`, which is an exotic object with
// a bizarre behavior described here:
// * https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot
// This check cover that case, but doesn't affect other unidefined values
// because those are covered by the previous condition anyways.
if (t === 'function' || t === 'undefined') {
return getSecureFunction(raw);
}
let isRawArray = false;
try {
isRawArray = isArrayOrNotOrThrowForRevoked(raw);
Expand All @@ -124,7 +136,7 @@ export const serializedSecureEnvSourceText = (function secureEnvFactory(rawEnv:
}
if (isRawArray) {
return getSecureArray(raw);
} else if (isProxyTarget(raw)) {
} else if (t === 'object') {
const sec: SecureValue | undefined = WeakMapGet(rom, raw);
if (isUndefined(sec)) {
return createSecureProxy(raw);
Expand Down Expand Up @@ -155,23 +167,9 @@ export const serializedSecureEnvSourceText = (function secureEnvFactory(rawEnv:
}
// if a distortion entry is found, it must be a valid proxy target
const distortedTarget = WeakMapGet(distortionMap, target) as SecureProxyTarget;
if (!isProxyTarget(distortedTarget)) {
// TODO: needs to be resilient, cannot just throw, what should we do instead?
throw ErrorCreate(`Invalid distortion.`);
}
return distortedTarget;
}

// it means it does have identity and should be proxified.
function isProxyTarget(o: RawValue | SecureValue): o is (RawFunction | RawConstructor | RawObject) {
// hire-wired for the common case
if (isNullish(o)) {
return false;
}
const t = typeof o;
return t === 'object' || t === 'function';
}

function renameFunction(rawProvider: (...args: any[]) => any, receiver: (...args: any[]) => any) {
let nameDescriptor: PropertyDescriptor | undefined;
try {
Expand Down
2 changes: 1 addition & 1 deletion src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export function isNull(obj: any): obj is null {
return obj === null;
}

export function isNullish(obj: any): obj is (null | undefined) {
export function isNullOrUndefined(obj: any): obj is (null | undefined) {
return isNull(obj) || isUndefined(obj);
}

Expand Down

0 comments on commit 527ad73

Please sign in to comment.