Giter Club home page Giter Club logo

Comments (7)

kyren avatar kyren commented on July 17, 2024

This shouldn't compile, this is 100% a soundness issue, and I have a fix incoming... I think. I need to do a lot more testing to make sure this fix is right.

As soon as I fix the soundness issue, I'll help you through what you're trying to do, because like I said this SHOULDN'T compile, but it does because of a really bad issue :(

This is a GREAT find, thank you so much for finding this :(

from rlua.

kyren avatar kyren commented on July 17, 2024

I think a2615a8 fixes this issue, in that the example program you gave no longer compiles and there is now a compiletest_rs test for this specific issue.

The issue allowed parameters passed to callbacks created from Lua::scope to be stored outside the callback, which should never be allowed. Handles that have a 'lua lifetime are not supposed to be stored "long term" at all, really, since they reference whatever specific instance of Lua that they come from and can't outlive it. The Lua instance passed to callbacks is ephemeral, it lives only as long as the callback, so when you convince a handle value to escape the callback, it's referencing a dropped Lua, which is what caused this crash.

This was only a problem inside Lua::scope, because normally functions must be 'static anyway so the borrow checker wouldn't allow sneaking a non-'static reference out. The fix is to add a lifetime bound in Scope::create_function / Scope::create_function_mut to make sure that the 'scope outlives 'callback. It's a bit subtle, but since 'scope must outlive 'callback, and the callback itself must outlive 'scope, this means that the callback can't exfiltrate anything with the 'callback lifetime at all, which is what we want.

This fix probably doesn't help your actual problem, since it just makes this example not compile. The good news is you might not need Lua::scope at all to do what you want to do, that's really only for when you have non-'static types other than Lua handles to deal with. If you simply want to be able to store Lua handles long term, try using the Lua registry with Lua::create_registry_value.

from rlua.

luteberget avatar luteberget commented on July 17, 2024

Thanks so much for the explanation!

I guess I thought that Rust references to Lua values could be carried around as long as the top-level Lua instance is alive (since they are refcounted), but I'm not familiar enough with the low-level Lua API to see exactly how this would work.

The Lua::scope was intended to limit the mutability of the program struct, so I think it still makes sense. Instead of storing LuaFunction in the Program struct, I'll try storing a LuaRegistryKey and also the &Lua top-level instance to delete from the registry on drop. Something like this:

struct StoredFunction<'a>(&'a Lua, Option<LuaRegistryKey>);

impl<'a> StoredFunction<'a> {
    pub fn new(lua :&'a Lua, f :LuaFunction) -> LuaResult<StoredFunction<'a>> {
        Ok(StoredFunction(lua, Some(lua.create_registry_value(f)?)))
    }
    
    pub fn call<A: rlua::ToLuaMulti<'a>, R: rlua::FromLuaMulti<'a>>(&self, args: A) -> LuaResult<R> {
        self.0.registry_value::<LuaFunction>(self.1.as_ref().expect("Stored function error"))
            .and_then(|x| x.call(args))
    }
}

impl<'a> Drop for StoredFunction<'a> {
    fn drop(&mut self) {
        self.0.remove_registry_value(self.1.take().unwrap()).unwrap();
    }
}

from rlua.

kyren avatar kyren commented on July 17, 2024

I guess I thought that Rust references to Lua values could be carried around as long as the top-level Lua instance is alive (since they are refcounted), but I'm not familiar enough with the low-level Lua API to see exactly how this would work.

There are actually multiple Lua instances, when a callback is called by Lua and given lua_State pointer, a new "ephemeral" Lua instance is created. The fact that all the Lua instances are potentially different and the specific 'lua lifetime used only ends up mattering in Scope methods, because like I said normally callbacks must be 'static anyway.

If you're okay with storing a a &Lua in a type, you can actually just store normal reference types, it's just generally a huge pain to deal with persistently borrowing &Lua, and you tend to run head-first into self borrows, which are even more of a pain to deal with. The reason that RegistryKey exists really is to save you from having to store a &Lua somewhere in the first place. If you wanted a way to get handles out via a "register" callback with, one way is to create a table in Rust, then make a closure that closes over that table and pass that into Lua as the "register" function, and have that closure populate the table. You can also turn the API around to have Lua just return a table of functions, or use an agreed upon global variable to store them.

I know the handle based API has some limitations and sometimes you could envision the API without those limitations, but generally this is because handles are just not designed to be stored inside Lua itself, so anything involving capturing them in callbacks or getting them out of callbacks is probably going to be painful.

from rlua.

kyren avatar kyren commented on July 17, 2024

In the meantime, I have found another soundness bug along the same lines with a closure capturing its own arguments into an owned value rather than a captured reference, and have had to attempt another fix (1a9c50f). If anybody is confident in their lifetime knowledge and wants to test this out and try to break it, I would be very appreciative, because I'm not very confident there isn't another issue here. I at least have coverage of all the issues with compiletest_rs, but I would really appreciate another set of eyes here.

from rlua.

kyren avatar kyren commented on July 17, 2024

I went ahead and released 0.14.2 as well with the second fix, hopefully I'm not missing any other safety issues here.

from rlua.

luteberget avatar luteberget commented on July 17, 2024

Great, thanks again for your help.

Just for information, I went ahead with storing the functions in a global table on the Lua side, and then creating a Rc<RefCell<Option<Context>>> to use as a parameter for the functions, to be able to remove the Context on the Rust side again after executing the Lua functions from the global table.

from rlua.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.