Skip to content

Commit

Permalink
feat: non-security mode for create-react-scripts compat. (KLUDGE) (#642)
Browse files Browse the repository at this point in the history
* feat: non-security mode for create-react-scripts compat. (KLUDGE)

Co-authored-by: Mark S. Miller <erights@gmail.com>
  • Loading branch information
dckc and erights authored Mar 26, 2021
1 parent 01fabaf commit 6bd9f03
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 4 deletions.
11 changes: 10 additions & 1 deletion packages/ses/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@ User-visible changes in SES:

## Next release

- No changes yet
- Added a new temporary `__allowUnsafeMonkeyPatching__` option to `lockdown`.

Sometimes SES is used where SES's safety is not required. Some libraries
are not compatible with SES because they monkey patch the shared primordials
is ways SES cannot allow. We temporarily introduce this option to enable
some of these libraries to work in, approximately, a SES environment
whose safety was sacrificed in order to allow this monkey patching to
succeed. More at the
[__allowUnsafeMonkeyPatching__ Options](./lockdown-options.md#__allowUnsafeMonkeyPatching__-options)
section of [lockdown-options](./lockdown-options.md).

## Release 0.12.5 (25-Mar-2021)

Expand Down
47 changes: 47 additions & 0 deletions packages/ses/lockdown-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Each option is explained in its own section below.
| `errorTaming` | `'safe'` | `'unsafe'` | `errorInstance.stack` |
| `stackFiltering` | `'concise'` | `'verbose'` | deep stacks signal/noise |
| `overrideTaming` | `'moderate'` | `'min'` or `'severe'` | override mistake antidote |
| `__allowUnsafeMonkeyPatching__` | `'safe'` | `'unsafe'` | run unsafe code unsafely |

## `regExpTaming` Options

Expand Down Expand Up @@ -487,3 +488,49 @@ by our override mitigation.

![overrideTaming: 'severe' vscode inspector display](docs/images/override-taming-star-inspector.png)
</details>

## `__allowUnsafeMonkeyPatching__` Options

Sometimes SES is used where SES's safety is not required. Some libraries
are not compatible with SES because they monkey patch the shared primordials
is ways SES cannot allow. We temporarily introduce this option to enable
some of these libraries to work in, approximately, a SES environment
whose safety was sacrificed in order to allow this monkey patching to
succeed.

With this option set to `'unsafe'`, SES initialization
does not harden the primordials, leaving them in their fully
mutable state. But the rest of SES initialization does happen, including
allowing `harden` to work. Since `harden` is transitively contagious
by inheritance and own property traversal, any use of `harden` on any
object will also happen to harden all primordials reachable from that
object. For example, `harden({})` will harden `Object.prototype`,
`Object`, `Function.prototype`, `Function.prototype.constructor`
(which under SES is not the same as the `Function` constructor), and all
the methods reachable from any of these.

Because of this transitive `hardening`, the kludge enabled by this
option may or may not work for any particular monkey patching library.
React seems to be a library for which this kludge does work, because
React seems to do its monkey patching is ways that either avoid or
precede the freezing of primordial caused by other uses of `harden`.
This option thereby enables React to work under SES and after `lockdown`
in a browser, and should only be used if the compromise of safety on
that browser page is acceptable.

The "`__`" in the option name indicates that this option is temporary.
As we encounter libraries that need this option, such as React, we
[plan to encourage them to be fixed](https://github.com/endojs/endo/issues/576#issuecomment-808562426)
so that they work correctly
under SES after SES initialization. Once enough of these are fixed,
we hope to deprecate and eventually remove this option.

For some libraries the monkey patching they are doing can be recast
as a separate shim that could be vetted to not introduce any violation of SES
safety. This may include all the React problems we're seeing! We do plan to
extend the SES initialization mechanism to be able to run vetted shims
during SES initialization: after repairs and before hardening the primordials.
This environment would be much like the environment created by the `'unsafe'`
setting of this option but without a working `harden`. But we have not yet done
so, and we don't yet know if React's monkey patching could be made into a
separate vetted shim that runs early.
14 changes: 11 additions & 3 deletions packages/ses/src/lockdown-shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { assert, makeAssert } from './error/assert.js';
* consoleTaming?: 'safe' | 'unsafe',
* overrideTaming?: 'min' | 'moderate' | 'severe',
* stackFiltering?: 'concise' | 'verbose',
* __allowUnsafeMonkeyPatching__?: 'safe' | 'unsafe',
* }} LockdownOptions
*/

Expand Down Expand Up @@ -140,6 +141,7 @@ export function repairIntrinsics(
consoleTaming = 'safe',
overrideTaming = 'moderate',
stackFiltering = 'concise',
__allowUnsafeMonkeyPatching__ = 'safe',

...extraOptions
} = options;
Expand Down Expand Up @@ -172,6 +174,7 @@ export function repairIntrinsics(
consoleTaming,
overrideTaming,
stackFiltering,
__allowUnsafeMonkeyPatching__,
};

/**
Expand Down Expand Up @@ -249,11 +252,16 @@ export function repairIntrinsics(

function hardenIntrinsics() {
// Circumvent the override mistake.
// TODO consider moving this to the end of the repair phase, and
// therefore before vetted shims rather than afterwards. It is not
// clear yet which is better.
enablePropertyOverrides(intrinsics, overrideTaming);

// Finally register and optionally freeze all the intrinsics. This
// must be the operation that modifies the intrinsics.
lockdownHarden(intrinsics);
if (__allowUnsafeMonkeyPatching__ !== 'unsafe') {
// Finally register and optionally freeze all the intrinsics. This
// must be the operation that modifies the intrinsics.
lockdownHarden(intrinsics);
}

// Having completed lockdown without failing, the user may now
// call `harden` and expect the object's transitively accessible properties
Expand Down
61 changes: 61 additions & 0 deletions packages/ses/test/test-unsafe-kludge-for-react.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import test from 'ava';
import '../lockdown.js';

lockdown({
__allowUnsafeMonkeyPatching__: 'unsafe',
overrideTaming: 'min',
});

test('Unsafe kludge for monkey patching', t => {
t.false(Object.isFrozen(Object.prototype));

const x = {};
x.toString = () => 'foo';
x.constructor = 'Foo';
t.is(`${x}`, 'foo');
t.is(x.constructor, 'Foo');

harden(x);
// Because harden is tranisitively contagious up inheritance chain,
// hardening x also hardens Object.prototype and other primordials
// reachable from it.
for (const reachable of [
Object.prototype,
Object,
Function.prototype,
Function.prototype.constructor,
Function.prototype.apply,
]) {
t.true(Object.isFrozen(reachable));
}
for (const unreachable of [
Function,
// eslint-disable-next-line no-eval
eval,
globalThis,
Reflect,
Reflect.apply,
]) {
t.false(Object.isFrozen(unreachable));
}

const y = {};
// Under even the 'min' override taming, we still enable
// Object.prototype.toString to be overridden by assignment.
y.toString = () => 'bar';
t.throws(
() => {
// At the 'min' override taming, we do not enable
// Object.prototype.constructor to be overridden by
// assignment. This did not matter before hardening
// x because the override mistake only applies to
// non-writable properties and Object.prototype had
// not yet been frozen.
y.constructor = 'Bar';
},
undefined,
'Override should not be enabled for "constructor".',
);
t.is(`${y}`, 'bar');
t.is(y.constructor, Object);
});

0 comments on commit 6bd9f03

Please sign in to comment.