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.
|
||||
#[cfg(any(feature = "lua53", feature = "lua52"))]
|
||||
GarbageCollectorError(StdString),
|
||||
/// Potentially unsafe action in safe mode.
|
||||
SafetyError(StdString),
|
||||
/// Setting memory limit is not available.
|
||||
///
|
||||
/// 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) => {
|
||||
write!(fmt, "garbage collector error: {}", msg)
|
||||
}
|
||||
Error::SafetyError(ref msg) => {
|
||||
write!(fmt, "safety error: {}", msg)
|
||||
},
|
||||
Error::MemoryLimitNotAvailable => {
|
||||
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,
|
||||
extra: Arc<Mutex<ExtraData>>,
|
||||
ephemeral: bool,
|
||||
safe: bool,
|
||||
// Lua has lots of interior mutability, should not be RefUnwindSafe
|
||||
_no_ref_unwind_safe: PhantomData<UnsafeCell<()>>,
|
||||
}
|
||||
|
@ -108,17 +109,63 @@ impl Drop for 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 {
|
||||
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.
|
||||
///
|
||||
/// The created Lua state would have safety guarantees and would not allow to load unsafe
|
||||
/// standard libraries or C modules.
|
||||
///
|
||||
/// [`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(
|
||||
extra_data: *mut c_void,
|
||||
ptr: *mut c_void,
|
||||
|
@ -173,67 +220,28 @@ impl Lua {
|
|||
new_ptr
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let mem_info = Box::into_raw(Box::new(MemoryInfo {
|
||||
used_memory: 0,
|
||||
memory_limit: 0,
|
||||
}));
|
||||
let mem_info = Box::into_raw(Box::new(MemoryInfo {
|
||||
used_memory: 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::lua_pop(state, 1);
|
||||
ffi::luaL_requiref(state, cstr!("_G"), ffi::luaopen_base, 1);
|
||||
ffi::lua_pop(state, 1);
|
||||
|
||||
let mut lua = Lua::init_from_ptr(state);
|
||||
lua.ephemeral = false;
|
||||
lua.extra.lock().unwrap().mem_info = mem_info;
|
||||
let mut lua = Lua::init_from_ptr(state);
|
||||
lua.ephemeral = false;
|
||||
lua.extra.lock().unwrap().mem_info = mem_info;
|
||||
|
||||
mlua_expect!(
|
||||
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| {
|
||||
mlua_expect!(
|
||||
protect_lua_closure(lua.main_state, 0, 0, |state| {
|
||||
load_from_std_lib(state, libs);
|
||||
})
|
||||
}
|
||||
}),
|
||||
"Error during loading standard libraries"
|
||||
);
|
||||
|
||||
lua
|
||||
}
|
||||
|
||||
/// Constructs a new Lua instance from the existing state.
|
||||
|
@ -292,10 +300,62 @@ impl Lua {
|
|||
main_state: main_state,
|
||||
extra: extra,
|
||||
ephemeral: true,
|
||||
safe: false,
|
||||
_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.
|
||||
// The returned value then pushed to the Lua stack.
|
||||
#[doc(hidden)]
|
||||
|
@ -1556,9 +1616,36 @@ impl Lua {
|
|||
main_state: self.main_state,
|
||||
extra: self.extra.clone(),
|
||||
ephemeral: true,
|
||||
safe: self.safe,
|
||||
_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.
|
||||
|
|
|
@ -20,12 +20,15 @@ impl StdLib {
|
|||
pub const PACKAGE: StdLib = StdLib(1 << 8);
|
||||
#[cfg(feature = "luajit")]
|
||||
pub const JIT: StdLib = StdLib(1 << 9);
|
||||
|
||||
/// `ffi` (unsafe) module `feature = "luajit"`
|
||||
#[cfg(feature = "luajit")]
|
||||
pub const FFI: StdLib = StdLib(1 << 10);
|
||||
pub const DEBUG: StdLib = StdLib(1 << 31); // always highest bit
|
||||
pub const FFI: StdLib = StdLib(1 << 30);
|
||||
/// `debug` (unsafe) module
|
||||
pub const DEBUG: StdLib = StdLib(1 << 31);
|
||||
|
||||
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 {
|
||||
(self & lib).0 != 0
|
||||
|
|
|
@ -4,9 +4,49 @@ use std::sync::Arc;
|
|||
use std::{error, f32, f64, fmt};
|
||||
|
||||
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]
|
||||
fn test_load() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
|
Loading…
Reference in New Issue