Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

how to make sol::thread running in sandbox #1360

Open
etorth opened this issue May 27, 2022 · 0 comments
Open

how to make sol::thread running in sandbox #1360

etorth opened this issue May 27, 2022 · 0 comments

Comments

@etorth
Copy link

etorth commented May 27, 2022

I am a lua newbie.

This is not a bug report nor a feature request.
The problem maybe simple for you guys but really drive me nuts, that how to make sol::thread run in sandbox.

I think many people trying to figure out this in a gentle way, as following links:
https://blog.rubenwardy.com/2020/07/26/sol3-script-sandbox/
https://stackoverflow.com/a/24358483/1490269
https://stackoverflow.com/questions/57030514/changing-the-env-of-a-lua-thread-using-c-api

Let me re-state it:
We have one lua state to interact with thousands of senders:

sol::state lua;
lua.open_libraries();
lua.script_file("user_defined_functions_and_variables.lua");

create_thousands_of_threads(lua); // one thread per sender
while(!quit){
    auto [event, event_sender] = wait_event();
    auto &th = find_thread(lua, event_sender);
    th.resume(event);
}

The lua state owns thousands of thread/coroutine, but this event-driven is sequential, means thread/coroutine will sequentially get the event to drive the yield/resume.

And each thread/coroutine yield/resume with their own environment, means:

  • Every thread/coroutine has a localized global variable table _G_sandbox, and it should override the default _G.
  • When thread/coroutine read-accesses a global variable, it firstly try to find in the _G_sandbox, if not find then try to find in _G, if still not find, return nil.
  • When thread/coroutine write-access to a global variable, it firstly try to find in the _G_sandbox, access it if found, else
    • if the accessing function is user-defined, create new global variable in _G_sandbox.
    • if this access is from lua standard libraries, report error and crash out. (optional if hard to implement)

When you finish the read, you know I want each thread/coroutine to run in sandbox that won't interfere with each other. They can read-access existing global variables, but shall not write/modify it, alternatively it should be in a localized _G_sandbox.

I currently have an implementation, looks working (or has bug I am not aware of), question is:

  • is this the supposed way for sol2 to implement it?
  • it uses raw lua functions lua_rawgeti/lua_rawseti not through sol2 interface, can this mess up any sol2 internal states?
  • Do we have better/simplier way to implement with sol2?
#include<bits/stdc++.h>
#include "sol/sol.hpp"

void checkError(const sol::protected_function_result &pfr)
{
    if(pfr.valid()){
        return;
    }

    const sol::error err = pfr;
    std::stringstream errStream(err.what());

    std::string errLine;
    while(std::getline(errStream, errLine, '\n')){
        std::cout << "callback error: " << errLine << std::endl;
    }
}

struct LuaThreadRunner
{
    sol::thread runner;
    sol::coroutine callback;

    LuaThreadRunner(sol::state &lua, const std::string &entry)
        : runner(sol::thread::create(lua.lua_state()))
        , callback(sol::state_view(runner.state())[entry])
    {}
};

int main()
{
    sol::state lua;
    lua.open_libraries();

    lua.script(R"(
        local _G = _G
        local error = error
        local coroutine = coroutine

        local _G_sandbox = {}
        function clearTLSTable()
            local threadId, inMainThread = coroutine.running()
            if inMainThread then
                error('call clearTLSTable in main thread')
            else
                _G_sandbox[threadId] = nil
            end
        end

        replaceEnv_metatable = {
            __index = function(table, key)
                local threadId, inMainThread = coroutine.running()
                if not inMainThread then
                    if _G_sandbox[threadId] ~= nil and _G_sandbox[threadId][key] ~= nil then
                        return _G_sandbox[threadId][key]
                    end
                end
                return _G[key]
            end,

            __newindex = function(table, key, value)
                local threadId, inMainThread = coroutine.running()
                if inMainThread then
                    _G[key] = value
                else
                    if _G_sandbox[threadId] == nil then
                        _G_sandbox[threadId] = {}
                    end
                    _G_sandbox[threadId][key] = value
                end
            end
        }
    )");

    sol::environment replaceEnv(lua, sol::create);
    replaceEnv[sol::metatable_key] = sol::table(lua["replaceEnv_metatable"]);

    // idea from: https://blog.rubenwardy.com/2020/07/26/sol3-script-sandbox/
    // set replaceEnv as default environment, otherwise I don't know how to setup replaceEnv to thread/coroutine

    lua_rawgeti(lua.lua_state(), LUA_REGISTRYINDEX, replaceEnv.registry_index());
    lua_rawseti(lua.lua_state(), LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);

    lua.script(R"(
        function coth_main(runner)
            local threadId, mainThread = coroutine.running()
            if mainThread then
                error('coth_main(runner) called in main thread', runner)
            end

            -- test require
            -- require should still work and accesses global variable: package

            local mod = require('io')
            print(mod)

            coroutine.yield()

            counter = 0     -- localized global varible tested
            counterMax = 10 --

            while counter < counterMax do
                print(string.format('runner %d counter %d, you can resume %d more times', runner, counter, counterMax - counter - 1))
                counter = counter + 1
                coroutine.yield()
            end

            clearTLSTable()
        end
    )");

    LuaThreadRunner runner1(lua, "coth_main");
    checkError(runner1.callback(1));

    LuaThreadRunner runner2(lua, "coth_main");
    checkError(runner2.callback(2));

    const auto fnResume = [](auto &callback, int index)
    {
        if(callback){
            checkError(callback());
        }
        else{
            std::cout << "runner " << index << " has exited" << std::endl;
        }
    };

    while(runner1.callback || runner2.callback){
        int event_from = 0;
        std::cout << "wait event from: ";
        std::cin >> event_from;

        switch(event_from){
            case 1: fnResume(runner1.callback, 1); break;
            case 2: fnResume(runner2.callback, 2); break;
            default: std::cout << "no runner " << event_from << std::endl;
        }
    }
    return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant