wplua: implement sandboxing of scripts

All loaded scripts can now be run in a protected environment
so that they cannot do any harm to the host or to each other
This commit is contained in:
George Kiagiadakis 2020-12-15 18:28:28 +02:00
parent b9ca4e1425
commit 8b4c5af49c
7 changed files with 189 additions and 9 deletions

6
lib/wplua/gresource.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/freedesktop/pipewire/wireplumber/wplua/">
<file compressed="true">sandbox.lua</file>
</gresource>
</gresources>

View file

@ -7,8 +7,15 @@ wplua_lib_sources = [
'wplua.c',
]
wplua_resources = gnome.compile_resources(
'wplua-resources',
'gresource.xml',
c_name: '_wplua',
extra_args: '--manual-register',
source_dir: meson.current_source_dir())
wplua_lib = static_library('wplua-' + wireplumber_api_version,
wplua_lib_sources,
[ wplua_lib_sources, wplua_resources ],
c_args : [
'-D_GNU_SOURCE',
'-DG_LOG_USE_STRUCTURED',

75
lib/wplua/sandbox.lua Normal file
View file

@ -0,0 +1,75 @@
-- WirePlumber
--
-- Copyright © 2020 Collabora Ltd.
-- @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- Based on https://github.com/kikito/sandbox.lua
-- Copyright © 2013 Enrique García Cota
--
-- SPDX-License-Identifier: MIT
SANDBOX_ENV_LIST = {}
local SANDBOX_ENV = {}
-- List of safe functions and packages
([[
_VERSION assert error ipairs next pairs
pcall select tonumber tostring type xpcall
table utf8
math.abs math.acos math.asin math.atan math.ceil
math.cos math.deg math.exp math.tointeger math.floor math.fmod
math.huge math.ult math.log math.maxinteger math.mininteger math.max
math.min math.modf math.pi math.rad math.random
math.sin math.sqrt math.tan math.type
string.byte string.char string.find string.format string.gmatch
string.gsub string.len string.lower string.match string.reverse
string.sub string.upper
os.clock os.difftime os.time os.date os.getenv
]]):gsub('%S+', function(id)
local module, method = id:match('([^%.]+)%.([^%.]+)')
if module then
SANDBOX_ENV[module] = SANDBOX_ENV[module] or {}
SANDBOX_ENV[module][method] = _G[module][method]
else
SANDBOX_ENV[id] = _G[id]
end
end)
-- Additionally export everything in SANDBOX_EXPORT
if type(SANDBOX_EXPORT) == "table" then
for k, v in pairs(SANDBOX_EXPORT) do
SANDBOX_ENV[k] = v
end
end
-- Additionally protect packages from malicious scripts trying to override methods
for k, v in pairs(SANDBOX_ENV) do
if type(v) == "table" then
SANDBOX_ENV[k] = setmetatable({}, {
__index = v,
__newindex = function(_, attr_name, _)
error('Can not modify ' .. k .. '.' .. attr_name .. '. Protected by the sandbox.')
end
})
end
end
function sandbox(chunk)
-- chunk's environment will be an empty table with __index
-- to access our SANDBOX_ENV (without being able to write it)
local env = setmetatable({}, {
__index = SANDBOX_ENV,
})
-- set it as the chunk's 1st upvalue (__ENV)
debug.setupvalue(chunk, 1, env)
-- store the chunk's environment so that it is not garbage collected
table.insert(SANDBOX_ENV_LIST, env)
-- execute the chunk
chunk()
end

View file

@ -10,6 +10,10 @@
#include "private.h"
#include <wp/wp.h>
#define URI_SANDBOX "resource:///org/freedesktop/pipewire/wireplumber/wplua/sandbox.lua"
extern void _wplua_register_resource (void);
static void
_wplua_openlibs (lua_State *L)
{
@ -21,7 +25,7 @@ _wplua_openlibs (lua_State *L)
/* {LUA_COLIBNAME, luaopen_coroutine}, */
{LUA_TABLIBNAME, luaopen_table},
/* {LUA_IOLIBNAME, luaopen_io}, */
/* {LUA_OSLIBNAME, luaopen_os}, */
{LUA_OSLIBNAME, luaopen_os},
{LUA_STRLIBNAME, luaopen_string},
{LUA_MATHLIBNAME, luaopen_math},
{LUA_UTF8LIBNAME, luaopen_utf8},
@ -53,10 +57,16 @@ _wplua_typeclass___call (lua_State *L)
lua_State *
wplua_new (void)
{
static gboolean resource_registered = FALSE;
lua_State *L = luaL_newstate ();
wp_debug ("initializing lua_State %p", L);
if (!resource_registered) {
_wplua_register_resource ();
resource_registered = TRUE;
}
_wplua_openlibs (L);
_wplua_init_gboxed (L);
_wplua_init_gobject (L);
@ -89,6 +99,16 @@ wplua_free (lua_State * L)
lua_close (L);
}
void
wplua_enable_sandbox (lua_State * L)
{
g_autoptr (GError) error = NULL;
wp_debug ("enabling Lua sandbox");
if (!wplua_load_uri (L, URI_SANDBOX, &error)) {
wp_critical ("Failed to load sandbox: %s", error->message);
}
}
void
wplua_register_type_methods (lua_State * L, GType type,
lua_CFunction constructor, const luaL_Reg * methods)
@ -128,19 +148,28 @@ wplua_register_type_methods (lua_State * L, GType type,
}
}
gboolean
static gboolean
_wplua_load_buffer (lua_State * L, const gchar *buf, gsize size,
const gchar * name, GError **error)
{
int ret;ret = luaL_loadbuffer (L, buf, size, name);
int ret;
int sandbox = 0;
/* wrap with sandbox() if it's loaded */
if (lua_getglobal (L, "sandbox") == LUA_TFUNCTION)
sandbox = 1;
else
lua_pop (L, 1);
ret = luaL_loadbuffer (L, buf, size, name);
if (ret != LUA_OK) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"Failed to compile: %s", lua_tostring (L, -1));
lua_pop (L, 1);
lua_pop (L, sandbox + 1);
return FALSE;
}
ret = lua_pcall (L, 0, 0, 0);
ret = lua_pcall (L, sandbox, 0, 0);
if (ret != LUA_OK) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"Failed to run: %s", lua_tostring (L, -1));
@ -177,8 +206,8 @@ wplua_load_uri (lua_State * L, const gchar *uri, GError **error)
file = g_file_new_for_uri (uri);
if (!(bytes = g_file_load_bytes (file, NULL, NULL, &err))) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"Failed to load '%s': %s", uri, err->message);
g_propagate_prefixed_error (error, err, "Failed to load '%s':", uri);
err = NULL;
return FALSE;
}

