hey guys. thought i'd share what i have so far, if anyone's interested.
this is test code, i still need to:
- create file path validation
- prevent bytecode from loading
- decide on what goes into the whitelist
- decide what libraries i want
- actually incorporate all of this into a class
but i thought it was worth sharing for MORE FEEDBACK. and hey maybe it'd help anyone else who is interested, too.
main.cpp:
Code:
// mylua_test-2: main.cpp
#include <lua5.1/lua.hpp>
#include <iostream>
#include <string>
using namespace std;
string whitelist[] = {
"_G",
"ipairs",
"next",
"pairs",
"print",
"tostring"
};
int num_whitelist = (int)( sizeof(whitelist) / sizeof(whitelist[0]) );
bool isGlobalOkay( const string& global )
{
for( int i = 0; i < num_whitelist; i++ )
{
if( whitelist[i] == global )
return true;
}
return false;
}
bool isFileOkay( const string& filename )
{
// TODO: check to see that file path is 'okay'
// TODO: check for bytecode (see: LUA_SIGNATURE)
return true;
}
void scrubGlobalEnvironment( lua_State *state )
{
lua_pushnil( state ); // first key
while( lua_next( state, LUA_GLOBALSINDEX ) != 0 )
{
if( lua_type( state, -2 ) == LUA_TSTRING )
{
if( !isGlobalOkay( lua_tostring( state, -2 ) ) )
{
cout << "killing key: " << lua_tostring(state, -2) << endl;
// stack: -2 = key, -1 = value
lua_pop( state, 1 );
// stack: -1 = key
lua_pushvalue( state, -1 );
// stack: -2 = key, -1 = key
lua_pushnil( state );
// stack: -3 = key, -2 = key, -1 = nil
lua_rawset( state, LUA_GLOBALSINDEX );
// stack: -1 = key
}
else
{
cout << "key " << lua_tostring(state, -2) << " okay" << endl;
lua_pop( state, 1 ); // for lua_next
}
}
else
{
cout << "what." << endl;
// not a string. this should never happen? i think?
lua_pop( state, 1 ); // for lua_next
}
}
}
lua_State *createRestrictedLuaState()
{
// create new state
lua_State *state = luaL_newstate();
// load libraries, eg base:
lua_pushcfunction( state, luaopen_base );
lua_call( state, 0, 0);
// nil globals
scrubGlobalEnvironment( state );
return state;
}
int main( int argc, const char* argv[] )
{
lua_State *state = createRestrictedLuaState();
if( argc > 1 )
{
string filename = argv[1];
if( isFileOkay( filename ) )
{
luaL_loadfile( state, filename.c_str() );
// TODO: MAGIC
lua_pcall( state, 0, 0, 0 );
}
}
lua_close( state );
return 0;
}
test.lua:
Code:
#!/usr/bin/env lua
print'hello from test.lua'
print'_G:'
for k, v in pairs(_G) do
print(k, v)
end
output of ./main test.lua
Code:
killing key: xpcall
killing key: coroutine
killing key: newproxy
key _G okay
killing key: gcinfo
key pairs okay
key ipairs okay
killing key: _VERSION
killing key: setfenv
killing key: unpack
key tostring okay
killing key: tonumber
killing key: pcall
killing key: getfenv
killing key: rawset
killing key: type
killing key: setmetatable
key next okay
killing key: select
killing key: assert
killing key: load
killing key: rawget
killing key: loadstring
killing key: getmetatable
killing key: rawequal
killing key: dofile
killing key: collectgarbage
key print okay
killing key: error
killing key: loadfile
hello from test.lua
_G:
_G table: 0x8ac7d0
pairs function: 0x8ad1a0
ipairs function: 0x8ad100
tostring function: 0x8ad960
next function: 0x8ad6c0
print function: 0x8adb00
it looks okay!
PS: the TODO: MAGIC is there to remind me that i may want to create a second, lua-based sandbox for each script i load so that my c++/lua interface is consistent.