Normative: Allow NaN values to be *optionally* canonicalized#758
Normative: Allow NaN values to be *optionally* canonicalized#758littledan wants to merge 1 commit into
Conversation
|
@allenwb does this seem fine to you? (Just checking as I think you were involved in the discussions closely at the last meeting). |
|
What do we need from here to decide whether to land this patch? @jfbastien has clarified that, even if this patch lands, we won't really have a full description of what happens with NaN values in practice. This is because processors sometimes change the NaN bit pattern later, e.g., in MOV instructions, in a way that JS implementations which don't canonicalize are probably not guarding against. Even as I'd personally prefer that we leave the NaN bit pattern completely unspecified, I think this patch still brings us a little bit closer to reality and would be helpful to land. |
| 1. Set _rawBytes_ to a List containing the 4 bytes that are the result of converting _value_ to IEEE 754-2008 binary32 format using “Round to nearest, ties to even” rounding mode. If _isLittleEndian_ is *false*, the bytes are arranged in big endian order. Otherwise, the bytes are arranged in little endian order. If _value_ is *NaN*, _rawValue_ may be set to any implementation chosen IEEE 754-2008 binary32 format Not-a-Number encoding. An implementation must always choose the same encoding for each implementation distinguishable *NaN* value. | ||
| 1. Else if _type_ is `"Float64"`, then | ||
| 1. Set _rawBytes_ to a List containing the 8 bytes that are the IEEE 754-2008 binary64 format encoding of _value_. If _isLittleEndian_ is *false*, the bytes are arranged in big endian order. Otherwise, the bytes are arranged in little endian order. If _value_ is *NaN*, _rawValue_ may be set to any implementation chosen IEEE 754-2008 binary64 format Not-a-Number encoding. An implementation must always choose the same encoding for each implementation distinguishable *NaN* value. | ||
| 1. Set _rawBytes_ to a List containing the 8 bytes that are the IEEE 754-2008 binary64 format encoding of _value_. If _isLittleEndian_ is *false*, the bytes are arranged in big endian order. Otherwise, the bytes are arranged in little endian order. If _value_ is *NaN*, _rawValue_ may be set to any implementation chosen IEEE 754-2008 binary64 format Not-a-Number encoding. An implementation must always choose either the same encoding for each implementation distinguishable *NaN* value, or an implementation-defined canonical value. |
There was a problem hiding this comment.
I see that this change is only being made to the Float64 branch, should it also be applied to the Float32 branch just above?
allenwb
left a comment
There was a problem hiding this comment.
A agree with Rick. It should also apply to the Float32 case
This patch legalizes V8's occasional canonicalization of NaNs by changing SetValueInBuffer to *either* a particular value for the implementation-distinguishable NaN value *or* an implementation-defined canonical value. This semantic change reached consensus at the November 2016 TC39 meeting. Closes tc39#635
allenwb
left a comment
There was a problem hiding this comment.
I agree with Rick.
In the Float32 case a Number value which may be a NaN is converted to a Float32 so a 32-bit NaN needs to be created from the 64-bing NaN. The bit encoding of the 32-bit NaN is observable in the buffer so we want the choice of encoding to be consistent within an implementaiton, just like for Float32.
| <emu-alg> | ||
| 1. If _type_ is `"Float32"`, then | ||
| 1. Let _rawBytes_ be a List containing the 4 bytes that are the result of converting _value_ to IEEE 754-2008 binary32 format using “Round to nearest, ties to even” rounding mode. If _isLittleEndian_ is *false*, the bytes are arranged in big endian order. Otherwise, the bytes are arranged in little endian order. If _value_ is *NaN*, _rawBytes_ may be set to any implementation chosen IEEE 754-2008 binary32 format Not-a-Number encoding. An implementation must always choose the same encoding for each implementation distinguishable *NaN* value. | ||
| 1. Let _rawBytes_ be a List containing the 4 bytes that are the result of converting _value_ to IEEE 754-2008 binary32 format using “Round to nearest, ties to even” rounding mode. If _isLittleEndian_ is *false*, the bytes are arranged in big endian order. Otherwise, the bytes are arranged in little endian order. If _value_ is *NaN*, _rawBytes_ may be set to any implementation chosen IEEE 754-2008 binary32 format Not-a-Number encoding. An implementation must always choose the same encoding for each implementation distinguishable *NaN* value, or an implementation-defined canonical value. |
|
Thanks for the reviews everyone! |
|
@littledan would you mind rebasing this PR, and summarizing the current status? I believe it has consensus, and i'm not sure if it still needs tests. @rwaldron can you rereview after the PR is rebased? |
|
Well, I would not feel comfortable making the change suggested above, to give more guarantees to Float32, given that this is unlikely to correspond to actual implementations. I would rather move this patch to removing any guarantees about how floats are canonicalized, given the information from @jfbastien, about how this is the implementation reality and is unlikely to change. @waldemarhorwat encouraged this path as well. @erights, would you be OK with this change? |
|
Yes, this change looks good to me. |
|
@erights To be concrete, I'm talking about, not this PR, but instead a PR to say that there's no stability for NaN (since this is the implementation reality). |
Thanks for clarifying. I did not know that. Link to other PR? |
3d0c24c to
7a79833
Compare
|
It was suggested in Matrix that the entire language about choosing the same NaN when serialising multiple times should be removed as spec fiction. Perhaps this PR could be repurposed for that? |
While hour12 is either true or false, let hourCycle to be either 'h12' or 'h23' but not 'h11' nor 'h24'. The current logic is not reasonable. We see no region in the CLDR use h11 nor h24 hour cycle as default. While we set a hour12: true on a 24 hour system region (which use 0:00 - 23:59), we should set to h12 instead of h11 Do not perform the starnge logic of setting default hourCycle based on both hour12 and the defaultHourCycle while hour12 is true or false. Instead, only depends on hour12 to set it to h12 or h23. Address tc39/ecma402#402 Co-authored-by: Shane F. Carr <sffc@google.com> Co-authored-by: Ujjwal Sharma <ryzokuken@igalia.com> Co-authored-by: André Bargull <andre.bargull@gmail.com>
|
in v8, the observable behavior is seemingly random between calls there |
Why? Such observable dynamic non-determinism opens side-channels even when when observers cannot measure duration. |
|
@erights that's how v8 behaves And, reading the proposed spec change now, I assume that standarizing that was the intention of this PR And I'm not sure that that's worded in the spec clearly enough? const n = new Float64Array(new Uint8Array(8).fill(0xff).buffer)[0]
;[
NaN, NaN, NaN, NaN, NaN, NaN,
n, n, n, n, n, n, n,
].forEach((x, i) => {
var str = new Uint8Array(new Float64Array([x]).buffer).toString()
console.log(i, str)
})prints: Note the value on 9 It does return "either a particular value for the implementation-distinguishable NaN value or an implementation-defined canonical value". Which follows the proposed spec change. The nuance is that it does that in a non-deterministic way
This can be misinterpreted in a way that one would think that it always chooses the same encoding or always returns an implementation-defined canonical value. While what this actually means is that behavior can be either of that at any point. |
|
The intention of our 2015 Munich plenary discussion, from what I recall, is essentially that "outside of Typed Arrays, there is one distinguishable NaN in the language. Typed Arrays can expose bit patterns in an implementation-defined way - meaning, there is no consistency expected or required" |
|
@ljharb that makes sense, but non-determinism could be indeed a side-channel e.g. Spectre was announced in 2018, after that discussion 🙃 |
Why does v8 behave that way? |
I'm unsure i'm just a passerby here sorry. What I wanted to bring the attention to is that this spec change explicitly allows that, and that allowing that non-determinism seems like the intention1 of this change (which was reviewed, even though, pre-Spectre). And that the wording around that is not very obvious Footnotes
|
Perhaps the answer would be "because that was the consensus of the discussion back in 2015"? Also "because tc39/test262#1476 landed"? |
|
What I think I remember from those old times: Mozilla/FF/SpiderMonkey used NaN-boxing, and so always canonicalized genuine NaNs. I don't know if it was the same canonical NaN on little endian vs big endian architectures. Google was not (and I assume is not) boxing their NaNs, and was not willing to pay in performance to do so, when the perf cost would be significant (which I believe) and the benefit is so small "No one would care." I remember resisting a bit but giving up in the face of this genuinely compelling argument from Google. However, I still tried to find some limitation we could agree on that would limit observable dynamic side channels. What I should have anticipated is that if the difference is too hard to test, no one would test it and implementers would proceed to violate it with no feedback. I do not remember even thinking about how to write test262 tests. In the era before Spectre and Meltdown, I am sure most of my fears about side channels fell on deaf ears. Again, my memories are terribly stale. I am often inaccurate of such time gaps. |
|
Glad to see all that test262 attention to these issues! I did not remember that. |
There is (usually) a difference between implementation-defined and undefined, and what's happening is more like "undefined". v8 does not define a consistent behavior for this. Upd: I see the definition though:
It's unfortunate that the spec doesn't put a difference between consistent and unknown-potentially-non-deterministic non-defined-in-spec behavior (like e.g. C++ spec does for |
|
"implementation-defined" means not spec defined. |
That's because we don't need to; there's no undefined behaviour in JS. There's plenty of places where implementations are given the choice to do something or not, or are given a choice of values to produce, or are given the freedom to approximate arithmetic results, but they are never given the opportunity to throw up their hands and do anything they want. If you have a specific suggestion for a place where you think that is unclear, I'd love to hear it.
It's expected that conformant implementations would provide a description of all the choices their implementation makes in these positions. In practice, most implementations don't. Also in practice, most implementations (and their users) don't actually care about strict conformance. The exact text in
Based on your observations, Chrome's behaviour does not conform with the latter part of that requirement. |
This (#758, where we are now) is a PR aimed to change that exact wording you cited and legalize Chrome behavior in spec. And that is actually undefined non-deterministic behavior.
What exactly in the spec would forbid non-determinism there once this PR is landed?
Compare ECMAScript implementation-defined to C++ implementation-defined and C++ unspecified. |
|
V8 does not guarantee a stable NaN pattern. E.g., we use different patterns for different purposes. I checked the latest discussion notes on the topic I don't think that the idea was to request a particular NaN pattern but instead allow any NaN pattern to occur. Post-spectre we abandoned the idea of in-process read isolation. Hence there is no need to canonicalize from that pov either. |
Well, it's definitely a side channel in Chrome It can track gc and communicate over the side-channel cross-origin (in the same process), bypass iframe storage partitioning from different sites, detect devtools, etc. |
|
Thanks @ChALkeR , you've done what I never figured out how to do: demonstrate that it is a real side channel that should be concerning. Thanks for pointing out that it is
After Meltdown and Spectre, it has been easy to raise the alarm about side channels that can be read by those who can somehow measure duration. It has been harder to get people to appreciate two bracketing issues:
See https://papers.agoric.com/taxonomy-of-security-issues/ GC observability is another timing-independent side channel. This is why we so carefully distinguished WeakMap/WeakSet (spec determinism) from WeakRef/FinalizationRegistry (observable gc decisions). And why we then tried to severely reduce the magnitude of that side channel (by making gc within a turn hard to observe, and by allowing the omission of the It is also why we originally proposed that a WeakRef in realm A holding a target from distinct realm B act as a strong reference. This would severely limit the magnitude of the inter-realm gc-observation side channel. Alas, not all engines already had the bookkeeping needed to distinguish realm-of-origin. Implementors were understandably reluctant to consider pervasive extra bookkeeping for an obscure corner case. |
SES directly contradicts both Node.js security model and (as it turns out) V8 security model too I'd be happy to chat about that before posting a public rant about how it doesn't work 🙃 (which is smth I've been postponing for a while now) I have demos Feel free to DM me in OpenJSF Slack about that. |
|
For context, chromium issue which works as intended: https://issues.chromium.org/issues/485920246 |
|
@ChALkeR , how did you come up with the exploits at https://junk.rray.org/poc/nani.html ? Reliably reproducible demos of NaN side channels! Several of us have looked, and it seems like black magic to discover these. Any advice you can offer for other to learn from? |
|
In light of @ChALkeR 's attack, I added back the "needs consensus" label. I understand why Chromium classifies this attack as "works as intended". But I do not think that tc39 should effectively do so itself. |
Closes: #XXXX Refs: #XXXX - [x] TODO cite Nikita's NaN side-channel demonstration. ## Description 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 tc39/ecma262#758 (comment) 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. ### Security Considerations The point. This NaN side-channel in ses had been implicit, easily forgotten, and hard to plug in user code. ### Scaling Considerations Not really. ### Documentation Considerations Somewhere we need to document that the `Float*Array` constructors are not available in created compartments by default, and why. ### Testing Considerations New tests included. One test newly skipped because it always hangs for me locally, which is annoying. ***Reviewers***, feel free to ask me to unskip it. ### Compatibility Considerations The absence of the `Float*Array` constructors in created compartments is potentially a compat break. In fact our need to repair `encodePassable` demonstrates such a compat break. ### Upgrade Considerations none.
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to master, this PR will be updated. # Releases ## @endo/ocapn@1.0.0 ### Major Changes - [#3183](#3183) [`279c0c4`](279c0c4) Thanks [@kumavis](https://github.com/kumavis)! - Initial public release of `@endo/ocapn`. The package is no longer private and is now published to npm. Tested against the python test suite from 2026-01-06 <https://github.com/ocapn/ocapn-test-suite/commits/f0273f21c5ee05a28785b51c231535124f28bca9> ### Minor Changes - [#3172](#3172) [`6405b36`](6405b36) Thanks [@turadg](https://github.com/turadg)! - Parameterize CapTP slot types and improve TypeScript 6 conformance across the OCapN client surface. Compile-time type changes only; no runtime behavior changes. ### Patch Changes - Updated dependencies \[[`f65b000`](f65b000), [`d1d9625`](d1d9625), [`88bc2b9`](88bc2b9), [`e619205`](e619205), [`43165e5`](43165e5), [`6ada52b`](6ada52b)]: - @endo/eventual-send@1.5.0 - @endo/promise-kit@1.2.1 - @endo/pass-style@1.8.0 - @endo/marshal@1.9.1 - @endo/harden@1.1.0 - @endo/nat@5.2.0 ## ses@2.0.0 ### Major Changes - [#3153](#3153) [`e619205`](e619205) Thanks [@erights](https://github.com/erights)! - # 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 <tc39/ecma262#758 (comment)> 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. ### Minor Changes - [#3129](#3129) [`a675d8e`](a675d8e) Thanks [@erights](https://github.com/erights)! - `overrideTaming: 'moderate'` includes `overrideTaming: 'min'`. Previously `overrideTaming: 'min'` correctly enabled `Iterator.prototype.constructor` to be overridden by assignment, but due to an oversight, `overrideTaming: 'moderate'` did not. Now it does. To make such mistakes less likely, this PR also adopts a style where all records within larger enablements triple-dot the corresponding record from a smaller enablement, if present. ## @endo/bundle-source@4.3.0 ### Minor Changes - [#3180](#3180) [`7f7ae8e`](7f7ae8e) Thanks [@turadg](https://github.com/turadg)! - `BundleCache.load()` is now generic on the `format` option: - Omitted (default) → `Promise<BundleSourceResult<'endoZipBase64'>>` - Literal format → `Promise<BundleSourceResult<format>>` - Runtime-typed `ModuleFormat` → `Promise<BundleSourceResult<ModuleFormat>>` Previously `load()` returned `Promise<unknown>`, requiring callers to assert the bundle shape. ### Patch Changes - Updated dependencies \[[`154102b`](154102b), [`2b674ca`](2b674ca), [`d1d9625`](d1d9625), [`b4820dc`](b4820dc), [`acbacba`](acbacba), [`cdb6eae`](cdb6eae), [`6ada52b`](6ada52b), [`6ad084a`](6ad084a), [`1cd1246`](1cd1246)]: - @endo/compartment-mapper@2.1.0 - @endo/promise-kit@1.2.1 - @endo/harden@1.1.0 ## @endo/common@1.4.0 ### Minor Changes - [#3172](#3172) [`98c89b7`](98c89b7) Thanks [@turadg](https://github.com/turadg)! - Add `objectExtendEach` helper for merging a sequence of objects into an accumulator, with precise TypeScript inference of the resulting intersection type. ### Patch Changes - Updated dependencies \[[`f65b000`](f65b000), [`d1d9625`](d1d9625)]: - @endo/eventual-send@1.5.0 - @endo/promise-kit@1.2.1 - @endo/errors@1.3.1 - @endo/harden@1.1.0 ## @endo/compartment-mapper@2.1.0 ### Minor Changes - [#3132](#3132) [`b4820dc`](b4820dc) Thanks [@boneskull](https://github.com/boneskull)! - Expose `_redundantPreloadHook` option in `captureFromMap()`, which will be called for each item in the `_preload` array that was already indirectly loaded via the entry `Compartment`. Fixes a bug in the type of `_preload` option, which now allows for mixed arrays. Fixes a bug in the preloader, which was not exhaustively checking if a non-entry module was already loaded via the entry `Compartment`. - [#3048](#3048) [`6ad084a`](6ad084a) Thanks [@kriskowal](https://github.com/kriskowal)! - Add support for Node.js subpath pattern replacement in `package.json` `exports` and `imports` fields. Patterns like `"./features/*.js": "./src/features/*.js"` and `"#internal/*.js": "./lib/*.js"` are now resolved at link time using prefix/suffix string matching with specificity ordering. Null-target patterns exclude matching specifiers. Conditional pattern values are resolved through the standard condition-matching rules. Patterns are expanded to concrete module entries during archiving. ### Patch Changes - [#3111](#3111) [`154102b`](154102b) Thanks [@boneskull](https://github.com/boneskull)! - Fix type of `PackageDataHook.packageData` which now correctly allows `$root$` as a key. - [#3182](#3182) [`2b674ca`](2b674ca) Thanks [@kriskowal](https://github.com/kriskowal)! - Cull underscore-prefixed internal properties (like `__createdBy`) from serialized compartment maps in archives. The compartment map validator now also ignores underscore-prefixed properties when checking for extraneous fields. - [#3173](#3173) [`acbacba`](acbacba) Thanks [@boneskull](https://github.com/boneskull)! - Fixes potential issue wherein a canonical name may be computed incorrectly. Includes performance improvements. - [#3157](#3157) [`cdb6eae`](cdb6eae) Thanks [@boneskull](https://github.com/boneskull)! - Dramatically improve performance of canonical name (shortest path) computation in `mapNodeModules()`. - [#3127](#3127) [`6ada52b`](6ada52b) Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime dependencies from package manifests. - [#3115](#3115) [`1cd1246`](1cd1246) Thanks [@boneskull](https://github.com/boneskull)! - Remove unused "error" `ModuleSourceHookModuleSource` type. - Updated dependencies \[[`e619205`](e619205), [`6ada52b`](6ada52b), [`a675d8e`](a675d8e)]: - ses@2.0.0 - @endo/module-source@1.4.1 ## @endo/eventual-send@1.5.0 ### Minor Changes - [#3172](#3172) [`f65b000`](f65b000) Thanks [@turadg](https://github.com/turadg)! - Improve `E()` type inference and publicly export method-projection helpers. - `RemoteFunctions`, `PickCallable`, and `ECallableOrMethods` now short-circuit on `any`, preventing `E(anyValue)` from collapsing to an unusable type. - `EMethods`, `EGetters`, and related helpers are now part of the public type surface, so downstream packages can name the projected shapes `E()` produces. Compile-time type changes only; no runtime behavior changes. ### Patch Changes - Updated dependencies \[]: - @endo/harden@1.1.0 ## @endo/exo@1.7.0 ### Minor Changes - [#3172](#3172) [`88bc2b9`](88bc2b9) Thanks [@turadg](https://github.com/turadg)! - Improve TypeScript inference for patterns, exo, and pass-style. These are compile-time type changes only; no runtime behavior changes. - **pass-style**: `CopyArray<T>` is now `readonly T[]` so readonly tuples (e.g. `readonly ['ibc']`) satisfy `Passable`. Backward-compatible because `T[]` still extends `readonly T[]`. - **patterns**: `M.remotable()` defaults to `any` (matching `M.promise()`), so unparameterized remotables are assignable to concrete remotable typedefs. The parameterized form `M.remotable<typeof SomeInterfaceGuard>()` still yields precise inference. - **patterns**: `TFRemotable` returns `any` (not `Payload`) for non-`InterfaceGuard` arguments. - **patterns**: `TFOr` handles array-of-patterns and falls back through `TFAnd`; `M.undefined()` maps to `void`. - **patterns**: `TFOptionalTuple` emits truly optional elements; `M.promise()` maps to `PromiseLike`. - **patterns**: `TFSplitRecord` handles the empty-rest case correctly. - **patterns**: `TFRestArgs` unwraps array patterns. - **patterns**: `TypeFromArgGuard` discriminates by `toStringTag`, not structural shape. - **patterns**: `MatcherOf` payload is preserved through `InterfaceGuard`. - **patterns**: new `CastedPattern<T>` for unchecked type assertions in pattern position. - **exo**: `defineExoClass`, `defineExoClassKit`, and `makeExo` no longer intersect facet constraints with `& Methods`. The previous constraint collapsed specific facet keys into the `string | number | symbol` index signature, making `FilteredKeys` return `never` and erasing facet method inference (`Pick<X, never> = {}`). - **exo**: `Guarded<M, G>` is now structurally compatible across `G`, and the kit `F` constraint is widened. - **exo**: `defineExoClassKit` preserves facet inference when no guard is supplied. TypeScript consumers that were working around the previous inference gaps with casts may be able to remove those casts. Downstream code that depended on the narrower `CopyArray<T> = T[]` or the previous `M.remotable()` default may need minor adjustments. - [#3133](#3133) [`9111b4e`](9111b4e) Thanks [@turadg](https://github.com/turadg)! - feat: infer TypeScript types from pattern guards - `TypeFromPattern<P>` — infer static types from any pattern matcher - `TypeFromMethodGuard<G>` — infer function signatures from `M.call()` / `M.callWhen()` guards - `TypeFromInterfaceGuard<G>` — infer method records from interface guard definitions - `M.remotable<typeof Guard>()` — facet-isolated return types in exo kits - `M.infer<typeof pattern>` — namespace shorthand analogous to `z.infer` - `matches` and `mustMatch` now narrow the specimen type via type predicates - `makeExo`, `defineExoClass`, and `defineExoClassKit` enforce method signatures against guards at compile time These are compile-time type changes only; there are no runtime behavioral changes. Existing TypeScript consumers may see new type errors where method signatures diverge from their guards. ### Patch Changes - Updated dependencies \[[`8195a5a`](8195a5a), [`98c89b7`](98c89b7), [`f65b000`](f65b000), [`88bc2b9`](88bc2b9), [`9111b4e`](9111b4e), [`43165e5`](43165e5), [`df84eea`](df84eea), [`6ada52b`](6ada52b)]: - @endo/patterns@1.9.0 - @endo/common@1.4.0 - @endo/eventual-send@1.5.0 - @endo/pass-style@1.8.0 - @endo/errors@1.3.1 - @endo/far@1.1.14 - @endo/harden@1.1.0 ## @endo/pass-style@1.8.0 ### Minor Changes - [#3172](#3172) [`88bc2b9`](88bc2b9) Thanks [@turadg](https://github.com/turadg)! - Improve TypeScript inference for patterns, exo, and pass-style. These are compile-time type changes only; no runtime behavior changes. - **pass-style**: `CopyArray<T>` is now `readonly T[]` so readonly tuples (e.g. `readonly ['ibc']`) satisfy `Passable`. Backward-compatible because `T[]` still extends `readonly T[]`. - **patterns**: `M.remotable()` defaults to `any` (matching `M.promise()`), so unparameterized remotables are assignable to concrete remotable typedefs. The parameterized form `M.remotable<typeof SomeInterfaceGuard>()` still yields precise inference. - **patterns**: `TFRemotable` returns `any` (not `Payload`) for non-`InterfaceGuard` arguments. - **patterns**: `TFOr` handles array-of-patterns and falls back through `TFAnd`; `M.undefined()` maps to `void`. - **patterns**: `TFOptionalTuple` emits truly optional elements; `M.promise()` maps to `PromiseLike`. - **patterns**: `TFSplitRecord` handles the empty-rest case correctly. - **patterns**: `TFRestArgs` unwraps array patterns. - **patterns**: `TypeFromArgGuard` discriminates by `toStringTag`, not structural shape. - **patterns**: `MatcherOf` payload is preserved through `InterfaceGuard`. - **patterns**: new `CastedPattern<T>` for unchecked type assertions in pattern position. - **exo**: `defineExoClass`, `defineExoClassKit`, and `makeExo` no longer intersect facet constraints with `& Methods`. The previous constraint collapsed specific facet keys into the `string | number | symbol` index signature, making `FilteredKeys` return `never` and erasing facet method inference (`Pick<X, never> = {}`). - **exo**: `Guarded<M, G>` is now structurally compatible across `G`, and the kit `F` constraint is widened. - **exo**: `defineExoClassKit` preserves facet inference when no guard is supplied. TypeScript consumers that were working around the previous inference gaps with casts may be able to remove those casts. Downstream code that depended on the narrower `CopyArray<T> = T[]` or the previous `M.remotable()` default may need minor adjustments. - [#3184](#3184) [`43165e5`](43165e5) Thanks [@turadg](https://github.com/turadg)! - Unblock TypeScript declaration emit in downstream packages that structurally expose `PassStyled`/`Container` types. Compile-time type changes only; no runtime behavior changes. - `PASS_STYLE` is now typed as the string-literal `'Symbol(passStyle)'` rather than `unique symbol`. The runtime value is unchanged (still `Symbol.for('passStyle')`), and computed-key indexing like `obj[PASS_STYLE]` continues to work because JS computed keys accept any value. This removes TS4023 / TS9006 errors in consumers whose inferred types structurally contain `[PASS_STYLE]` (via `PassStyled`, `ExtractStyle`, object spread of a `PassStyled`, etc.). A `unique symbol` is only nameable via its original declaration module, which consumers have no reason to import; a string-literal type has no such nameability requirement. - `CopyArrayInterface`, `CopyRecordInterface`, and `CopyTaggedInterface` are now exported, so downstream `.d.ts` emit can name them when they appear through structural expansion of `Passable`/`Container`. - The `PassStyleOf` array overload is widened from `(p: any[]) => 'copyArray'` to `(p: readonly any[]) => 'copyArray'`, so `as const` tuples and `readonly T[]` values classify as `'copyArray'`. This aligns the classifier with `CopyArray<T>`, which is already `readonly T[]`. Backward-compatible because `T[]` still extends `readonly T[]`. Obviates the `@endo/pass-style` patch that agoric-sdk has been carrying in `.yarn/patches/`. TypeScript consumers that relied on `typeof PASS_STYLE` being `unique symbol` (e.g. annotating a value as `symbol` from `PASS_STYLE`) will need minor adjustments — widen the annotation to `symbol | string`, or cast via `unknown`. ### Patch Changes - [#3127](#3127) [`6ada52b`](6ada52b) Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime dependencies from package manifests. - Updated dependencies \[[`98c89b7`](98c89b7), [`f65b000`](f65b000), [`d1d9625`](d1d9625)]: - @endo/common@1.4.0 - @endo/eventual-send@1.5.0 - @endo/promise-kit@1.2.1 - @endo/errors@1.3.1 - @endo/harden@1.1.0 ## @endo/patterns@1.9.0 ### Minor Changes - [#3067](#3067) [`8195a5a`](8195a5a) Thanks [@gibson042](https://github.com/gibson042)! - - Updates `containerHasSplit` to consider copyArray elements in forward order, better aligning with intuition. - [#3172](#3172) [`88bc2b9`](88bc2b9) Thanks [@turadg](https://github.com/turadg)! - Improve TypeScript inference for patterns, exo, and pass-style. These are compile-time type changes only; no runtime behavior changes. - **pass-style**: `CopyArray<T>` is now `readonly T[]` so readonly tuples (e.g. `readonly ['ibc']`) satisfy `Passable`. Backward-compatible because `T[]` still extends `readonly T[]`. - **patterns**: `M.remotable()` defaults to `any` (matching `M.promise()`), so unparameterized remotables are assignable to concrete remotable typedefs. The parameterized form `M.remotable<typeof SomeInterfaceGuard>()` still yields precise inference. - **patterns**: `TFRemotable` returns `any` (not `Payload`) for non-`InterfaceGuard` arguments. - **patterns**: `TFOr` handles array-of-patterns and falls back through `TFAnd`; `M.undefined()` maps to `void`. - **patterns**: `TFOptionalTuple` emits truly optional elements; `M.promise()` maps to `PromiseLike`. - **patterns**: `TFSplitRecord` handles the empty-rest case correctly. - **patterns**: `TFRestArgs` unwraps array patterns. - **patterns**: `TypeFromArgGuard` discriminates by `toStringTag`, not structural shape. - **patterns**: `MatcherOf` payload is preserved through `InterfaceGuard`. - **patterns**: new `CastedPattern<T>` for unchecked type assertions in pattern position. - **exo**: `defineExoClass`, `defineExoClassKit`, and `makeExo` no longer intersect facet constraints with `& Methods`. The previous constraint collapsed specific facet keys into the `string | number | symbol` index signature, making `FilteredKeys` return `never` and erasing facet method inference (`Pick<X, never> = {}`). - **exo**: `Guarded<M, G>` is now structurally compatible across `G`, and the kit `F` constraint is widened. - **exo**: `defineExoClassKit` preserves facet inference when no guard is supplied. TypeScript consumers that were working around the previous inference gaps with casts may be able to remove those casts. Downstream code that depended on the narrower `CopyArray<T> = T[]` or the previous `M.remotable()` default may need minor adjustments. - [#3133](#3133) [`9111b4e`](9111b4e) Thanks [@turadg](https://github.com/turadg)! - feat: infer TypeScript types from pattern guards - `TypeFromPattern<P>` — infer static types from any pattern matcher - `TypeFromMethodGuard<G>` — infer function signatures from `M.call()` / `M.callWhen()` guards - `TypeFromInterfaceGuard<G>` — infer method records from interface guard definitions - `M.remotable<typeof Guard>()` — facet-isolated return types in exo kits - `M.infer<typeof pattern>` — namespace shorthand analogous to `z.infer` - `matches` and `mustMatch` now narrow the specimen type via type predicates - `makeExo`, `defineExoClass`, and `defineExoClassKit` enforce method signatures against guards at compile time These are compile-time type changes only; there are no runtime behavioral changes. Existing TypeScript consumers may see new type errors where method signatures diverge from their guards. - [#3133](#3133) [`df84eea`](df84eea) Thanks [@turadg](https://github.com/turadg)! - Add optional `label` parameter to `M.promise()`, aligning its signature with `M.remotable(label?)`. When a label is provided, runtime error messages include it for diagnostics (e.g., "Must be a promise Foo, not remotable"). ### Patch Changes - [#3127](#3127) [`6ada52b`](6ada52b) Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime dependencies from package manifests. - Updated dependencies \[[`98c89b7`](98c89b7), [`f65b000`](f65b000), [`88bc2b9`](88bc2b9), [`e619205`](e619205), [`43165e5`](43165e5), [`6ada52b`](6ada52b)]: - @endo/common@1.4.0 - @endo/eventual-send@1.5.0 - @endo/pass-style@1.8.0 - @endo/marshal@1.9.1 - @endo/errors@1.3.1 - @endo/harden@1.1.0 ## @endo/check-bundle@1.1.1 ### Patch Changes - [#3182](#3182) [`2b674ca`](2b674ca) Thanks [@kriskowal](https://github.com/kriskowal)! - Cull underscore-prefixed internal properties (like `__createdBy`) from serialized compartment maps in archives. The compartment map validator now also ignores underscore-prefixed properties when checking for extraneous fields. - Updated dependencies \[[`154102b`](154102b), [`2b674ca`](2b674ca), [`b4820dc`](b4820dc), [`acbacba`](acbacba), [`cdb6eae`](cdb6eae), [`6ada52b`](6ada52b), [`6ad084a`](6ad084a), [`1cd1246`](1cd1246)]: - @endo/compartment-mapper@2.1.0 - @endo/errors@1.3.1 - @endo/harden@1.1.0 ## @endo/errors@1.3.1 ### Patch Changes - Updated dependencies \[[`e619205`](e619205), [`a675d8e`](a675d8e)]: - ses@2.0.0 - @endo/harden@1.1.0 ## @endo/import-bundle@1.6.1 ### Patch Changes - Updated dependencies \[[`154102b`](154102b), [`2b674ca`](2b674ca), [`b4820dc`](b4820dc), [`acbacba`](acbacba), [`e619205`](e619205), [`cdb6eae`](cdb6eae), [`6ada52b`](6ada52b), [`6ad084a`](6ad084a), [`1cd1246`](1cd1246), [`a675d8e`](a675d8e)]: - @endo/compartment-mapper@2.1.0 - ses@2.0.0 - @endo/errors@1.3.1 - @endo/harden@1.1.0 ## @endo/lockdown@1.0.19 ### Patch Changes - Updated dependencies \[[`e619205`](e619205), [`a675d8e`](a675d8e)]: - ses@2.0.0 ## @endo/lp32@1.2.1 ### Patch Changes - [#3127](#3127) [`6ada52b`](6ada52b) Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime dependencies from package manifests. - Updated dependencies \[]: - @endo/errors@1.3.1 - @endo/harden@1.1.0 - @endo/stream@1.3.1 ## @endo/marshal@1.9.1 ### Patch Changes - [#3153](#3153) [`e619205`](e619205) Thanks [@erights](https://github.com/erights)! - # 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 <tc39/ecma262#758 (comment)> 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. - [#3127](#3127) [`6ada52b`](6ada52b) Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime dependencies from package manifests. - Updated dependencies \[[`98c89b7`](98c89b7), [`f65b000`](f65b000), [`88bc2b9`](88bc2b9), [`43165e5`](43165e5), [`6ada52b`](6ada52b)]: - @endo/common@1.4.0 - @endo/eventual-send@1.5.0 - @endo/pass-style@1.8.0 - @endo/errors@1.3.1 - @endo/harden@1.1.0 - @endo/nat@5.2.0 ## @endo/memoize@1.2.1 ### Patch Changes - [#3107](#3107) [`05cdb5f`](05cdb5f) Thanks [@erights](https://github.com/erights)! - `@endo/memoize` no longer depends on `ses`, just `@endo/harden` - Updated dependencies \[]: - @endo/harden@1.1.0 ## @endo/module-source@1.4.1 ### Patch Changes - [#3127](#3127) [`6ada52b`](6ada52b) Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime dependencies from package manifests. - Updated dependencies \[[`e619205`](e619205), [`a675d8e`](a675d8e)]: - ses@2.0.0 ## @endo/netstring@1.1.1 ### Patch Changes - [#3127](#3127) [`6ada52b`](6ada52b) Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime dependencies from package manifests. - Updated dependencies \[[`d1d9625`](d1d9625)]: - @endo/promise-kit@1.2.1 - @endo/harden@1.1.0 - @endo/stream@1.3.1 ## @endo/promise-kit@1.2.1 ### Patch Changes - [#3108](#3108) [`d1d9625`](d1d9625) Thanks [@erights](https://github.com/erights)! - `@endo/promise-kit` no longer depends on `ses`, just `@endo/harden` - Updated dependencies \[]: - @endo/harden@1.1.0 ## @endo/ses-ava@1.4.1 ### Patch Changes - Updated dependencies \[[`e619205`](e619205), [`a675d8e`](a675d8e)]: - ses@2.0.0 - @endo/harden@1.1.0 ## @endo/stream@1.3.1 ### Patch Changes - Updated dependencies \[[`f65b000`](f65b000), [`d1d9625`](d1d9625), [`e619205`](e619205), [`a675d8e`](a675d8e)]: - @endo/eventual-send@1.5.0 - @endo/promise-kit@1.2.1 - ses@2.0.0 - @endo/harden@1.1.0 ## @endo/stream-node@1.2.1 ### Patch Changes - [#3127](#3127) [`6ada52b`](6ada52b) Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime dependencies from package manifests. - Updated dependencies \[]: - @endo/errors@1.3.1 - @endo/harden@1.1.0 - @endo/stream@1.3.1 ## @endo/daemon@2.5.3 ### Patch Changes - Updated dependencies \[[`8195a5a`](8195a5a), [`154102b`](154102b), [`2b674ca`](2b674ca), [`f65b000`](f65b000), [`d1d9625`](d1d9625), [`b4820dc`](b4820dc), [`88bc2b9`](88bc2b9), [`9111b4e`](9111b4e), [`acbacba`](acbacba), [`e619205`](e619205), [`df84eea`](df84eea), [`cdb6eae`](cdb6eae), [`6ada52b`](6ada52b), [`6ad084a`](6ad084a), [`1cd1246`](1cd1246), [`a675d8e`](a675d8e)]: - @endo/patterns@1.9.0 - @endo/compartment-mapper@2.1.0 - @endo/eventual-send@1.5.0 - @endo/promise-kit@1.2.1 - @endo/exo@1.7.0 - ses@2.0.0 - @endo/marshal@1.9.1 - @endo/netstring@1.1.1 - @endo/stream-node@1.2.1 - @endo/captp@4.5.0 - @endo/errors@1.3.1 - @endo/far@1.1.14 - @endo/harden@1.1.0 - @endo/import-bundle@1.6.1 - @endo/stream@1.3.1 ## @endo/stream-types-test@1.0.19 ### Patch Changes - Updated dependencies \[[`e619205`](e619205), [`a675d8e`](a675d8e)]: - ses@2.0.0 - @endo/nat@5.2.0 - @endo/stream@1.3.1 ## @endo/test262-runner@0.1.50 ### Patch Changes - Updated dependencies \[[`154102b`](154102b), [`2b674ca`](2b674ca), [`b4820dc`](b4820dc), [`acbacba`](acbacba), [`e619205`](e619205), [`cdb6eae`](cdb6eae), [`6ada52b`](6ada52b), [`6ad084a`](6ad084a), [`1cd1246`](1cd1246), [`a675d8e`](a675d8e)]: - @endo/compartment-mapper@2.1.0 - ses@2.0.0
Closes: #XXXX Refs: #XXXX - [x] TODO cite Nikita's NaN side-channel demonstration. ## Description 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 tc39/ecma262#758 (comment) 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. ### Security Considerations The point. This NaN side-channel in ses had been implicit, easily forgotten, and hard to plug in user code. ### Scaling Considerations Not really. ### Documentation Considerations Somewhere we need to document that the `Float*Array` constructors are not available in created compartments by default, and why. ### Testing Considerations New tests included. One test newly skipped because it always hangs for me locally, which is annoying. ***Reviewers***, feel free to ask me to unskip it. ### Compatibility Considerations The absence of the `Float*Array` constructors in created compartments is potentially a compat break. In fact our need to repair `encodePassable` demonstrates such a compat break. ### Upgrade Considerations none.
This patch legalizes V8's occasional canonicalization of NaNs
by changing SetValueInBuffer to either a particular value
for the implementation-distinguishable NaN value or an
implementation-defined canonical value.
This semantic change reached consensus at the November 2016
TC39 meeting.
Closes #635
This patch still needs the associated test262 update. The tests that are affected include these, but maybe more: