laverdet / isolated-vm

Secure & isolated JS environments for nodejs
ISC License
2.2k stars 154 forks source link

Open Questions regarding Documentation #388

Closed Nitwel closed 1 year ago

Nitwel commented 1 year ago

Hey there, we're using isolated-vm to run user-made extensions on our platform and still have some open questions regarding security and how stuff works in detail.

Question 1

Starting with the following sentence:

At a minimum you should take care not to leak any instances of isolated-vm objects (Reference, ExternalCopy, etc) to untrusted code. It is usually trivial for an attacker to use these instances as a springboard back into the nodejs isolate which will yield complete control over a process.

We would really like to understand why that is the case, as to us it is not trivial. It's no inconvenience for us to follow that guide, but it would be great to understand the reasons behind it.

Question 2

Next, how Reference and ExternalCopy work under the hood. A reference is documented as follows:

A instance of Reference is a pointer to a value stored in any isolate.

It is likely meant that when passing to an isolate, it sends over a reference to the value created in Node.js, but the sentence sounds as if a Reference is a pointer to a value, and said value is stored in an isolate which doesn't make a lot of sense.

Question 3

What's the difference between Reference and ExternalCopy? Both sound like memory gets allocated on the Node.js side, but what happens if you pass in an ExternalCopy directly into an isolate, will it create a Reference to an ExternalCopy or do something else?

Question 4

Regarding TransferOptions, it is mentioned that without specifying those, functions do whatever they want, but it would be important for our understanding what function does exactly what when you don't pass in any TransferOptions.

Thanks again for this amazing library, it has opened a lot of new possibilities for us, but we really would love to get some more insight into understanding it more thoroughly. ❤️

laverdet commented 1 year ago

1) This was more true in the past, less true now that these objects have been "hardened" (Reference no longer follows prototype chain by default, all prototypes frozen). You still shouldn't do it though:

const ivm = require('isolated-vm');
const isolate = new ivm.Isolate({ memoryLimit: 32 })
const context = isolate.createContextSync()
context.global.setSync('foo', new ivm.Reference(Function));
console.log(context.evalSync(`foo.applySync(null, ["import('fs').then(fs => console.log(fs.readFileSync('binding.gyp','utf8')))"], { result: { reference: true } }).applySync(null, [])`));

2) A Reference can point to any value in any isolate. Whether that is the default nodejs isolate or one created by isolated-vm doesn't matter.

I want to point out that isolated-vm doesn't treat the nodejs isolate any differently than isolated-vm isolates (with the exception of the thread synchronization rules mentioned in the readme).

3) An ExternalCopy is stored outside v8 entirely, therefore the value can be accessed by any isolate at any time (as long as it has a handle to the copy obviously). A Reference is a pointer to v8-managed memory and you can't access it without a dereferencing (for the owned isolate) or copying (for another isolate), and both operations require a lock on the isolate that owns the dereferenced value. ExternalCopy is useful for advanced cases where you want to give an isolate the option to import some value, but to do it on demand. Maybe the value is really large and you don't want to waste precious v8 heap. Or if you want to copy the same value into multiple isolates. Otherwise you'd have to perform the serialization step once for each isolate.

4) Primitives are usually transferred. If you are invoking a method on a Reference it will usually opt to give you a Reference in return. Sorry I don't have more information here.

Nitwel commented 1 year ago

Thank you very much. I have one question left regarding the main isolate on the NodeJS side works.

How does it differ from outside the main isolate or is there no difference between the main isolate and the NodeJS environment? Does the main isolate run in a separate thread or even a separate process, same for self created isolates, are they the same or different thread / proccess?

laverdet commented 1 year ago

The main difference is the scheduler. Work which is scheduled from ivm to nodejs is done through libuv. Work which is scheduled from nodejs to ivm is done through a custom scheduler. Additionally, isolated-vm isolates can make use of any thread, including nodejs controlled threads. That's why you can invoke sync methods from nodejs, but not the other way around. When you invoke a sync method we aren't calling out to another thread, the allocated isolate just jumps into nodejs's thread, completes some work, and then leaves.

nodejs on the other hand allocates a thread per isolate, and that thread is dedicated to the given isolate. It's a perfectly fine system, and was just made for different purposes. There is some extra [CPU] overhead involved in sharing threads so for nodejs it probably makes sense since you shouldn't have a crazy number of threads. isolated-vm was made for a video game running code provided by 1000's of players so the memory overhead of each player was supposed to be as low as possible.

Nitwel commented 1 year ago

Thanks again for your answer. Will close this now as every question is resolved to us. 😄