isolated-vm: Timeout is ignored when creating very large `BigInt`s
If a timeout is provided and supplied code ends in an infinite loop or similar so that it exceeds this limit, an error saying ‘Script execution timed out’ is thrown. However, when the BigInt constructor is called with a very large number (>= 1e8 digits) as parameter, this “task” never gets killed (or at least much later than the provided timeout). Instead, it tries to construct this BigInt and (maybe similar to Array.prototype.fill in an earlier version of v8?) the isolate becomes unresponsive (Isolate#getHeapStatistics and other methods hang) until it’s done.
Reproducible code
const ivm = require('isolated-vm');
(async () => {
const isolate = new ivm.Isolate();
const ctx = await isolate.createContext();
await ctx.eval('BigInt("1".repeat(1e8))', {
timeout: 100
}).then(x => x.result);
})();
Node.js: v14.7.0 v8: 8.4.371.19-node.12 ivm: 3.3.5
About this issue
- Original URL
- State: open
- Created 4 years ago
- Reactions: 2
- Comments: 35 (14 by maintainers)
Commits related to this issue
- Check source length before running generated code See #202 — committed to laverdet/isolated-vm by laverdet 4 years ago
- Fix crash when `eval` is called with weird string Really spooky behavior here, probably caused by v8 being in a state close to stack limit. See #202 — committed to laverdet/isolated-vm by laverdet 4 years ago
- Remove unneeded workaround See #202 — committed to laverdet/isolated-vm by laverdet 4 years ago
- Remove unneeded workaround See #202 — committed to laverdet/isolated-vm by laverdet 4 years ago
The bad news is that this error can’t be fixed with a polyfill like the Array.fill issue. Even with a polyfill you could end up with
ctx.eval('eval("1".repeat(1e8) + "n")')which invokes the same problematic v8 code but directly from the parser.The good news is that the fix in v8 seems relatively simple. I hacked this together which fixes the issue on my end:
The pipeline from v8 pull request to nodejs release is a slow one so if this issue is causing you problems you’ll have to patch nodejs and compile yourself. I’ll post here with updates when I reach out to the v8 team about the issue.
Regardless, it’s clear that a very big rearchitecture needs to happen in order to harden isolated-vm. The whole thing really needs to be rebuilt on a multi-process abstraction to protect against the speculative execution attacks disclosed in 2018. This would also address the shared heap issues which popped up in nodejs v19.x as well. That’s a month long project that I just don’t have the spare time for though.
onCatastrophicErroris a stop gap solution for this kind of issue though. It provides a signal that v8 has failed to terminate and isolated-vm intentionally permanently deadlocks the thread to prevent it from doing any more damage. It gives you the opportunity to gracefully shut down the server, and potentially mark this script or user as problematic to avoid running their code in the future.@Gongreg that’s actually surprising to hear because I personally fixed this exact issue [very large BigInt parses] in v8 back in 2020: https://github.com/v8/v8/commit/7e8e76e784061277e13112c67c21c3f9438da257. I did kind of regret not making the chunk size smaller since it would take at least 250ms to time out if I remember correctly. I wonder if it regressed.
Hello, I wanted to get some clarification on this issue.
Lets take the following code snippet:
As far as I understand it is expected that doing the isolates will go over defined memory limit (saw that mentioned in multiple issues).
But what I am not able to find info about, is it is expected that trying to
disposeof the isolate is going to fail & the memory usage is going to keep increasing?If this is expected, maybe there are recommendations how to recover from the following scenario?
This one I think fails in the compiler. And compiler in v8 is not interruptable. To fix: fix it in v8. V8 has two main security problems, IMHO.
I resubmitted the patch after the revert and it was accepted with some broader changes: https://github.com/v8/v8/commit/7e8e76e784061277e13112c67c21c3f9438da257 https://chromium-review.googlesource.com/c/v8/v8/+/2392629
You can definitely report them here first. v8’s parser is uninterruptible and that will probably never change. In this case, v8 gives you a hook to validate any source passed to the “eval family” of functions. So we can just throw out any unreasonably long strings and refuse to compile them.
My fix landed in v8 today – https://chromium-review.googlesource.com/c/v8/v8/+/2384166
I can probably convince the nodejs team to cherrypick this into nodejs v14.x. I found out that this materially affects vanilla nodejs. If you copy and paste
BigInt("1".repeat(1e8))into the nodejs REPL it locks up the process before you even press enter.