tc39 / proposal-ses

Draft proposal for SES (Secure EcmaScript)
223 stars 20 forks source link

Why does Math.random open non-overt channels? #10

Open erights opened 8 years ago

erights commented 8 years ago

If we specified that Math.random is seeded by an adequate source of entropy and that it itself is a cryptographically strong random number generator, then it would be, as far as anyone (ignorant of the encapsulated seed) can tell, perfectly non-deterministic, which, amusingly, also does not enable any side channels or covert channels.

However, because someone sometime in the distant past used Math.random in the inner loop of some benchmark, all browsers made it as fast as possible while being just good enough for most statistical purposes. There have actually been exploits caused by the ability of one caller of Math.random to infer how many times someone else called it between two calls by itself. (Does anyone have a link?)

Rather than ask browser makers to provide a strong Math.random in the SES realm, it is safer to just remove it. It is impossible to write a black box conformance test that tests whether a random number generator is producing good enough randomness. There are already distinct APIs that provide good randomness anyway, though these are currently provided only by the host environment. Rather than fix Math.random, we should move some of this work into the language standard.

In any case, even though a strong enough Math.random would not open non-overt channels, it would prevent reproducibility and so hurt SES's benefits for testing and debugging.

benjamingr commented 8 years ago

I think removing Math.random() downright like that would be confusing for developers trying to use frozen-realms.

It definitely can't stay as is, because it would give the user internal information about the system.

The problem we need to solve isn't "Math.random is predictable" it's "Math.random leaks information about external state".

Wouldn't requiring that Math.random inside a frozen realm be completely isolated from the regular Math.random be sufficient?

erights commented 8 years ago

I think removing Math.random() downright like that would be confusing for developers trying to use frozen-realms.

That's the reason for the polyfill example. Most developers will, I expect, see only lightweight realms spawned for a polyfilled frozen realms with Date and Math restored to their normal function.

It definitely can't stay as is, because it would give the user internal information about the system.

The problem we need to solve isn't "Math.random is predictable" it's "Math.random leaks information about external state".

Wouldn't requiring that Math.random inside a frozen realm be completely isolated from the regular Math.random be sufficient?

I think this misunderstands which "external" we are concerned about. Two objects, Alice and Bob, that share the same frozen primordials and nothing else should not thereby be able to communicate. If Math.random is predicable enough, then we enable the following scenario:

Alice says:

const x = Math.random();

In a separate interleaved turn later Bob says:

if (secretBit) { Math.random(); }

In a separate interleaved turn later Alice says:

const y = Math.random();
if (y === expectedSuccessor(x)) {
  // secretBit was zero
} else if (y === expectedSuccessor(expectedSuccessor(x))) {
  // secretBit was probably one
} else {
  // What me worry?
}
benjamingr commented 8 years ago

I think this misunderstands which "external" we are concerned about. Two objects, Alice and Bob, that share the same frozen primordials and nothing else should not thereby be able to communicate.

This is exactly what I meant by "external", our actual requirement here is that the above scenario is not possible - not that Math.random is not defined.

We only need implementations to provide independent Math.random for every new spawned safe realm. So that in:

Alice:

 if(attempt) Math.random();

Bob:

var x = Math.random();

Bob can't deduce anything regardless of whether attempt was true in Alice's realm. One way to solve it is a cryptographically strong pseudorandom number generator - another is that implementations use two independent cryptographically weak ones.

benjamingr commented 8 years ago

Oh I see the miscommunication, when I said:

Wouldn't requiring that Math.random inside a frozen realm be completely isolated from the regular Math.random be sufficient?

I really should have said:

Wouldn't requiring that Math.random inside a frozen realm be completely isolated from the regular Math.random and other frozen realms be sufficient?

erights commented 8 years ago

If Alice and Bob do not need their own globals, they might have simply been the result of evaluating

const alice = Realm.TheFrozenRealm.eval(aliceSrc);
const bob = Realm.TheFrozenRealm.eval(bobSrc);

(I am using the API as written in this proposal at this time, though it is already obsolete based on feedback from the TC39. The API change does not affect the point illustrated by the above code.)

See the mobile code example in the proposal for a concrete case when evaluating in the root realm is fine and there's no need to spawn separate child realms.

benjamingr commented 8 years ago

Oh, if Math.random is actually shared - then it has to be cryptographically strong or not execute.

I wonder if Math.random can be patched through Zones to act independent - if each different zone gets a different Math.random and zones are set when evaluating foreign code - then technically we meet the requirement without removing Math.random - no?

const randoms = new WeakMap();
Math.random = () {
    if(!randoms.has(Zone.current)) randoms.put(Zone.current, getIndependentGenerator());
    return randoms.get(Zone.current)();
};

This assumes Zone.current is a valid map key.