Javet icon indicating copy to clipboard operation
Javet copied to clipboard

The use of eval in vm is restricted

Open LoyDgIk opened this issue 1 year ago • 11 comments

runtime.getExecutor("""
    const vm = require('node:vm');
    const script = new vm.Script("eval('1+1')");
    script.runInNewContext();
""".trimIndent()).setModule(true).executeVoid()

It's normal to write like this. But

runtime.getExecutor("""
    const vm = require('node:vm');
    const script = new vm.Script("eval('1+1')");
    script.runInNewContext();
""".trimIndent()).compileV8Module().execute();

Will throw an error:EvalError: Code generation from strings disallowed for this context

So this is a bug?

LoyDgIk avatar Dec 10 '24 18:12 LoyDgIk

It seems you are using the engine pool which disables eval() by default. Please check this page out to allow eval().

caoccao avatar Dec 10 '24 23:12 caoccao

IMG_20241211_080834 I set allowEval to true, but it doesn't seem to work.

LoyDgIk avatar Dec 11 '24 00:12 LoyDgIk

It's more like a JS trick as follows.

nodeRuntime.allowEval(true);
nodeRuntime.getExecutor("const vm = require(\"node:vm\");\n" +
        "const script = new vm.Script(\"eval('1+1')\");\n" +
        "console.log(script.runInThisContext());\n" +
        "console.log(script.runInNewContext(vm.createContext({eval})));").executeVoid();
// Output
2
2

caoccao avatar Dec 11 '24 04:12 caoccao

Have you tried .compileV8Module().execute()? This is when an error would be thrown, whereas .executeVoid() works without issues. I needed to use .compileV8Module() in IV8ModuleResolver.resolve() and that's how I discovered this problem.

LoyDgIk avatar Dec 11 '24 06:12 LoyDgIk

Those API require certain understanding on how V8 works internally. Please let your code.

caoccao avatar Dec 11 '24 06:12 caoccao

fun resolve(runtime: V8Runtime, resourceName: String, v8ModuleReferrer: IV8Module): Any? {
    return when {
        resourceName.startsWith("/") || resourceName.startsWith("./") || resourceName.startsWith("../") -> {
            val parentModuleName = v8ModuleReferrer.getResourceName()
            val moduleRelativePath = Paths.get(parentModuleName).parent.resolve(resourceName).normalize()
            val moduleFile = Paths.get(localRelativePath).resolve(moduleRelativePath).toFile()

            if (moduleFile.exists() && moduleFile.isFile) {
                val url = "file://${moduleFile.canonicalPath}"
                val code = readFile(url)
                runtime.getExecutor("import.meta.url=${Json.encodeToString(url)};$code")
                    .setResourceName(moduleRelativePath.toString()).compileV8Module()
            } else {
                null
            }
        }
        resourceName.startsWith("node:") -> nodeResolve.resolve(runtime, resourceName, v8ModuleReferrer)
        else -> {
            val moduleObject = runtime.getNodeModule(resourceName, nodeModuleAnyClazz).getModuleObject()
            moduleObject.set("default", moduleObject)
            runtime.createV8Module(resourceName, moduleObject)
        }
    }
}

This is my code. So, how do we address the issues with eval?

LoyDgIk avatar Dec 11 '24 07:12 LoyDgIk

It doesn't seem to be a complete reproducible code. Could you leave a repo with the issue?

caoccao avatar Dec 11 '24 07:12 caoccao

It's more like a JS trick as follows.

nodeRuntime.allowEval(true);
nodeRuntime.getExecutor("const vm = require(\"node:vm\");\n" +
        "const script = new vm.Script(\"eval('1+1')\");\n" +
        "console.log(script.runInThisContext());\n" +
        "console.log(script.runInNewContext(vm.createContext({eval})));").executeVoid();
// Output
2
2

I just realized that this eval is manually injected into the vm context, while in fact, native Node.js does not require such an operation. native Node.js: IMG_20241216_113508 javet: Screenshot_2024-12-16-11-34-02-150_com whl quickjs wrapper sample

LoyDgIk avatar Dec 16 '24 03:12 LoyDgIk

It's more like a JS trick as follows.

nodeRuntime.allowEval(true);
nodeRuntime.getExecutor("const vm = require(\"node:vm\");\n" +
        "const script = new vm.Script(\"eval('1+1')\");\n" +
        "console.log(script.runInThisContext());\n" +
        "console.log(script.runInNewContext(vm.createContext({eval})));").executeVoid();
// Output
2
2

Can you take a look at it for me?

LoyDgIk avatar Dec 17 '24 10:12 LoyDgIk

const vm = require('vm');

// 创建一个沙箱上下文 const sandbox = { x: 1, y: 2 };

// 创建一个虚拟机上下文,允许执行 eval vm.createContext(sandbox);

// 使用 vm.runInContext 执行代码 const code = 'eval("x + y")'; // 使用 eval 来执行动态代码 const result = vm.runInContext(code, sandbox);

console.log(result); // 输出: 3 在这个例子中,通过 vm.createContext 创建了一个沙箱,并在该上下文中执行了一个包含 eval() 的动态代码。正常环境 这个代码被允许执行,因为 eval 在上下文中没有被禁用。但是javet 运行结果是eval不能正确执行, 可能是处于禁用。

FoxNick avatar Dec 17 '24 16:12 FoxNick

v8Isolate->SetModifyCodeGenerationFromStringsCallback(nullptr); 不知道这段代码是用来干什么的 注释掉 vm eavl 开启和禁用 问题解决

FoxNick avatar Dec 18 '24 03:12 FoxNick