Skip to content

Commit

Permalink
make "sandbox" and "splash" lua modules
Browse files Browse the repository at this point in the history
  • Loading branch information
kmike committed Dec 31, 2014
1 parent fc62715 commit 7cbcaa8
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 64 deletions.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
13 changes: 7 additions & 6 deletions splash/lua.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
44 changes: 23 additions & 21 deletions splash/scripts/sandbox.lua → splash/lua_modules/sandbox.lua
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
41 changes: 20 additions & 21 deletions splash/scripts/splash.lua → splash/lua_modules/splash.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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
41 changes: 25 additions & 16 deletions splash/qtrender_lua.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import os
import json
import functools
import itertools
Expand Down Expand Up @@ -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)
Expand All @@ -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 """
Expand All @@ -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):

Expand Down

0 comments on commit 7cbcaa8

Please sign in to comment.