Briefly demo'd MetaMask/metamask-mobile#8009, a minimally transformed ses@0.18.8 wip that runs on Hermes (latest React Native default JS engine)
here's the omnibus of issues/mods below, while figuring which parts we definitely need (vs can skip/remove in a codemod) cc @erights would appreciate any thoughts
Bundling (Metro, release)
Runtime
Non blocking
Tested across bundled Hermes for RN
Nb: latest minor version tags above, side-by-side with Hermes tarballs downloaded during build
async functions
# ...
metamask-mobile/android/app/build/ASSETS/createBundleProdDebugJsAndAssets/index.android.bundle: error: async functions are unsupported
const loadWithoutErrorAnnotation = async (compartmentPrivateFields, moduleAliases, compartment, moduleSpecifier, pendingJobs, moduleLoads, errors) => {
metamask-mobile/android/app/build/ASSETS/createBundleProdDebugJsAndAssets/index.android.bundle: error: async functions are unsupported
const memoizedLoadWithErrorAnnotation = async (compartmentPrivateFields, moduleAliases, compartment, moduleSpecifier, pendingJobs, moduleLoads, errors) => {
metamask-mobile/android/app/build/ASSETS/createBundleProdDebugJsAndAssets/index.android.bundle: error: async functions are unsupported
const load = async (compartmentPrivateFields, moduleAliases, compartment, moduleSpecifier) => {
mobile/android/app/build/ASSETS/createBundleProdDebugJsAndAssets/index.android.bundle:1233 error: async generators are unsupported
async function* AsyncGeneratorFunctionInstance() {}
# ...
Nb: async function* a() {}; alone won't emit an error
but using/referencing it and beyond const b = a; will
encountered bundling the app with Metro (considering Re.Pack)
# via RN CLI
yarn watch:clean
yarn start:android # react-native run-android --variant=prodDebug
# or via Gradle Wrapper
./gradlew clean # after changes to SES
./gradlew :app:createBundleProdDebugJsAndAssets # bundle only
./gradlew :app:installProdDebug -PreactNativeArchitectures=arm64-v8a # bundle then build for M2
Since Hermes async and await support is In Progress (let alone async generators)
so Hermes doesn't support async arrow functions directly
but we're getting indirect support later via @babel/preset-env
# regenerator-runtime
metamask@7.15.0 /Users/leo/Documents/GitHub/metamask-mobile
├─┬ @babel/runtime@7.23.2
│ └── regenerator-runtime@0.14.0
# ...
# (old name: babel-plugin-transform-async-to-generator)
metamask@7.15.0 /Users/leo/Documents/GitHub/metamask-mobile
├─┬ @babel/preset-env@7.23.2
│ └── @babel/plugin-transform-async-to-generator@7.22.5
└─┬ metro-react-native-babel-preset@0.73.10
└── @babel/plugin-transform-async-to-generator@7.22.5 deduped
# @babel/plugin-transform-arrow-functions
metamask@7.15.0 /Users/leo/Documents/GitHub/metamask-mobile
├─┬ @babel/preset-env@7.23.2
│ └── @babel/plugin-transform-arrow-functions@7.22.5
├─┬ fbjs-scripts@3.0.1
│ └─┬ babel-preset-fbjs@3.4.0
│ └── @babel/plugin-transform-arrow-functions@7.22.5 deduped
├─┬ metro-react-native-babel-preset@0.73.10
│ └── @babel/plugin-transform-arrow-functions@7.22.5 deduped
└─┬ react-native-reanimated@3.1.0
└── @babel/plugin-transform-arrow-functions@7.22.5 deduped
# @babel/plugin-transform-async-generator-functions
└─┬ @babel/preset-env@7.24.4
└── @babel/plugin-transform-async-generator-functions@7.24.3
# @babel/plugin-async-generator-functions
metamask@7.15.0 /Users/leo/Documents/GitHub/metamask-mobile
└── (empty)
nb: metro-react-native-babel-preset@0.77.0 includes these 37 babel plugins
If so, re-write to Promises, otherwise skip parts or remove and refactor, or is there a better solution?
TODO: check refactor from async arrow fns to async fns; check latest bundled Hermes versions of RN
No longer an issue on bundled Hermes for RN 0.71.17+ (i.e. not a custom build from source)
assertDirectEvalAvailable
Since eval is Excluded From Support
tameRegExpConstructor
no RegExp[Symbol.species] descriptor, no stack
ses.cjs#4472: TypeError('no RegExp[Symbol.species] descriptor');
// node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts
// ...
/// <reference lib="es2015.symbol" />
interface SymbolConstructor {
// ...
/**
* A function valued property that is the constructor function that is used to create
* derived objects.
*/
readonly species: unique symbol;
// ...
}
This is because it is Excluded From Support on Hermes
Symbol.species and its interactions with JS library functions
For a good reason it seems, noting both warnings
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/species
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@species
So we likely want this part skipped or tameRegExpConstructor option to skip (plus remaining changes, then upstream)
if( !speciesDesc) {
throw TypeError('no RegExp[Symbol.species] descriptor'); // Thrown on Hermes since excluded from support
}
fixed in
tameSymbolConstructor
property is not configurable, no stack / at [object CallSite]
ses.cjs#9481: defineProperties(SharedSymbol, descs);
We can fix this simply by omitting configurable: true on Hermes
const descs = fromEntries(
arrayMap(originalDescsEntries, ([name, desc]) => [
name,
{ ...desc }, // configurable: true, omitted
]),
);
in order to tame the Symbol constructor (used on constructed compartments),
we attempt to temporarily make all props configurable: true
so that we can whitelist (remove non-standard) props, then harden them (back to non-configurable)
Hermes has 15 Symbol props on all versions (contrasted to e.g. Node with 18)
on metamask-mobile (Android)
when we observe Object.getOwnPropertyDescriptor(originalDescsEntries, name)?.configurable
we get 15 non-true props: 1 false prop (length), 14 undefined
nb: on Hermes v0.12.0 built from scratch
➜ bin ./hermes --version
LLVM (http://llvm.org/):
LLVH version 8.0.0svn
Optimized build
Hermes JavaScript compiler and Virtual Machine.
Hermes release version: 0.12.0
HBC bytecode version: 96
Features:
Debugger
Zip file input
➜ bin ./hermes
>> Object.getOwnPropertyDescriptors(Symbol)
{ length: { value: 0, writable: false, enumerable: false, configurable: true }, name: { value: "Symbol", writable: false, enumerable: false, configurable: true }, prototype: { value: Symbol { [constructor]: [Function Symbol], [description]: [accessor], [toString]: [Function toString], [valueOf]: [Function valueOf], [Symbol(Symbol.toStringTag)]: "Symbol", [Symbol(Symbol.toPrimitive)]: [Function [Symbol.toPrimitive]] }, writable: false, enumerable: false, configurable: false }, for: { value: [Function for], writable: true, enumerable: false, configurable: true }, keyFor: { value: [Function keyFor], writable: true, enumerable: false, configurable: true }, hasInstance: { value: Symbol(Symbol.hasInstance), writable: false, enumerable: false, configurable: false }, iterator: { value: Symbol(Symbol.iterator), writable: false, enumerable: false, configurable: false }, isConcatSpreadable: { value: Symbol(Symbol.isConcatSpreadable), writable: false, enumerable: false, configurable: false }, toPrimitive: { value: Symbol(Symbol.toPrimitive), writable: false, enumerable: false, configurable: false }, toStringTag: { value: Symbol(Symbol.toStringTag), writable: false, enumerable: false, configurable: false }, match: { value: Symbol(Symbol.match), writable: false, enumerable: false, configurable: false }, matchAll: { value: Symbol(Symbol.matchAll), writable: false, enumerable: false, configurable: false }, search: { value: Symbol(Symbol.search), writable: false, enumerable: false, configurable: false }, replace: { value: Symbol(Symbol.replace), writable: false, enumerable: false, configurable: false }, split: { value: Symbol(Symbol.split), writable: false, enumerable: false, configurable: false } }
(i built/ran Hermes from scratch to observe above, since Hermes via eshost-cli on m2 is unsupported via jsvu(mac64arm) and doesn't fetch via esvu(darwin-arm64) and built Hermes via eshost complains hermes: Unknown command line argument '-fenable-tdz' and Hermes playground only prints [object Object] - but thx for demo'ing @gibson042, jealous how it worked so eloquently on your machine :p)
nb: some Hermes Language Features
- excludes:
Symbol.species and its interactions with JS library functions
- excludes:
Symbol.unscopables (Hermes does not support with)
- in progress:
Symbol.prototype.description (it's not fully spec-conformant yet. Symbol().description should be undefined but it's currently '')
- supports: Iteration (with
[Symbol.iterator])
- supports: Symbols (including most well-known Symbols)
nb: our tameSymbolConstructor fn
// https://github.com/endojs/endo/blob/master/packages/ses/src/tame-symbol-constructor.js
/**
* This taming provides a tamed alternative to the original `Symbol` constructor
* that starts off identical, except that all its properties are "temporarily"
* configurable. The original `Symbol` constructor remains unmodified on
* the start compartment's global. The tamed alternative is used as the shared
* `Symbol` constructor on constructed compartments.
*
* Starting these properties as configurable assumes two succeeding phases of
* processing: A whitelisting phase, that
* removes all properties not on the whitelist (which requires them to be
* configurable) and a global hardening step that freezes all primordials,
* returning these properties to their expected non-configurable status.
*
* The ses shim is constructed to eventually enable vetted shims to run between
* repair and global hardening. However, such vetted shims would normally
* run in the start compartment, which continues to use the original unmodified
* `Symbol`, so they should not normally be affected by the temporary
* configurability of these properties.
*
* Note that the spec refers to the global `Symbol` function as the
* ["Symbol Constructor"](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-symbol-constructor)
* even though it has a call behavior (can be called as a function) and does not
* not have a construct behavior (cannot be called with `new`). Accordingly,
* to tame it, we must replace it with a function without a construct
* behavior.
*/
nb: a SymbolTaming lockdown option doesn't exist yet (same with FunctionTaming on tameFunctionConstructors)
solutions
extra
- instead of throwing a RN polyfill error-guard Fatal Error in Metro on Hermes, throw a similar SES TypeError before instead? unless tolerated
- update
The ses shim is constructed to _eventually_ enable vetted shims... 10mo ago now we have vetted shims
- and remaining codebase refs
security considerations pending further thought
getAnonymousIntrinsics
⚠️ Babel is likely transforming SES, check the config to ensure the shim is ignored ⚠️
Conflicting definitions of %InertAsyncFunction%, no stack
ses.cjs#3688: TypeError( `Conflicting definitions of ${name}`);
Not present in
- hermes-engine-cli@0.12.0
- react-native@0.71.17
debugger statements working in Flipper > Hermes Debugger (RN)
Present in
- react-native@0.72.15(metamask-mobile)
debugger statements broken in Flipper > Hermes Debugger (RN)
commenting
throw new TypeError(`Conflicting definitions of ${name}`);
results in SES continuing to lockdown
resulting in a fully functional React Native app
but at what security cost
%InertFunction% // no conflicting definitions
%InertAsyncFunction% // ❌ conflicting definitions
%InertGeneratorFunction% // no conflicting definitions
completePrototypes
lockdown.prototype property not whitelisted, no stack
ses.cjs#3730: TypeError( `${name}.prototype property not whitelisted`)
to repro, uncomment
|
# $HERMES -b test/hermes-smoke-dist.hbc |
whitelistIntrinsics
Unexpected intrinsic intrinsics.isFinite.__proto__ at %FunctionPrototype%, no stack
ses.cjs#3953: TypeError( `Unexpected intrinsic ${path}.__proto__ at ${protoName}`)
isFinite appears to be the first of many intrinsics
No more SES TypeErrors thrown after whitelisting intrinsics
hardenIntrinsics
No errors visible, but the React Native app hangs
__hardenTaming__: 'unsafe' (default safe) fixes the issue, but is not a viable solution
https://github.com/endojs/endo/blob/master/packages/ses/docs/lockdown.md#__hardentaming__-options
The Hermes VM throws
Error running main: (TypeError#1)
TypeError#1: Failed to load module "./index.js" in package "file:///Users/leo/Documents/GitHub/endo/packages/ses/" (1 underlying failures: Error transforming "mjs" source in "file:///Users/leo/Documents/GitHub/endo/packages/ses/index.js": Cannot assign to read only property 'toString' of function 'function (path) {
if (validator.call(path)) {
return fn.apply(this, arguments);
}
}'
at throwAggregateError (packages/ses/src/module-load.js:546:11)
at load (packages/ses/src/module-load.js:594:3)
at async makeBundle (packages/compartment-mapper/src/bundle.js:292:3)
at async writeBundle (packages/ses/scripts/bundle.js:50:18)
at async main (packages/ses/scripts/bundle.js:107:3)
Briefly demo'd MetaMask/metamask-mobile#8009, a minimally transformed ses@0.18.8 wip that runs on Hermes (latest React Native default JS engine)
here's the omnibus of issues/mods below, while figuring which parts we definitely need (vs can skip/remove in a codemod) cc @erights would appreciate any thoughts
Bundling (Metro, release)
error: async functions are unsupportedloadWithoutErrorAnnotationmemoizedLoadWithErrorAnnotationloaddrainQueue(ses@1.5.0)asyncTrampoline(ses@master)error: async generators are unsupportedasync function* AsyncGeneratorFunctionInstance() {}Runtime
// assertDirectEvalAvailable(); // SES TypeErrorevalfor SES (Secure EcmaScript) support facebook/hermes#957withstatement for SES (Secure EcmaScript) support facebook/hermes#1056addIntrinsics(tameRegExpConstructor(regExpTaming)); // SES TypeErroraddIntrinsics(tameSymbolConstructor()); // Metro exception, RN polyfill error guard Fatal ErroraddIntrinsics(getAnonymousIntrinsics()); // SES TypeErrorcompletePrototypes(); // SES TypeErrorwhitelistIntrinsics(intrinsics, markVirtualizedNativeFunction); // SES TypeErrorremoveUnpermittedIntrinsicsremoveUnpermittedIntrinsicson Hermes #2655callerandargumentsspec compliancy facebook/hermes#1582Non blocking
Tested across bundled Hermes for RN
Nb: latest minor version tags above, side-by-side with Hermes tarballs downloaded during build
async functionsNb:
async function* a() {};alone won't emit an errorbut using/referencing it and beyond
const b = a;willencountered bundling the app with Metro (considering Re.Pack)
Since Hermes
asyncandawaitsupport is In Progress (let alone async generators)so Hermes doesn't support async arrow functions directly
but we're getting indirect support later via @babel/preset-env
which lowers them to generator fns or plain fns using regenerator (relies on regenerator-runtime)
nb: metro-react-native-babel-preset@0.77.0 includes these 37 babel plugins
If so, re-write to Promises, otherwise skip parts or remove and refactor, or is there a better solution?
TODO: check refactor from async arrow fns to async fns; check latest bundled Hermes versions of RNNo longer an issue on bundled Hermes for RN 0.71.17+ (i.e. not a custom build from source)
assertDirectEvalAvailable
Since
evalis Excluded From Supportevalfor SES (Secure EcmaScript) support facebook/hermes#957tameRegExpConstructorno RegExp[Symbol.species] descriptor,no stackses.cjs#4472:
TypeError('no RegExp[Symbol.species] descriptor');This is because it is Excluded From Support on Hermes
For a good reason it seems, noting both warnings
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/species
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@species
So we likely want this part skipped or tameRegExpConstructor option to skip (plus remaining changes, then upstream)
fixed in
tameSymbolConstructor
property is not configurable,no stack/at [object CallSite]ses.cjs#9481:
defineProperties(SharedSymbol, descs);We can fix this simply by omitting
configurable: trueon Hermesin order to tame the Symbol constructor (used on constructed compartments),
we attempt to temporarily make all props
configurable: trueso that we can whitelist (remove non-standard) props, then harden them (back to non-configurable)
Hermes has 15
Symbolprops on all versions (contrasted to e.g. Node with 18)on metamask-mobile (Android)
when we observe
Object.getOwnPropertyDescriptor(originalDescsEntries, name)?.configurablewe get 15 non-true props: 1
falseprop (length), 14undefinednb: on Hermes v0.12.0 built from scratch
(i built/ran Hermes from scratch to observe above, since Hermes via eshost-cli on m2 is unsupported via jsvu(mac64arm) and doesn't fetch via esvu(darwin-arm64) and built Hermes via eshost complains
hermes: Unknown command line argument '-fenable-tdz'and Hermes playground only prints[object Object]- but thx for demo'ing @gibson042, jealous how it worked so eloquently on your machine :p)nb: some Hermes Language Features
Symbol.speciesand its interactions with JS library functionsSymbol.unscopables(Hermes does not supportwith)Symbol.prototype.description(it's not fully spec-conformant yet.Symbol().descriptionshould beundefinedbut it's currently'')[Symbol.iterator])nb: our
tameSymbolConstructorfnnb: a
SymbolTaminglockdown option doesn't exist yet (same with FunctionTaming ontameFunctionConstructors)solutions
packages/ses/src/tame-symbol-constructor.js)packages/ses/src/lockdown.js#L276)safe(current implementation)unsafe(skip taming, none at all)extra
The ses shim is constructed to _eventually_ enable vetted shims...10mo ago now we have vetted shimssecurity considerations pending further thought
getAnonymousIntrinsics
Conflicting definitions of %InertAsyncFunction%,no stackses.cjs#3688:
TypeError( `Conflicting definitions of ${name}`);Not present in
debuggerstatements working in Flipper > Hermes Debugger (RN)Present in
debuggerstatements broken in Flipper > Hermes Debugger (RN)commenting
results in SES continuing to lockdown
resulting in a fully functional React Native app
but at what security cost
completePrototypes
lockdown.prototype property not whitelisted,no stackses.cjs#3730:
TypeError( `${name}.prototype property not whitelisted`)to repro, uncomment
endo/packages/ses/scripts/hermes.sh
Line 38 in cdd2dad
whitelistIntrinsics
Unexpected intrinsic intrinsics.isFinite.__proto__ at %FunctionPrototype%,no stackses.cjs#3953:
TypeError( `Unexpected intrinsic ${path}.__proto__ at ${protoName}`)isFiniteappears to be the first of many intrinsicsNo more SES TypeErrors thrown after whitelisting intrinsics
hardenIntrinsics
No errors visible, but the React Native app hangs
__hardenTaming__: 'unsafe'(default safe) fixes the issue, but is not a viable solutionhttps://github.com/endojs/endo/blob/master/packages/ses/docs/lockdown.md#__hardentaming__-options
The Hermes VM throws