Skip to content
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

fix(resolve): protect against reentrancy attack #401

Merged
merged 4 commits into from
Jan 27, 2020

Conversation

michaelfig
Copy link
Member

Closes #9

Note that the test for isNormalPromiseThen does not check that the Promise.prototype is frozen as this is always true under SES and too strict outside of SES (tries assimilating all Promises). It also checks if p is frozen only when under SES, as it is too strict outside of SES (tries assimilating platform Promises).

Please review carefully. There is no rush.

packages/eventual-send/src/index.js Outdated Show resolved Hide resolved
getPrototypeOf(p) === promiseProto &&
promiseResolve(p) === p &&
gopd(p, 'then') === undefined &&
(isSES || gopd(promiseProto, 'then').value === originalThen) // unnecessary under SES
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outside of SES, if we're not checking isFrozen(promiseProto) and we are checking gopd(promiseProto, 'then').value === originalThen, we should also check that this data property is non-writable, non-configurable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found that using harden() anywhere in eventual-send meant that we are taking a stand in hardening HandledPromises. Going all the way, I instead harden things that are returned by HandledPromise.resolve which simplifies the test and makes it behave more consistently inside and outside SES. This helped ERTP pass all its unit tests again.

I hope I made an acceptable tradeoff.

If that's not acceptable, then we need a much more convincing story of what we should actually harden in eventual-send.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@erights PTAL.

@Chris-Hibbert
Copy link
Contributor

Sorry, but I'm not qualified to review this.

@michaelfig michaelfig removed the request for review from Chris-Hibbert January 10, 2020 21:34
@erights
Copy link
Member

erights commented Jan 10, 2020

Object.setPrototypeOf(HandledPromise, Promise);

My apologies! I saw the comment, and interpreted it as being about setting HandledPromise.prototype to be Promise.prototype, which we do in fact need. However, I see this was always true, now with the line

HandledPromise.prototype = promiseProto;

which was never turned off. Regarding the line that I mistakenly asked you to change, I actually have no strong feeling one way or the other. I'm happy with what you changed it to, and with making the corresponding change to the spec. But I'd also be happy without it. Feel free to revert this portion of this PR before merging, or not, as you wish.

Sorry for the confusion!

@warner warner added the eventual-send package: eventual-send label Jan 22, 2020
@michaelfig
Copy link
Member Author

After using agoric-labs/tape-xs#3 I discovered that the test suite under XS is hanging with the following output:

# E method calls
ok 1 return is a thenable
 lin_xs_cli: main() returned a promise; entering event loop
ok 2 method call works
# E shortcuts
ok 3 method call works
ok 4 anonymous method works
ok 5 property get
# chained properties
ok 6 should be equivalent
ok 7 should be equivalent
# E.resolve is always asynchronous
ok 8 should be identical

@michaelfig
Copy link
Member Author

michaelfig commented Jan 27, 2020

@dckc, the hanging test is at an awaited promise

@michaelfig
Copy link
Member Author

@dckc Aha! I'm narrowing down on the problem, and it looks like a legit portability bug in this PR.

@michaelfig
Copy link
Member Author

It's the override mistake. Under XS, I cannot override the .then of a promise because Promise.prototype.then is frozen!

That's actually a good thing. This prevents having to assimilate promises under XS.

I've updated the tests accordingly. Will retry.

@erights
Copy link
Member

erights commented Jan 27, 2020

That's actually a good thing. This prevents having to assimilate promises under XS.

The override mistake only protects against override by assignment. It doesn't prevent override by defineProperty, and so does not provide any actual safety against intentional override.

@michaelfig
Copy link
Member Author

That's actually a good thing. This prevents having to assimilate promises under XS.

The override mistake only protects against override by assignment. It doesn't prevent override by defineProperty, and so does not provide any actual safety against intentional override.

Oh, okay. I'll rewrite the test accordingly.

michaelfig and others added 4 commits January 26, 2020 20:11
The harden calls in eventual-send already need an SES-like
environment for proper security.  Make HandledPromise.resolve
simpler and prevent proxy trickery.
@michaelfig
Copy link
Member Author

Bingo! The workaround of the override mistake using Object.defineProperty shows proper assimilation behaviour under both Node and XS.

Still, it was interesting to reveal Moddable-OpenSource/moddable#322

Copy link
Member

@erights erights left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@michaelfig michaelfig merged commit d1f25ef into master Jan 27, 2020
@michaelfig michaelfig deleted the 9-resolve-reentrancy branch January 27, 2020 13:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
eventual-send package: eventual-send
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Promise.resolve(p).then(t => {...})` not actually safe from reentrancy attack.
4 participants