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(core,ext): Fix not to be affected by Promise.prototype.then #16326

Merged
merged 18 commits into from
Oct 29, 2022

Conversation

petamoriken
Copy link
Contributor

fix #16196

@petamoriken
Copy link
Contributor Author

It seems that if I rewrite Promise.prototype.then in repl yet, it will be called. Maybe we need to disallow await.

@petamoriken
Copy link
Contributor Author

I ran the following file and had no problem. It seems that the problem is not with await, but with Promise.prototype.then being called somewhere in the repl.

console.log("start");

const original = Promise.prototype.then;
Promise.prototype.then = function () {
  console.log("bang!");
  return original.apply(this, arguments);
};

await new Promise((resolve) => setTimeout(resolve, 100));

console.log("end");

@petamoriken petamoriken marked this pull request as ready for review October 22, 2022 07:42
Comment on lines +446 to +447
// Wrapping on a new Promise is necessary to not expose the SafePromise
// prototype to user-land.
Copy link
Member

Choose a reason for hiding this comment

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

Is it unsafe to expose SafePromise.prototype to user-land? I think it can't be modified even if it's exposed because it's frozen. (I might be missing something...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This implementation is referenced from SafePromisePrototypeFinally. I think it is meant to prevent the internal code from being strangely utilized in user-land.

deno/core/00_primordials.js

Lines 439 to 455 in 45ac6e6

/**
* Attaches a callback that is invoked when the Promise is settled (fulfilled or
* rejected). The resolved value cannot be modified from the callback.
* Prefer using async functions when possible.
* @param {Promise<any>} thisPromise
* @param {() => void) | undefined | null} onFinally The callback to execute
* when the Promise is settled (fulfilled or rejected).
* @returns A Promise for the completion of the callback.
*/
primordials.SafePromisePrototypeFinally = (thisPromise, onFinally) =>
// Wrapping on a new Promise is necessary to not expose the SafePromise
// prototype to user-land.
new Promise((a, b) =>
new SafePromise((a, b) => PromisePrototypeThen(thisPromise, a, b))
.finally(onFinally)
.then(a, b)
);

@kt3k
Copy link
Member

kt3k commented Oct 23, 2022

Can you add some integration test cases that modify Promise.prototype.then and check the behaviors of APIs which were vulnerable before this change?

@petamoriken petamoriken marked this pull request as ready for review October 25, 2022 12:23
@petamoriken
Copy link
Contributor Author

@kt3k I added tests for Deno.serve and Deno.spawn.

Copy link
Member

@kt3k kt3k left a comment

Choose a reason for hiding this comment

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

LGTM

@kt3k
Copy link
Member

kt3k commented Oct 26, 2022

While SafePromiseAll is good for preventing tampering of Promise.prototype.then, we need to be careful to use it as it seems having performance penalty compared to Promise.all.

I think the usages of SafePromiseAll in this PR doesn't affect hot paths of benchmarks we've been tracking in the benchmark page.

@kt3k kt3k merged commit 59ac110 into denoland:main Oct 29, 2022
@petamoriken petamoriken deleted the fix/promise-then branch October 29, 2022 13:02
@magurotuna
Copy link
Member

Great to see it's landed! Is it also good to have some lint rule to enforce (or at least make developers be aware of) the use of these new SafePromise stuff?

@kt3k
Copy link
Member

kt3k commented Oct 31, 2022

Is it also good to have some lint rule to enforce (or at least make developers be aware of) the use of these new SafePromise stuff?

That makes sense to me. SafePromiseAll is added in this PR and I think that can be checked by the linter. I'm not sure if we can do similar check for PromisePrototypeThen as it probably requires the type inference of objects..

@magurotuna
Copy link
Member

I'm not sure if we can do similar check for PromisePrototypeThen as it probably requires the type inference of objects..

Yeah that's true. My rudimentary idea is that we have the linter check for all .then(...) and warn them. Yes, this approach would produce lots of false positives, i.e. the linter would warn the call .then(...) even if this then method is not for Promise.
In my opinion, however, false positives are much better than false negatives in this case. When false positives occur we can suppress them, saying "hey linter, trust me, this is a false positive." However, when it comes to false negatives, we can never notice that there is problematic code that's prone to pollution from malicious users.

@magurotuna
Copy link
Member

Opened an issue for deno_lint denoland/deno_lint#1095

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Overriding Promise.prototype.then affects runtime
3 participants