-
Notifications
You must be signed in to change notification settings - Fork 86
fix(ses): plug implicit NaN side-channel #3153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| --- | ||
| 'ses': major | ||
| '@endo/marshal': patch | ||
| --- | ||
|
|
||
| # Plug NaN Side-channel | ||
|
|
||
| The JavaScript language can leak the bit encoding of a NaN via shared TypedArray views of an common ArrayBuffer. Although the JavaScript language has only one NaN value, the underlying IEEE 754 double-precision floating-point representation has many different bit patterns that represent NaN. This can be exploited as a side-channel to leak information. This actually happens on some platforms such as v8. | ||
|
|
||
| @ChALkeR explains at https://github.com/tc39/ecma262/pull/758#issuecomment-3919093669 that the behavior of this side-channel on v8. At https://junk.rray.org/poc/nani.html he demonstrates it, and it indeed even worse than I expected. | ||
|
|
||
| To plug this side-channel, we make two coordinated changes. | ||
| * We stop listing the `Float*Array` constructors as universal globals. This prevents them from being implicitly endowed to created compartments, because they are not harmless. However, we still keep them on the start compartment (the original global), consider them intrinsics, and still repair and harden them on `lockdown()`. Thus, they can be explicitly endowed to child compartments at the price of enabling code in that compartment to read the side-channel. | ||
| * On `lockdown()`, we repair the `DataView.prototype.setFloat*` methods so that they only write canonical NaNs into the underlying ArrayBuffer. | ||
|
|
||
| The `@endo.marshal` package's `encodePassable` encodings need to obtain the bit representation of floating point values. It had used `Float64Array` for that. However, sometimes the `@endo/marshal` package is evaluated in a created compartment that would now lack that constructor. (This reevaluation typically occurs when bundling bundles in that package.) So instead, `encodePassable` now uses the `DataView` methods which are now safe. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| import { Object, DataView, Reflect, Number } from './commons.js'; | ||
|
|
||
| const { is, defineProperty, entries } = Object; | ||
| const { apply } = Reflect; | ||
|
|
||
| const { prototype: dataViewPrototype } = DataView; | ||
|
|
||
| /** | ||
| * These `FERAL*` methods open up the NaN side-channel on some platforms, | ||
| * like v8. Thus, we need to encapsulate them and replace them with wrappers | ||
| * that canonicaize NaNs. | ||
| */ | ||
| const { | ||
| setFloat16: FERAL_SET_FLOAT16, | ||
| setFloat32: FERAL_SET_FLOAT32, | ||
| setFloat64: FERAL_SET_FLOAT64, | ||
|
|
||
| setUint16, | ||
| setUint32, | ||
| setBigUint64, | ||
| } = dataViewPrototype; | ||
|
|
||
| // See https://webidl.spec.whatwg.org/#js-unrestricted-double which implies | ||
| // that this is the canonical NaN for web standards. | ||
| // Casual googling stongly suggests that this is also the cosmWasm | ||
| // canonical NaN. But I have not yet found an authoritative page stating this. | ||
| const canonicalNaN = 0x7ff8000000000000n; | ||
|
|
||
| // Use method shorthand syntax to be this-sensitive but now be constructable | ||
| // nor have a `prototype` property. | ||
| const methods = { | ||
| /** | ||
| * @param {number} byteOffset | ||
| * @param {number} value | ||
| * @param {boolean} [littleEndian] | ||
| */ | ||
| setFloat16(byteOffset, value, littleEndian = undefined) { | ||
| if (is(value, NaN)) { | ||
| return apply(setUint16, this, [ | ||
| byteOffset, | ||
| Number(canonicalNaN), | ||
| littleEndian, | ||
| ]); | ||
| } else { | ||
| return apply(FERAL_SET_FLOAT16, this, [byteOffset, value, littleEndian]); | ||
| } | ||
| }, | ||
| /** | ||
| * @param {number} byteOffset | ||
| * @param {number} value | ||
| * @param {boolean} [littleEndian] | ||
| */ | ||
| setFloat32(byteOffset, value, littleEndian = undefined) { | ||
| if (is(value, NaN)) { | ||
| return apply(setUint32, this, [ | ||
| byteOffset, | ||
| Number(canonicalNaN), | ||
| littleEndian, | ||
| ]); | ||
| } else { | ||
| return apply(FERAL_SET_FLOAT32, this, [byteOffset, value, littleEndian]); | ||
| } | ||
| }, | ||
| /** | ||
| * @param {number} byteOffset | ||
| * @param {number} value | ||
| * @param {boolean} [littleEndian] | ||
| */ | ||
| setFloat64(byteOffset, value, littleEndian = undefined) { | ||
| if (is(value, NaN)) { | ||
| return apply(setBigUint64, this, [ | ||
| byteOffset, | ||
| canonicalNaN, | ||
| littleEndian, | ||
| ]); | ||
| } else { | ||
| return apply(FERAL_SET_FLOAT64, this, [byteOffset, value, littleEndian]); | ||
| } | ||
| }, | ||
| }; | ||
|
|
||
| /** | ||
| * Replaces the dangerous `setFloat*` methods on `DataView.prototype` | ||
|
gibson042 marked this conversation as resolved.
|
||
| * with safe wrappers that first canonicalize NaNs before calling the | ||
| * original hidden methods. | ||
| * By itself, this does not make us safe against the NaN side channel. | ||
| * Separately, we do not include the `Float*Array` constructors on the | ||
| * list of universal safe globals. Thus, constructed compartments do not | ||
| * get these by default. | ||
| * | ||
| * These replacement `setFloat*` methods canonicalize NaN, but they | ||
| * do not canonicalize `-0` to `0`. If callers wish to do so, they should do | ||
| * it themselves before calling these | ||
| */ | ||
| export const tameNaNSideChannel = () => { | ||
| for (const [name, method] of entries(methods)) { | ||
| defineProperty(dataViewPrototype, name, { | ||
| // Since we're redefining properties that already exist, by omitting the | ||
| // other descriptor attributes here, they are unchanged. | ||
| value: method, | ||
| }); | ||
| } | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import test from 'ava'; | ||
| import '../index.js'; | ||
|
|
||
| // The Float*Arrays are not fully harmless because they open a NaN side-channel | ||
| // on some implementations like v8. However, they are still in the | ||
| // start compartment, the intrinsics, and therefore repaired and frozen. They | ||
| // are not endowed to constructed compartments by default, but can be | ||
| // explicitly endowed. | ||
| // The Int*Arrays remain fully harmless and universal. | ||
| // | ||
| test('float arrays in compartments', t => { | ||
| t.false(Object.isFrozen(Float64Array)); | ||
| t.false(Object.isFrozen(Int32Array)); | ||
| lockdown(); | ||
| t.true(Object.isFrozen(Float64Array)); | ||
| t.true(Object.isFrozen(Int32Array)); | ||
|
|
||
| const c1 = new Compartment(); | ||
| const c2 = new Compartment({ | ||
| __options__: true, | ||
| globals: { Float64Array }, | ||
| }); | ||
|
|
||
| t.false('Float64Array' in c1.globalThis); | ||
| t.true('Int32Array' in c1.globalThis); | ||
| t.true('Float64Array' in c2.globalThis); | ||
|
|
||
| t.is(c1.evaluate('Float64Array'), undefined); | ||
| t.is(c1.evaluate('Int32Array'), Int32Array); | ||
| t.is(c2.evaluate('Float64Array'), Float64Array); | ||
|
|
||
| t.throws( | ||
| () => { | ||
| c1.evaluate(`new Float64Array()`); | ||
| }, | ||
| { | ||
| message: 'Float64Array is not a constructor', | ||
| }, | ||
| ); | ||
| t.true(c1.evaluate('new Int32Array()') instanceof Int32Array); | ||
| t.true(c2.evaluate('new Float64Array()') instanceof Float64Array); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.