Question

Scenario:

I am working on Javascript code that allows running arbitrary user-supplied code in a web worker environment, similar to this approach. Let's call the two parties host (launches the worker) and guest (inside the worker).

Now, I want to be able to run multiple scripts on the worker at the same time, or at least have them sent to the worker right away, without having to wait for the previous script to finish. It is crucial to reduce delay and overhead because a single "session" could take millions or even billions of script runs - the more I can do in a few seconds the better; and yes, there is a good reason as to why I am choosing Javascript (performance is important, but not the most important aspect).

However, that means that the worker somehow needs to reliably tell me when a script started and stopped. That requires some message sending, involving a unique script identifier.

Code:

I am currently using this code in the guest:

onmessage = function(event) {
    var scriptId = event.data.id;
    var cmd = event.data.cmd;
    var args = event.data.args;

    switch (cmd) {
        case "init":
            // code here to lock down the worker and disable everything potentially insecure, except for postMessage
            break;
        case "run":
            if (!workerInitialized()) return;
            // run user given code
            var code = args.code;
            // ...
            postMessage({cmd: "start", args: scriptId});        // signal that script started
            runScript(code);        // runs eval(code) with proper error reporting etc.
            postMessage({cmd: "stop", args: scriptId});         // signal that script stopped
            break;
        // ...
    }
}

Problem:

As you can see, scriptId exists in a stackframe above eval(code). I am using some cryptographic library to generate the id so the user cannot possibly guess the id (since the worker gets killed upon any wrong guess). Also, onmessage cannot be overridden by code.

Can I make sure that code cannot "fake" the stop message, by peaking at the stack when the code is executed in an environment that the author of code cannot control (e.g. on a node server or in the browser of a different user)?

Possible Solution:

Send the "stop" message in setTimeout and hide scriptId in closure so it will be executed right after code finished execution.

This JSFiddle shows that it works, but maybe I am overlooking some evil edge cases?

That means, I change the case "run" code to this:

postMessage({cmd: "start", args: scriptId});        // signal that script started
setTimeout((function(scriptId) { return function() { postMessage({cmd: "stop", args: scriptId}); };})(scriptId), 0);         // signal that script stopped, right after this function returned
scriptId = null;
runScript(code);        // runs eval(code) with proper error reporting etc.

Is it impossible for code to read scriptId in any execution environment? I don't even care if it can stop the timer, because the worker will be killed after a fixed timeout anyway (as long as it cannot fake the stop message which will stop that "timeout timer"!).

Was it helpful?

Solution

is not possible for code to read scriptId in any execution environment?

Yes. Code can only access variables in its current scope (of the execution context), not local variables from scopes on the call stack.

However, eval'd code can access and possibly overwrite/intercept the global postMessage and onmessage functions. If you have prevented that, they still would be able to call them, so you need to verify that the calls came from your worker manager and not from the custom code. If you have ensured that, everything should be fine.

Notice that when you're running multiple scripts in the same "worker session", you might need to clean up the global scope after each run (or prevent any modifications in beforehand). Otherwise, independent scripts might be able to communicate with each other.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top