Sandboxing support
This commit is contained in:
parent
f75b7b7879
commit
87c10ca93d
|
@ -72,6 +72,11 @@ extern "C" {
|
|||
// TODO: luaL_findtable
|
||||
|
||||
pub fn luaL_typename(L: *mut lua_State, idx: c_int) -> *const c_char;
|
||||
|
||||
// sandbox libraries and globals
|
||||
#[link_name = "luaL_sandbox"]
|
||||
pub fn luaL_sandbox_(L: *mut lua_State);
|
||||
pub fn luaL_sandboxthread(L: *mut lua_State);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -123,6 +128,29 @@ pub unsafe fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int) {
|
|||
lua::lua_unref(L, r#ref)
|
||||
}
|
||||
|
||||
pub unsafe fn luaL_sandbox(L: *mut lua_State, enabled: c_int) {
|
||||
use super::lua::*;
|
||||
|
||||
// set all libraries to read-only
|
||||
lua_pushnil(L);
|
||||
while lua_next(L, LUA_GLOBALSINDEX) != 0 {
|
||||
if lua_istable(L, -1) != 0 {
|
||||
lua_setreadonly(L, -1, enabled);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// set all builtin metatables to read-only
|
||||
lua_pushliteral(L, "");
|
||||
lua_getmetatable(L, -1);
|
||||
lua_setreadonly(L, -1, enabled);
|
||||
lua_pop(L, 2);
|
||||
|
||||
// set globals to readonly and activate safeenv since the env is immutable
|
||||
lua_setreadonly(L, LUA_GLOBALSINDEX, enabled);
|
||||
lua_setsafeenv(L, LUA_GLOBALSINDEX, enabled);
|
||||
}
|
||||
|
||||
//
|
||||
// TODO: Generic Buffer Manipulation
|
||||
//
|
||||
|
|
|
@ -26,8 +26,4 @@ extern "C" {
|
|||
|
||||
// open all builtin libraries
|
||||
pub fn luaL_openlibs(L: *mut lua_State);
|
||||
|
||||
// sandbox libraries and globals
|
||||
pub fn luaL_sandbox(L: *mut lua_State);
|
||||
pub fn luaL_sandboxthread(L: *mut lua_State);
|
||||
}
|
||||
|
|
79
src/lua.rs
79
src/lua.rs
|
@ -108,6 +108,9 @@ struct ExtraData {
|
|||
hook_callback: Option<HookCallback>,
|
||||
#[cfg(feature = "lua54")]
|
||||
warn_callback: Option<WarnCallback>,
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
sandboxed: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(any(feature = "lua51", feature = "luajit"), allow(dead_code))]
|
||||
|
@ -548,6 +551,8 @@ impl Lua {
|
|||
hook_callback: None,
|
||||
#[cfg(feature = "lua54")]
|
||||
warn_callback: None,
|
||||
#[cfg(feature = "luau")]
|
||||
sandboxed: false,
|
||||
}));
|
||||
|
||||
mlua_expect!(
|
||||
|
@ -750,6 +755,60 @@ impl Lua {
|
|||
self.entrypoint(move |lua, _: ()| func(lua))
|
||||
}
|
||||
|
||||
/// Enables (or disables) sandbox mode on this Lua instance.
|
||||
///
|
||||
/// This method, in particular:
|
||||
/// - Set all libraries to read-only
|
||||
/// - Set all builtin metatables to read-only
|
||||
/// - Set globals to read-only (and activates safeenv)
|
||||
/// - Setup local environment table that performs writes locally and proxies reads
|
||||
/// to the global environment.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use mlua::{Lua, Result};
|
||||
/// # fn main() -> Result<()> {
|
||||
/// let lua = Lua::new();
|
||||
///
|
||||
/// lua.sandbox(true)?;
|
||||
/// lua.load("var = 123").exec()?;
|
||||
/// assert_eq!(lua.globals().get::<_, u32>("var")?, 123);
|
||||
///
|
||||
/// // Restore the global environment (clear changes made in sandbox)
|
||||
/// lua.sandbox(false)?;
|
||||
/// assert_eq!(lua.globals().get::<_, Option<u32>>("var")?, None);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Requires `feature = "luau"`
|
||||
#[cfg(feature = "luau")]
|
||||
pub fn sandbox(&self, enabled: bool) -> Result<()> {
|
||||
unsafe {
|
||||
let extra = &mut *self.extra.get();
|
||||
if extra.sandboxed != enabled {
|
||||
let state = self.main_state.ok_or(Error::MainThreadNotAvailable)?;
|
||||
check_stack(state, 3)?;
|
||||
protect_lua!(state, 0, 0, |state| {
|
||||
if enabled {
|
||||
ffi::luaL_sandbox(state, 1);
|
||||
ffi::luaL_sandboxthread(state);
|
||||
} else {
|
||||
// Restore original `LUA_GLOBALSINDEX`
|
||||
self.ref_thread_exec(|ref_thread| {
|
||||
ffi::lua_xpush(ref_thread, state, ffi::LUA_GLOBALSINDEX);
|
||||
ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX);
|
||||
});
|
||||
ffi::luaL_sandbox(state, 0);
|
||||
}
|
||||
})?;
|
||||
extra.sandboxed = enabled;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a 'hook' function that will periodically be called as Lua code executes.
|
||||
///
|
||||
/// When exactly the hook function is called depends on the contents of the `triggers`
|
||||
|
@ -1424,7 +1483,11 @@ impl Lua {
|
|||
&'lua self,
|
||||
func: Function<'lua>,
|
||||
) -> Result<Thread<'lua>> {
|
||||
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored")))]
|
||||
#[cfg(any(
|
||||
feature = "lua54",
|
||||
all(feature = "luajit", feature = "vendored"),
|
||||
feature = "luau",
|
||||
))]
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(self.state);
|
||||
check_stack(self.state, 1)?;
|
||||
|
@ -1434,6 +1497,14 @@ impl Lua {
|
|||
let thread_state = ffi::lua_tothread(extra.ref_thread, index);
|
||||
self.push_ref(&func.0);
|
||||
ffi::lua_xmove(self.state, thread_state, 1);
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
{
|
||||
// Inherit `LUA_GLOBALSINDEX` from the caller
|
||||
ffi::lua_xpush(self.state, thread_state, ffi::LUA_GLOBALSINDEX);
|
||||
ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX);
|
||||
}
|
||||
|
||||
return Ok(Thread(LuaRef { lua: self, index }));
|
||||
}
|
||||
};
|
||||
|
@ -1442,7 +1513,11 @@ impl Lua {
|
|||
|
||||
/// Resets thread (coroutine) and returns to the cache for later use.
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored")))]
|
||||
#[cfg(any(
|
||||
feature = "lua54",
|
||||
all(feature = "luajit", feature = "vendored"),
|
||||
feature = "luau",
|
||||
))]
|
||||
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) {
|
||||
let extra = &mut *self.extra.get();
|
||||
let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index);
|
||||
|
|
|
@ -356,7 +356,13 @@ impl<'lua> Table<'lua> {
|
|||
pub fn set_readonly(&self, enabled: bool) {
|
||||
let lua = self.0.lua;
|
||||
unsafe {
|
||||
lua.ref_thread_exec(|refthr| ffi::lua_setreadonly(refthr, self.0.index, enabled as _));
|
||||
lua.ref_thread_exec(|refthr| {
|
||||
ffi::lua_setreadonly(refthr, self.0.index, enabled as _);
|
||||
if !enabled {
|
||||
// Reset "safeenv" flag
|
||||
ffi::lua_setsafeenv(refthr, self.0.index, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -214,6 +214,13 @@ impl<'lua> Thread<'lua> {
|
|||
lua.push_ref(&func.0);
|
||||
ffi::lua_xmove(lua.state, thread_state, 1);
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
{
|
||||
// Inherit `LUA_GLOBALSINDEX` from the caller
|
||||
ffi::lua_xpush(lua.state, thread_state, ffi::LUA_GLOBALSINDEX);
|
||||
ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -278,6 +285,53 @@ impl<'lua> Thread<'lua> {
|
|||
recycle: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables sandbox mode on this thread.
|
||||
///
|
||||
/// Under the hood replaces the global environment table with a new table,
|
||||
/// that performs writes locally and proxies reads to caller's global environment.
|
||||
///
|
||||
/// This mode ideally should be used together with the global sandbox mode [`Lua::sandbox()`].
|
||||
///
|
||||
/// Please note that Luau links environment table with chunk when loading it into Lua state.
|
||||
/// Therefore you need to load chunks into a thread to link with the thread environment.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use mlua::{Lua, Result};
|
||||
/// # fn main() -> Result<()> {
|
||||
/// let lua = Lua::new();
|
||||
/// let thread = lua.create_thread(lua.create_function(|lua2, ()| {
|
||||
/// lua2.load("var = 123").exec()?;
|
||||
/// assert_eq!(lua2.globals().get::<_, u32>("var")?, 123);
|
||||
/// Ok(())
|
||||
/// })?)?;
|
||||
/// thread.sandbox()?;
|
||||
/// thread.resume(())?;
|
||||
///
|
||||
/// // The global environment should be unchanged
|
||||
/// assert_eq!(lua.globals().get::<_, Option<u32>>("var")?, None);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Requires `feature = "luau"`
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
#[doc(hidden)]
|
||||
pub fn sandbox(&self) -> Result<()> {
|
||||
let lua = self.0.lua;
|
||||
unsafe {
|
||||
let thread = lua.ref_thread_exec(|t| ffi::lua_tothread(t, self.0.index));
|
||||
check_stack(thread, 1)?;
|
||||
check_stack(lua.state, 3)?;
|
||||
// Inherit `LUA_GLOBALSINDEX` from the caller
|
||||
ffi::lua_xpush(lua.state, thread, ffi::LUA_GLOBALSINDEX);
|
||||
ffi::lua_replace(thread, ffi::LUA_GLOBALSINDEX);
|
||||
protect_lua!(lua.state, 0, 0, |_| ffi::luaL_sandboxthread(thread))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> PartialEq for Thread<'lua> {
|
||||
|
@ -295,7 +349,11 @@ impl<'lua, R> AsyncThread<'lua, R> {
|
|||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored")))]
|
||||
#[cfg(any(
|
||||
feature = "lua54",
|
||||
all(feature = "luajit", feature = "vendored"),
|
||||
feature = "luau",
|
||||
))]
|
||||
impl<'lua, R> Drop for AsyncThread<'lua, R> {
|
||||
fn drop(&mut self) {
|
||||
if self.recycle {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use std::env;
|
||||
use std::fs;
|
||||
|
||||
use mlua::{Error, Lua, Result, Value};
|
||||
use mlua::{Error, Lua, Result, Table, Value};
|
||||
|
||||
#[test]
|
||||
fn test_require() -> Result<()> {
|
||||
|
@ -67,3 +67,61 @@ fn test_readonly_table() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sandbox() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
lua.sandbox(true)?;
|
||||
|
||||
lua.load("global = 123").exec()?;
|
||||
let n: i32 = lua.load("return global").eval()?;
|
||||
assert_eq!(n, 123);
|
||||
assert_eq!(lua.globals().get::<_, Option<i32>>("global")?, Some(123));
|
||||
|
||||
// Threads should inherit "main" globals
|
||||
let f = lua.create_function(|lua, ()| lua.globals().get::<_, i32>("global"))?;
|
||||
let co = lua.create_thread(f.clone())?;
|
||||
assert_eq!(co.resume::<_, Option<i32>>(())?, Some(123));
|
||||
|
||||
// Sandboxed threads should also inherit "main" globals
|
||||
let co = lua.create_thread(f)?;
|
||||
co.sandbox()?;
|
||||
assert_eq!(co.resume::<_, Option<i32>>(())?, Some(123));
|
||||
|
||||
lua.sandbox(false)?;
|
||||
|
||||
// Previously set variable `global` should be cleared now
|
||||
assert_eq!(lua.globals().get::<_, Option<i32>>("global")?, None);
|
||||
|
||||
// Readonly flags should be cleared as well
|
||||
let table = lua.globals().get::<_, Table>("table")?;
|
||||
table.set("test", "test")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sandbox_threads() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let f = lua.create_function(|lua, v: Value| lua.globals().set("global", v))?;
|
||||
|
||||
let co = lua.create_thread(f.clone())?;
|
||||
co.resume(321)?;
|
||||
// The main state should see the `global` variable (as the thread is not sandboxed)
|
||||
assert_eq!(lua.globals().get::<_, Option<i32>>("global")?, Some(321));
|
||||
|
||||
let co = lua.create_thread(f.clone())?;
|
||||
co.sandbox()?;
|
||||
co.resume(123)?;
|
||||
// The main state should see the previous `global` value (as the thread is sandboxed)
|
||||
assert_eq!(lua.globals().get::<_, Option<i32>>("global")?, Some(321));
|
||||
|
||||
// Try to reset the (sandboxed) thread
|
||||
co.reset(f)?;
|
||||
co.resume(111)?;
|
||||
assert_eq!(lua.globals().get::<_, Option<i32>>("global")?, Some(111));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue