const attackerControlled = ...;
if (attackerControlled < 60_000) {
throw new Error("Must wait at least 1min!");
}
setTimeout(() => {
console.log("Surely at least 1min has passed!");
}, attackerControlled);
The attacker could set the value to a comically large number and the callback would execute immediately. This also seems to be true for NaN. The better solution (imo) would be to throw an error, but I assume we can't due to backwards compatibility.The problem here is having an attacker control a security sensitive timer in the first place.
If your code would misbehave outside a certain range of values and you're input might span a larger range, you should be checking your input against the range that's valid. Your sample code simply doesn't do that, and that's why there's a bug.
That the bug happens to involve a timer is irrelevant.
What's funny is you think that about the caller of setTimeout but not setTimeout itself :)
Except for the fact that this behaviour is surprising.
> you should be checking your input against the range that's valid. Your sample code simply doesn't do that, and that's why there's a bug.
Indeed, so why doesn't setTimeout internally do that?
Welcome to Node.js v22.7.0.
Type ".help" for more information.
> setTimeout(() => console.log('reached'), 3.456e9)
Timeout { <contents elided> }
> (node:64799) TimeoutOverflowWarning: 3456000000 does not fit into a 32-bit signed integer.
Timeout duration was set to 1.
(Use `node --trace-warnings ...` to show where the warning was created)
reached
I'm surprised to see that setTimeout returns an object - I assume at one point it was an integer identifying the timer, the same way it is on the web. (I think I remember it being so at one point.)Sometimes the wait is over before I find the responsible code, and sometimes it does check server-side, but that's just part of the fun...
const subtractNextDelay = () => {
if (typeof remainingDelay === "number") {
remainingDelay -= MAX_REAL_DELAY;
} else {
remainingDelay -= BigInt(MAX_REAL_DELAY);
}
};Or maybe I'm missing your point.
[1] https://html.spec.whatwg.org/multipage/timers-and-user-promp...
Just JS being JS: setTimeout(()=>{}, Infinity) executes immediately
I thought all numbers in JavaScript were basically some variation of double precision floating points, if so, why is setTimeout limited to a smaller 32bit signed integer?
If this is true, then if I pass something like "0.5", does it round the number when casting it to an integer? Or does it execute the callback after half a millisecond like you would expect it would?
[1]: https://github.com/DvdGiessen/virtual-clock/blob/master/src/...
The problem is that when setBigTimeout is invoked with a floating-point number (and numbers are floating-point in JS by default), it keeps computing the time left till trigger in floating point. But FP numbers are weird:
> 1e16 - 1 == 1e16
true
At some point, they don't have enough precision to represent exact differences, so they start rounding, and this gets extremely more inaccurate as the value increases. For correct behavior, remainingDelay needs to be stored in BigInt.Of course, this problem is mostly theoretical, as it starts happening at around 2^83 milliseconds, which doesn't even fit in a 64-bit time_t, and it's not like humanity will exist by then. But still!
As a side note, why do you use this weird non-Github, non-Gitlab, non-Bitbucket sketchy looking git host? I can see the code obviously, but it makes me worry about supply chain security.
It’s made by Drew Devault who is mostly well-respected in the hacker community, and it’s made exactly to be an alternative to BigCo-owned source hosts like GitHub, Gitlab and Bitbucket.
Latest news is that he authored/published a controversial character assassination on Richard Stallman while trying and failing to stay anonymous. Then some further digging after this unmasking found he's into pedophilic anime. Sitting on his computer uploading drawings of scantily-clad children to NSFW subreddits.
No-one with any decency can respect that behavior, it's disgusting.
Every five seconds check for due dates sooner than 10 seconds from now and schedule them.
The longer a delay the higher the odds the process exits without finishing the work.
We recently had a failed unit test because setTimeout(fn, 1000) triggered at 999ms. That test had ran more than a hundred times before just fine. Till one day it didn't.
Flaky unit tests are a scourge. The top causes of flaky unit tests in my experience:
- wall clock time ( and timezones )
- user time ( and timeouts )
- network calls
- local I/O
These are also, generally speaking, a cause of unnecessarily slow unit tests. If your unit test is waiting 1000ms, then it's taking 1000ms longer than it needs to.If you want to test that your component waits, then mock setTimeout and verify it's called with 1000 as a parameter.
If you want to test how your component waiting interacts with other components, then schedule, without timers, the interactions of effects as a separate test.
Fast reliable unit tests are difficult, but a fast reliable unit test suite is like having a super-power. It's like driving along a windy mountainside road and the difference between one with a small gravel trap and one lined with armco barriers. Even though in both cases you can the safe driving speed may be the same, having the barriers there will give you the confidence to actually go at that speed.
Doing every you can to improve the reliably and speed of your unit test suite will pay off in developer satisfaction. Every time a test suite fails because of a test failing that had nothing to do with the changes under test, a bit more of a resume gets drafted.
Not difficult if you build your code (not just the test suite) around scheduling APIs (and queues implementations, etc.) that can be implemented using virtual time instead of CPU/wall clock time (I call that soft vs hard time).
Actually I find it a breeze to create such fast and deterministic unit tests.
Maybe the system clock did a network time synchronisation during the setTimeout window.
This is fun, though. JS is a bucket of weird little details like this.
https://git.sr.ht/~evanhahn/setBigTimeout/tree/main/item/mod...
https://git.sr.ht/~evanhahn/setBigTimeout/tree/main/item/mod...
Yes, it's nice and flexible - but also introduces some dangerous subtle bugs.
And no, they're not all that. There's a bunch that are 2^32 such as this timeout, apparently, plus all the bit shift operations.
To be clear, I am not trying to be mean, I'm just curious to hear why I would pick this over cf.