View file

@ -20,6 +20,8 @@ G_BEGIN_DECLS
lua_State * wplua_new (void);
void wplua_free (lua_State * L);
void wplua_enable_sandbox (lua_State * L);
void wplua_register_type_methods (lua_State * L, GType type,
lua_CFunction constructor, const luaL_Reg * methods);

View file

@ -7,7 +7,7 @@ common_env = [
test(
'test-wplua',
executable('test-toml', 'wplua.c', dependencies: common_deps),
executable('test-wplua', 'wplua.c', dependencies: common_deps),
env: common_env,
workdir : meson.current_source_dir(),
)

View file

@ -403,6 +403,66 @@ test_wplua_signals ()
wplua_free (L);
}
static void
test_wplua_sandbox ()
{
g_autoptr (GError) error = NULL;
lua_State *L = wplua_new ();
wplua_register_type_methods(L, TEST_TYPE_OBJECT,
l_test_object_new, l_test_object_methods);
const gchar code[] =
"SANDBOX_EXPORT = {\n"
" Test = TestObject.new,\n"
" Table = { test = 'foobar' }\n"
"}\n";
wplua_load_buffer (L, code, sizeof (code) - 1, &error);
g_assert_no_error (error);
wplua_enable_sandbox (L);
const gchar code2[] =
"o = TestObject.new()\n";
wplua_load_buffer (L, code2, sizeof (code2) - 1, &error);
g_debug ("expected error: %s", error ? error->message : "null");
g_assert_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED);
g_clear_error (&error);
const gchar code3[] =
"o = Test()\n";
wplua_load_buffer (L, code3, sizeof (code3) - 1, &error);
g_assert_no_error (error);
const gchar code4[] =
"assert(string.len(Table.test) == 6)\n";
wplua_load_buffer (L, code4, sizeof (code4) - 1, &error);
g_assert_no_error (error);
const gchar code5[] =
"o:call('change', 'by Lua', 55)\n";
wplua_load_buffer (L, code5, sizeof (code5) - 1, &error);
g_debug ("expected error: %s", error ? error->message : "null");
g_assert_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED);
g_clear_error (&error);
const gchar code6[] =
"string.test = 'hello world'\n";
wplua_load_buffer (L, code6, sizeof (code6) - 1, &error);
g_debug ("expected error: %s", error ? error->message : "null");
g_assert_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED);
g_clear_error (&error);
const gchar code7[] =
"Table.test = 'hello world'\n";
wplua_load_buffer (L, code7, sizeof (code7) - 1, &error);
g_debug ("expected error: %s", error ? error->message : "null");
g_assert_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED);
g_clear_error (&error);
wplua_free (L);
}
gint
main (gint argc, gchar *argv[])
{
@ -414,6 +474,7 @@ main (gint argc, gchar *argv[])
g_test_add_func ("/wplua/properties", test_wplua_properties);
g_test_add_func ("/wplua/closure", test_wplua_closure);
g_test_add_func ("/wplua/signals", test_wplua_signals);
g_test_add_func ("/wplua/sandbox", test_wplua_sandbox);
return g_test_run ();
}