Provide safe and unsafe Lua modes:
- In safe mode Lua would not have ability to load C code via `require` or `package.loadlib` - Unsafe mode allows everything.
This commit is contained in:
parent
1b2b94c808
commit
5a9a308790
|
@ -36,6 +36,8 @@ pub enum Error {
|
||||||
/// The Lua VM returns this error when there is an error running a `__gc` metamethod.
|
/// The Lua VM returns this error when there is an error running a `__gc` metamethod.
|
||||||
#[cfg(any(feature = "lua53", feature = "lua52"))]
|
#[cfg(any(feature = "lua53", feature = "lua52"))]
|
||||||
GarbageCollectorError(StdString),
|
GarbageCollectorError(StdString),
|
||||||
|
/// Potentially unsafe action in safe mode.
|
||||||
|
SafetyError(StdString),
|
||||||
/// Setting memory limit is not available.
|
/// Setting memory limit is not available.
|
||||||
///
|
///
|
||||||
/// This error can only happen when Lua state was not created by us and does not have the
|
/// This error can only happen when Lua state was not created by us and does not have the
|
||||||
|
@ -153,6 +155,9 @@ impl fmt::Display for Error {
|
||||||
Error::GarbageCollectorError(ref msg) => {
|
Error::GarbageCollectorError(ref msg) => {
|
||||||
write!(fmt, "garbage collector error: {}", msg)
|
write!(fmt, "garbage collector error: {}", msg)
|
||||||
}
|
}
|
||||||
|
Error::SafetyError(ref msg) => {
|
||||||
|
write!(fmt, "safety error: {}", msg)
|
||||||
|
},
|
||||||
Error::MemoryLimitNotAvailable => {
|
Error::MemoryLimitNotAvailable => {
|
||||||
write!(fmt, "setting memory limit is not available")
|
write!(fmt, "setting memory limit is not available")
|
||||||
}
|
}
|
||||||
|
|
207
src/lua.rs
207
src/lua.rs
|
@ -43,6 +43,7 @@ pub struct Lua {
|
||||||
main_state: *mut ffi::lua_State,
|
main_state: *mut ffi::lua_State,
|
||||||
extra: Arc<Mutex<ExtraData>>,
|
extra: Arc<Mutex<ExtraData>>,
|
||||||
ephemeral: bool,
|
ephemeral: bool,
|
||||||
|
safe: bool,
|
||||||
// Lua has lots of interior mutability, should not be RefUnwindSafe
|
// Lua has lots of interior mutability, should not be RefUnwindSafe
|
||||||
_no_ref_unwind_safe: PhantomData<UnsafeCell<()>>,
|
_no_ref_unwind_safe: PhantomData<UnsafeCell<()>>,
|
||||||
}
|
}
|
||||||
|
@ -108,17 +109,63 @@ impl Drop for Lua {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Lua {
|
impl Lua {
|
||||||
/// Creates a new Lua state and loads standard library without the `debug` library.
|
/// Creates a new Lua state and loads the safe subset of the standard libraries.
|
||||||
|
///
|
||||||
|
/// The created Lua state would have safety guarantees and would not allow to load unsafe
|
||||||
|
/// standard libraries or C modules.
|
||||||
pub fn new() -> Lua {
|
pub fn new() -> Lua {
|
||||||
Self::new_with(StdLib::ALL_NO_DEBUG)
|
mlua_expect!(
|
||||||
|
Self::new_with(StdLib::ALL_SAFE),
|
||||||
|
"can't create new safe Lua state"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new Lua state and loads the specified set of standard libraries.
|
/// Creates a new Lua state and loads all the standard libraries.
|
||||||
|
///
|
||||||
|
/// The created Lua state would not have safety guarantees and would allow to load C modules.
|
||||||
|
pub unsafe fn unsafe_new() -> Lua {
|
||||||
|
Self::unsafe_new_with(StdLib::ALL)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new Lua state and loads the specified safe subset of the standard libraries.
|
||||||
///
|
///
|
||||||
/// Use the [`StdLib`] flags to specifiy the libraries you want to load.
|
/// Use the [`StdLib`] flags to specifiy the libraries you want to load.
|
||||||
///
|
///
|
||||||
|
/// The created Lua state would have safety guarantees and would not allow to load unsafe
|
||||||
|
/// standard libraries or C modules.
|
||||||
|
///
|
||||||
/// [`StdLib`]: struct.StdLib.html
|
/// [`StdLib`]: struct.StdLib.html
|
||||||
pub fn new_with(libs: StdLib) -> Lua {
|
pub fn new_with(libs: StdLib) -> Result<Lua> {
|
||||||
|
if libs.contains(StdLib::DEBUG) {
|
||||||
|
return Err(Error::SafetyError(
|
||||||
|
"the unsafe `debug` module can't be loaded using safe `new_with`".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
#[cfg(feature = "luajit")]
|
||||||
|
{
|
||||||
|
if libs.contains(StdLib::FFI) {
|
||||||
|
return Err(Error::SafetyError(
|
||||||
|
"the unsafe `ffi` module can't be loaded using safe `new_with`".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut lua = unsafe { Self::unsafe_new_with(libs) };
|
||||||
|
|
||||||
|
mlua_expect!(lua.disable_c_modules(), "Error during disabling C modules");
|
||||||
|
lua.safe = true;
|
||||||
|
|
||||||
|
Ok(lua)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new Lua state and loads the specified subset of the standard libraries.
|
||||||
|
///
|
||||||
|
/// Use the [`StdLib`] flags to specifiy the libraries you want to load.
|
||||||
|
///
|
||||||
|
/// The created Lua state would not have safety guarantees and would allow to load C modules.
|
||||||
|
///
|
||||||
|
/// [`StdLib`]: struct.StdLib.html
|
||||||
|
pub unsafe fn unsafe_new_with(libs: StdLib) -> Lua {
|
||||||
unsafe extern "C" fn allocator(
|
unsafe extern "C" fn allocator(
|
||||||
extra_data: *mut c_void,
|
extra_data: *mut c_void,
|
||||||
ptr: *mut c_void,
|
ptr: *mut c_void,
|
||||||
|
@ -173,67 +220,28 @@ impl Lua {
|
||||||
new_ptr
|
new_ptr
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
let mem_info = Box::into_raw(Box::new(MemoryInfo {
|
||||||
let mem_info = Box::into_raw(Box::new(MemoryInfo {
|
used_memory: 0,
|
||||||
used_memory: 0,
|
memory_limit: 0,
|
||||||
memory_limit: 0,
|
}));
|
||||||
}));
|
|
||||||
|
|
||||||
let state = ffi::lua_newstate(allocator, mem_info as *mut c_void);
|
let state = ffi::lua_newstate(allocator, mem_info as *mut c_void);
|
||||||
|
|
||||||
ffi::luaL_requiref(state, cstr!("_G"), ffi::luaopen_base, 1);
|
ffi::luaL_requiref(state, cstr!("_G"), ffi::luaopen_base, 1);
|
||||||
ffi::lua_pop(state, 1);
|
ffi::lua_pop(state, 1);
|
||||||
|
|
||||||
let mut lua = Lua::init_from_ptr(state);
|
let mut lua = Lua::init_from_ptr(state);
|
||||||
lua.ephemeral = false;
|
lua.ephemeral = false;
|
||||||
lua.extra.lock().unwrap().mem_info = mem_info;
|
lua.extra.lock().unwrap().mem_info = mem_info;
|
||||||
|
|
||||||
mlua_expect!(
|
mlua_expect!(
|
||||||
protect_lua_closure(lua.main_state, 0, 0, |state| {
|
protect_lua_closure(lua.main_state, 0, 0, |state| {
|
||||||
load_from_std_lib(state, libs);
|
|
||||||
}),
|
|
||||||
"Error during loading standard libraries"
|
|
||||||
);
|
|
||||||
|
|
||||||
lua
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consumes and leaks `Lua` object, returning a static reference `&'static Lua`.
|
|
||||||
///
|
|
||||||
/// This function is useful when the `Lua` object is supposed to live for the remainder
|
|
||||||
/// of the program's life.
|
|
||||||
/// In particular in asynchronous context this will allow to spawn Lua tasks to execute
|
|
||||||
/// in background.
|
|
||||||
///
|
|
||||||
/// Dropping the returned reference will cause a memory leak. If this is not acceptable,
|
|
||||||
/// the reference should first be wrapped with the [`Lua::from_static`] function producing a `Lua`.
|
|
||||||
/// This `Lua` object can then be dropped which will properly release the allocated memory.
|
|
||||||
///
|
|
||||||
/// [`Lua::from_static`]: #method.from_static
|
|
||||||
pub fn into_static(self) -> &'static Self {
|
|
||||||
Box::leak(Box::new(self))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Constructs a `Lua` from a static reference to it.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// This function is unsafe because improper use may lead to memory problems or undefined behavior.
|
|
||||||
pub unsafe fn from_static(lua: &'static Lua) -> Self {
|
|
||||||
*Box::from_raw(lua as *const Lua as *mut Lua)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Loads the specified set of standard libraries into an existing Lua state.
|
|
||||||
///
|
|
||||||
/// Use the [`StdLib`] flags to specifiy the libraries you want to load.
|
|
||||||
///
|
|
||||||
/// [`StdLib`]: struct.StdLib.html
|
|
||||||
pub fn load_from_std_lib(&self, libs: StdLib) -> Result<()> {
|
|
||||||
unsafe {
|
|
||||||
protect_lua_closure(self.main_state, 0, 0, |state| {
|
|
||||||
load_from_std_lib(state, libs);
|
load_from_std_lib(state, libs);
|
||||||
})
|
}),
|
||||||
}
|
"Error during loading standard libraries"
|
||||||
|
);
|
||||||
|
|
||||||
|
lua
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a new Lua instance from the existing state.
|
/// Constructs a new Lua instance from the existing state.
|
||||||
|
@ -292,10 +300,62 @@ impl Lua {
|
||||||
main_state: main_state,
|
main_state: main_state,
|
||||||
extra: extra,
|
extra: extra,
|
||||||
ephemeral: true,
|
ephemeral: true,
|
||||||
|
safe: false,
|
||||||
_no_ref_unwind_safe: PhantomData,
|
_no_ref_unwind_safe: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads the specified subset of the standard libraries into an existing Lua state.
|
||||||
|
///
|
||||||
|
/// Use the [`StdLib`] flags to specifiy the libraries you want to load.
|
||||||
|
///
|
||||||
|
/// [`StdLib`]: struct.StdLib.html
|
||||||
|
pub fn load_from_std_lib(&self, libs: StdLib) -> Result<()> {
|
||||||
|
if self.safe && libs.contains(StdLib::DEBUG) {
|
||||||
|
return Err(Error::SafetyError(
|
||||||
|
"the unsafe `debug` module can't be loaded in safe mode".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
#[cfg(feature = "luajit")]
|
||||||
|
{
|
||||||
|
if self.safe && libs.contains(StdLib::FFI) {
|
||||||
|
return Err(Error::SafetyError(
|
||||||
|
"the unsafe `ffi` module can't be loaded in safe mode".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
protect_lua_closure(self.main_state, 0, 0, |state| {
|
||||||
|
load_from_std_lib(state, libs);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes and leaks `Lua` object, returning a static reference `&'static Lua`.
|
||||||
|
///
|
||||||
|
/// This function is useful when the `Lua` object is supposed to live for the remainder
|
||||||
|
/// of the program's life.
|
||||||
|
/// In particular in asynchronous context this will allow to spawn Lua tasks to execute
|
||||||
|
/// in background.
|
||||||
|
///
|
||||||
|
/// Dropping the returned reference will cause a memory leak. If this is not acceptable,
|
||||||
|
/// the reference should first be wrapped with the [`Lua::from_static`] function producing a `Lua`.
|
||||||
|
/// This `Lua` object can then be dropped which will properly release the allocated memory.
|
||||||
|
///
|
||||||
|
/// [`Lua::from_static`]: #method.from_static
|
||||||
|
pub fn into_static(self) -> &'static Self {
|
||||||
|
Box::leak(Box::new(self))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a `Lua` from a static reference to it.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// This function is unsafe because improper use may lead to memory problems or undefined behavior.
|
||||||
|
pub unsafe fn from_static(lua: &'static Lua) -> Self {
|
||||||
|
*Box::from_raw(lua as *const Lua as *mut Lua)
|
||||||
|
}
|
||||||
|
|
||||||
// Executes module entrypoint function, which returns only one Value.
|
// Executes module entrypoint function, which returns only one Value.
|
||||||
// The returned value then pushed to the Lua stack.
|
// The returned value then pushed to the Lua stack.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -1556,9 +1616,36 @@ impl Lua {
|
||||||
main_state: self.main_state,
|
main_state: self.main_state,
|
||||||
extra: self.extra.clone(),
|
extra: self.extra.clone(),
|
||||||
ephemeral: true,
|
ephemeral: true,
|
||||||
|
safe: self.safe,
|
||||||
_no_ref_unwind_safe: PhantomData,
|
_no_ref_unwind_safe: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn disable_c_modules(&self) -> Result<()> {
|
||||||
|
let package: Table = self.globals().get("package")?;
|
||||||
|
|
||||||
|
package.set(
|
||||||
|
"loadlib",
|
||||||
|
self.create_function(|_, ()| -> Result<()> {
|
||||||
|
Err(Error::SafetyError(
|
||||||
|
"package.loadlib is disabled in safe mode".to_string(),
|
||||||
|
))
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
||||||
|
let searchers: Table = package.get("searchers")?;
|
||||||
|
#[cfg(any(feature = "lua51", feature = "luajit"))]
|
||||||
|
let searchers: Table = package.get("loaders")?;
|
||||||
|
|
||||||
|
let loader = self.create_function(|_, ()| Ok("\n\tcan't load C modules in safe mode"))?;
|
||||||
|
|
||||||
|
// The third and fourth searchers looks for a loader as a C library
|
||||||
|
searchers.raw_set(3, loader.clone())?;
|
||||||
|
searchers.raw_remove(4)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks.
|
/// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks.
|
||||||
|
|
|
@ -20,12 +20,15 @@ impl StdLib {
|
||||||
pub const PACKAGE: StdLib = StdLib(1 << 8);
|
pub const PACKAGE: StdLib = StdLib(1 << 8);
|
||||||
#[cfg(feature = "luajit")]
|
#[cfg(feature = "luajit")]
|
||||||
pub const JIT: StdLib = StdLib(1 << 9);
|
pub const JIT: StdLib = StdLib(1 << 9);
|
||||||
|
|
||||||
|
/// `ffi` (unsafe) module `feature = "luajit"`
|
||||||
#[cfg(feature = "luajit")]
|
#[cfg(feature = "luajit")]
|
||||||
pub const FFI: StdLib = StdLib(1 << 10);
|
pub const FFI: StdLib = StdLib(1 << 30);
|
||||||
pub const DEBUG: StdLib = StdLib(1 << 31); // always highest bit
|
/// `debug` (unsafe) module
|
||||||
|
pub const DEBUG: StdLib = StdLib(1 << 31);
|
||||||
|
|
||||||
pub const ALL: StdLib = StdLib(u32::MAX);
|
pub const ALL: StdLib = StdLib(u32::MAX);
|
||||||
pub const ALL_NO_DEBUG: StdLib = StdLib((1 << 31) - 1);
|
pub const ALL_SAFE: StdLib = StdLib((1 << 30) - 1);
|
||||||
|
|
||||||
pub fn contains(self, lib: Self) -> bool {
|
pub fn contains(self, lib: Self) -> bool {
|
||||||
(self & lib).0 != 0
|
(self & lib).0 != 0
|
||||||
|
|
|
@ -4,9 +4,49 @@ use std::sync::Arc;
|
||||||
use std::{error, f32, f64, fmt};
|
use std::{error, f32, f64, fmt};
|
||||||
|
|
||||||
use mlua::{
|
use mlua::{
|
||||||
Error, ExternalError, Function, Lua, Nil, Result, String, Table, UserData, Value, Variadic,
|
Error, ExternalError, Function, Lua, Nil, Result, StdLib, String, Table, UserData, Value,
|
||||||
|
Variadic,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_safety() -> Result<()> {
|
||||||
|
let lua = Lua::new();
|
||||||
|
assert!(lua.load(r#"require "debug""#).exec().is_err());
|
||||||
|
match lua.load_from_std_lib(StdLib::DEBUG) {
|
||||||
|
Err(Error::SafetyError(_)) => {}
|
||||||
|
Err(e) => panic!("expected SafetyError, got {:?}", e),
|
||||||
|
Ok(_) => panic!("expected SafetyError, got no error"),
|
||||||
|
}
|
||||||
|
drop(lua);
|
||||||
|
|
||||||
|
let lua = unsafe { Lua::unsafe_new() };
|
||||||
|
assert!(lua.load(r#"require "debug""#).exec().is_ok());
|
||||||
|
drop(lua);
|
||||||
|
|
||||||
|
match Lua::new_with(StdLib::DEBUG) {
|
||||||
|
Err(Error::SafetyError(_)) => {}
|
||||||
|
Err(e) => panic!("expected SafetyError, got {:?}", e),
|
||||||
|
Ok(_) => panic!("expected SafetyError, got new Lua state"),
|
||||||
|
}
|
||||||
|
|
||||||
|
let lua = Lua::new();
|
||||||
|
match lua.load(r#"package.loadlib()"#).exec() {
|
||||||
|
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
|
||||||
|
Error::SafetyError(_) => {}
|
||||||
|
e => panic!("expected SafetyError cause, got {:?}", e),
|
||||||
|
},
|
||||||
|
Err(e) => panic!("expected CallbackError, got {:?}", e),
|
||||||
|
Ok(_) => panic!("expected CallbackError, got no error"),
|
||||||
|
};
|
||||||
|
match lua.load(r#"require "fake_ffi""#).exec() {
|
||||||
|
Err(Error::RuntimeError(msg)) => assert!(msg.contains("can't load C modules in safe mode")),
|
||||||
|
Err(e) => panic!("expected RuntimeError, got {:?}", e),
|
||||||
|
Ok(_) => panic!("expected RuntimeError, got no error"),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load() -> Result<()> {
|
fn test_load() -> Result<()> {
|
||||||
let lua = Lua::new();
|
let lua = Lua::new();
|
||||||
|
|
Loading…
Reference in New Issue