@@ -290,22 +290,19 @@ pub unsafe fn handle_error(state: *mut ffi::lua_State, err: c_int) -> Result<()>
290
290
}
291
291
ffi:: LUA_ERRERR => Error :: ErrorError ( err_string) ,
292
292
ffi:: LUA_ERRMEM => {
293
- // This is not impossible to hit, but this library is not set up
294
- // to handle this properly. Lua does a longjmp on out of memory
295
- // (like all lua errors), but it can do this from a huge number
296
- // of lua functions, and it is extremely difficult to set up the
297
- // pcall protection for every lua function that might allocate.
298
- // If lua does this in an unprotected context, it will abort
299
- // anyway, so the best we can do right now is guarantee an abort
300
- // even in a protected context.
301
- println ! ( "Lua memory error, aborting!" ) ;
293
+ // TODO: We should provide lua with custom allocators that guarantee aborting on
294
+ // OOM, instead of relying on the system allocator. Currently, this is a
295
+ // theoretical soundness bug, because an OOM could trigger a longjmp out of
296
+ // arbitrary rust code that is unprotectedly calling a lua function marked as
297
+ // potentially causing memory errors, which is most of them.
298
+ eprintln ! ( "Lua memory error, aborting!" ) ;
302
299
process:: abort ( )
303
300
}
304
301
ffi:: LUA_ERRGCMM => {
305
- // This should be impossible, or at least is indicative of an
306
- // internal bug. Similarly to LUA_ERRMEM, this could indicate a
307
- // longjmp out of rust code, so we just abort.
308
- println ! ( "Lua error during __gc, aborting!" ) ;
302
+ // This should be impossible, since we wrap setmetatable to protect __gc
303
+ // metamethods, but if we do end up here then the same logic as setmetatable
304
+ // applies and we must abort.
305
+ eprintln ! ( "Lua error during __gc, aborting!" ) ;
309
306
process:: abort ( )
310
307
}
311
308
_ => lua_panic ! ( state, "internal error: unrecognized lua error code" ) ,
@@ -489,7 +486,44 @@ pub unsafe extern "C" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int {
489
486
}
490
487
}
491
488
492
- /// Does not call checkstack, uses 1 stack space
489
+ // Safely call setmetatable, if a __gc function is given, will wrap it in pcall, and panic on error.
490
+ pub unsafe extern "C" fn safe_setmetatable ( state : * mut ffi:: lua_State ) -> c_int {
491
+ if ffi:: lua_gettop ( state) < 2 {
492
+ push_string ( state, "not enough arguments to setmetatable" ) ;
493
+ ffi:: lua_error ( state) ;
494
+ }
495
+
496
+ // Wrapping the __gc method in setmetatable ONLY works because Lua 5.3 only honors the __gc
497
+ // method when it exists upon calling setmetatable, and ignores it if it is set later.
498
+ push_string ( state, "__gc" ) ;
499
+ if ffi:: lua_rawget ( state, -2 ) == ffi:: LUA_TFUNCTION {
500
+ unsafe extern "C" fn safe_gc ( state : * mut ffi:: lua_State ) -> c_int {
501
+ ffi:: lua_pushvalue ( state, ffi:: lua_upvalueindex ( 1 ) ) ;
502
+ ffi:: lua_insert ( state, 1 ) ;
503
+ if ffi:: lua_pcall ( state, 1 , 0 , 0 ) != ffi:: LUA_OK {
504
+ // If a user supplied __gc metamethod causes an error, we must always abort. We may
505
+ // be inside a protected context due to being in a callback, but inside an
506
+ // unprotected ffi call that can cause memory errors, so may be at risk of
507
+ // longjmping over arbitrary rust.
508
+ eprintln ! ( "Lua error during __gc, aborting!" ) ;
509
+ process:: abort ( )
510
+ } else {
511
+ ffi:: lua_gettop ( state)
512
+ }
513
+ }
514
+
515
+ ffi:: lua_pushcclosure ( state, safe_gc, 1 ) ;
516
+ push_string ( state, "__gc" ) ;
517
+ ffi:: lua_insert ( state, -2 ) ;
518
+ ffi:: lua_rawset ( state, -3 ) ;
519
+ } else {
520
+ ffi:: lua_pop ( state, 1 ) ;
521
+ }
522
+ ffi:: lua_setmetatable ( state, -2 ) ;
523
+ 0
524
+ }
525
+
526
+ // Does not call checkstack, uses 1 stack space
493
527
pub unsafe fn main_state ( state : * mut ffi:: lua_State ) -> * mut ffi:: lua_State {
494
528
ffi:: lua_rawgeti ( state, ffi:: LUA_REGISTRYINDEX , ffi:: LUA_RIDX_MAINTHREAD ) ;
495
529
let main_state = ffi:: lua_tothread ( state, -1 ) ;
0 commit comments