diff --git a/src/mods/Sandbox.lua b/src/mods/Sandbox.lua index e41190ba..f52a07ce 100644 --- a/src/mods/Sandbox.lua +++ b/src/mods/Sandbox.lua @@ -28,8 +28,14 @@ local DENIED = { } -- Same idea one level up: love.filesystem is reachable by name, and --- love.thread starts a Lua state this sandbox has no say over. -local DENIED_PREFIX = { ["love"] = true, ["ffi"] = true } +-- love.thread starts a Lua state this sandbox has no say over. jit.util +-- is the LuaJIT-specific equal of the debug library above -- funcbc, +-- funck and friends read the bytecode and constants of any function a +-- chunk can reach, which is enough to walk back to upvalues (the real +-- _G, love, io) the rest of this file exists to keep out of reach. The +-- bare `jit` table stays -- env.jit above hands it over directly for +-- jit.on/off/flush -- so only the submodule require is denied. +local DENIED_PREFIX = { ["love"] = true, ["ffi"] = true, ["jit"] = true } -- The wire, which is what the network permission governs. local NETWORK = { socket = true, enet = true, http = true, https = true, diff --git a/tests/modkit/cases/sandbox.lua b/tests/modkit/cases/sandbox.lua index 27133e15..5f9c06c8 100644 --- a/tests/modkit/cases/sandbox.lua +++ b/tests/modkit/cases/sandbox.lua @@ -44,6 +44,11 @@ local PROBE = [[ out.requireDebug = attempt(require, "debug") out.requirePackage = attempt(require, "package") out.requireFfi = attempt(require, "ffi") + -- jit.util exposes bytecode/constant introspection over any function this + -- chunk can reach -- the same class of escape the debug library is denied + -- for -- so it must fail the same way require("debug") does rather than + -- walking straight through under the bare "jit" global's cover. + out.requireJitUtil = attempt(require, "jit.util") out.requireSocket = attempt(require, "socket") out.requireSemver = select(2, pcall(require, "src.mods.Semver")) @@ -180,6 +185,8 @@ T.eq(out.getfenv, nil, "getfenv is still absent, so a mod cannot read the real _ T.eq(out.debug, nil, "the debug library is still absent") T.check(out.loveThread ~= false, "love.thread is still refused: it opens a full Lua state") T.check(out.requireFfi ~= false, "require(\"ffi\") is still refused: it is arbitrary C") +T.check(out.requireJitUtil ~= false, + "require(\"jit.util\") is still refused: it is bytecode/constant introspection") T.check(out.requireDebug ~= false, "require(\"debug\") is still refused") T.check(out.requirePackage ~= false, "require(\"package\") is still refused") T.eq(out.popen, nil, "io.popen refuses rather than spawning a process")