From 7cbcaa8c973cfe9a31bb97f7f1df84d3d7060628 Mon Sep 17 00:00:00 2001 From: Mikhail Korobov Date: Wed, 31 Dec 2014 20:36:50 +0500 Subject: [PATCH] make "sandbox" and "splash" lua modules --- MANIFEST.in | 1 + setup.py | 2 + splash/lua.py | 13 +++--- splash/{scripts => lua_modules}/sandbox.lua | 44 +++++++++++---------- splash/{scripts => lua_modules}/splash.lua | 41 ++++++++++--------- splash/qtrender_lua.py | 41 +++++++++++-------- 6 files changed, 78 insertions(+), 64 deletions(-) rename splash/{scripts => lua_modules}/sandbox.lua (86%) rename splash/{scripts => lua_modules}/splash.lua (99%) diff --git a/MANIFEST.in b/MANIFEST.in index fd52158d0..e35002215 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,5 +7,6 @@ include requirements.txt recursive-include docs *.rst recursive-include splash/scripts *.lua +recursive-include splash/lua_modules *.lua recursive-include splash/tests *.txt *.js *.ini recursive-include splash/vendor/harviewer/webapp *.js *.html *.css *.gif *.png *.swf *.html diff --git a/setup.py b/setup.py index f945b97cf..d35327c8d 100755 --- a/setup.py +++ b/setup.py @@ -46,6 +46,8 @@ def get_version(): 'vendor/harviewer/webapp/scripts/tabs/*.*', 'vendor/harviewer/webapp/har.js', + 'lua_modules/*.lua', + 'scripts/example.lua', 'scripts/splash.lua', 'scripts/sandbox.lua', diff --git a/splash/lua.py b/splash/lua.py index c167eae06..25bf42483 100644 --- a/splash/lua.py +++ b/splash/lua.py @@ -85,16 +85,17 @@ def _execute_in_sandbox(lua, script): Execute ``script`` in ``lua`` runtime using ``sandbox``. Return a (sandboxed) global environment for the executed script. - "Sandbox" table should be present in the environment. It should provide - ``Sandbox.run(untrusted_code)`` method and ``Sandbox.env`` table with a - global environment. See ``splash/scripts/sandbox.lua``. + "sandbox" module should be importable in the environment. + It should provide ``sandbox.run(untrusted_code)`` method and + ``sandbox.env`` table with a global environment. + See ``splash/lua_modules/sandbox.lua``. """ - Sandbox = lua.globals()["Sandbox"] - result = Sandbox.run(script) + sandbox = lua.eval("require('sandbox')") + result = sandbox.run(script) if result is not True: ok, res = result raise lupa.LuaError(res) - return Sandbox.env + return sandbox.env def _get_entrypoint(lua, script): diff --git a/splash/scripts/sandbox.lua b/splash/lua_modules/sandbox.lua similarity index 86% rename from splash/scripts/sandbox.lua rename to splash/lua_modules/sandbox.lua index 9879f6c05..5649e1e5a 100644 --- a/splash/scripts/sandbox.lua +++ b/splash/lua_modules/sandbox.lua @@ -1,9 +1,9 @@ ------------------- ------ Sandbox ----- +----- sandbox ----- ------------------- -Sandbox = {} +local sandbox = {} -Sandbox.env = { +sandbox.env = { -- -- 6.1 Basic Functions -- http://www.lua.org/manual/5.2/manual.html#6.1 @@ -134,7 +134,7 @@ Sandbox.env = { -- Fix metatables. Some of the functions are available -- via metatables of primitive types; disable them all. -- -Sandbox.fix_metatables = function() +sandbox.fix_metatables = function() -- 1. TODO: change string metatable to the sandboxed version -- (it is now just disabled) debug.setmetatable('', nil) @@ -154,33 +154,33 @@ end -- -- maximum memory (in KB) that can be used by Lua script -Sandbox.mem_limit = 10000 +sandbox.mem_limit = 10000 -function Sandbox.enable_memory_limit() - if Sandbox._memory_tracking_enabled then +function sandbox.enable_memory_limit() + if sandbox._memory_tracking_enabled then return end local mt = {__gc = function (u) - if collectgarbage("count") > Sandbox.mem_limit then + if collectgarbage("count") > sandbox.mem_limit then error("script uses too much memory") else setmetatable({}, getmetatable(u)) end end} setmetatable({}, mt) - Sandbox._memory_tracking_enabled = true + sandbox._memory_tracking_enabled = true end -- Maximum number of instructions that can be executed. -- XXX: the slowdown only becomes percievable at ~5m instructions. -Sandbox.instruction_limit = 1e6 -Sandbox.instruction_count = 0 +sandbox.instruction_limit = 1e6 +sandbox.instruction_count = 0 -function Sandbox.enable_instruction_limit() +function sandbox.enable_instruction_limit() local function _debug_step(event, line) - Sandbox.instruction_count = Sandbox.instruction_count + 1 - if Sandbox.instruction_count > Sandbox.instruction_limit then + sandbox.instruction_count = sandbox.instruction_count + 1 + if sandbox.instruction_count > sandbox.instruction_limit then error("script uses too much CPU", 2) end end @@ -190,9 +190,9 @@ end -- debug hooks are per-coroutine; use this function -- as a replacement for `coroutine.create` -function Sandbox.create_coroutine(f, ...) +function sandbox.create_coroutine(f, ...) return coroutine.create(function(...) - Sandbox.enable_instruction_limit() + sandbox.enable_instruction_limit() return f(...) end, ...) end @@ -202,11 +202,13 @@ end -- -- Lua 5.2 sandbox -- -function Sandbox.run(untrusted_code) - Sandbox.fix_metatables() - Sandbox.enable_instruction_limit() - Sandbox.enable_memory_limit() - local untrusted_function, message = load(untrusted_code, nil, 't', Sandbox.env) +function sandbox.run(untrusted_code) + sandbox.fix_metatables() + sandbox.enable_instruction_limit() + sandbox.enable_memory_limit() + local untrusted_function, message = load(untrusted_code, nil, 't', sandbox.env) if not untrusted_function then return nil, message end return pcall(untrusted_function) end + +return sandbox diff --git a/splash/scripts/splash.lua b/splash/lua_modules/splash.lua similarity index 99% rename from splash/scripts/splash.lua rename to splash/lua_modules/splash.lua index ae63bcce0..cbba92813 100644 --- a/splash/scripts/splash.lua +++ b/splash/lua_modules/splash.lua @@ -57,7 +57,7 @@ end -- wraps async methods to `coroutine.yield` and fixes Lua <-> Python -- error handling. -- -Splash = {} +local Splash = {} Splash.__index = Splash function Splash.create(py_splash) @@ -82,7 +82,6 @@ function Splash.create(py_splash) return self end - -- -- Create jsfunc method from jsfunc_private. -- It is required to handle errors properly. @@ -92,25 +91,6 @@ function Splash:jsfunc(...) return unwraps_errors(func) end - --- a helper function -function Splash:_wait_restart_on_redirects(time, max_redirects) - if not time then - return true - end - - local redirects_remaining = max_redirects - while redirects_remaining do - local ok, reason = self:wait{time, cancel_on_redirect=true} - if reason ~= 'redirect' then - return ok, reason - end - redirects_remaining = redirects_remaining - 1 - end - error("Maximum number of redirects happen") -end - - -- -- Default rendering script which implements -- a common workflow: go to a page, wait for some time @@ -156,3 +136,22 @@ function Splash:go_and_wait(args) self:set_viewport(args.viewport) end end + + +function Splash:_wait_restart_on_redirects(time, max_redirects) + if not time then + return true + end + + local redirects_remaining = max_redirects + while redirects_remaining do + local ok, reason = self:wait{time, cancel_on_redirect=true} + if reason ~= 'redirect' then + return ok, reason + end + redirects_remaining = redirects_remaining - 1 + end + error("Maximum number of redirects happen") +end + +return Splash diff --git a/splash/qtrender_lua.py b/splash/qtrender_lua.py index 53e7a25bf..2bed933cb 100644 --- a/splash/qtrender_lua.py +++ b/splash/qtrender_lua.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import +import os import json import functools import itertools @@ -456,24 +457,14 @@ def result_content_type(self): return None return str(self._result_content_type) - def get_wrapper(self): - """ - Return a Lua wrapper for this object. - """ - # FIXME/TODO: cache file contents? - self.lua.execute(get_script_source("sandbox.lua")) - self.lua.execute(get_script_source("splash.lua")) - wrapper = self.lua.globals()["Splash"] - return wrapper.create(self) - def start_main(self, lua_source): """ Start "main" function and return it as a coroutine. """ - splash_obj = self.get_wrapper() + splash_obj = self._get_wrapper() if self.sandboxed: main, env = get_main_sandboxed(self.lua, lua_source) - main_coro = self._Sandbox.create_coroutine(main) + main_coro = self._sandbox.create_coroutine(main) return main_coro(splash_obj) else: main, env = get_main(self.lua, lua_source) @@ -483,14 +474,19 @@ def instruction_count(self): if not self.sandboxed: return -1 try: - return self._Sandbox.instruction_count + return self._sandbox.instruction_count except Exception as e: print(e) return -1 + def _get_wrapper(self): + """ Return a Lua wrapper for this object. """ + wrapper = self.lua.eval("require('splash')") + return wrapper.create(self) + @property - def _Sandbox(self): - return self.lua.globals()["Sandbox"] + def _sandbox(self): + return self.lua.eval("require('sandbox')") def run_async_command(self, cmd): """ Execute _AsyncCommand """ @@ -510,9 +506,22 @@ def _create_runtime(self): Return a restricted Lua runtime. Currently it only allows accessing attributes of this object. """ - return get_new_runtime( + runtime = get_new_runtime( attribute_handlers=(self._attr_getter, self._attr_setter) ) + self._setup_lua_paths(runtime) + return runtime + + def _setup_lua_paths(self, lua): + packages_path = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + 'lua_modules' + ) + ) + lua.execute(""" + package.path = "{packages_path}/?.lua;" .. package.path + """.format(packages_path=packages_path)) def _attr_getter(self, obj, attr_name):