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

Add option to limit the memory usage of Lua code #212

Merged
merged 57 commits into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
2669b63
add memory limit
Le0Developer May 8, 2022
b58a05e
add panic handler
Le0Developer May 8, 2022
bbfaf16
allow accessing and changing max memory
Le0Developer May 8, 2022
3d5cd61
add docs
Le0Developer May 8, 2022
f574206
add LuaMemoryError
Le0Developer May 8, 2022
b125d1e
fix compiler warning
Le0Developer May 8, 2022
bb69bcc
use 0 to denote unlimited memory and always use our allocator
Le0Developer May 8, 2022
d607896
add tests
Le0Developer May 8, 2022
22e4081
use python mem allocator and fix memory leak
Le0Developer May 8, 2022
b0306de
fix test by using a global so lua doesnt gc it
Le0Developer May 8, 2022
4103220
revert to libc allocator
Le0Developer May 8, 2022
158e922
remove memory_left property
Le0Developer May 8, 2022
007a913
add nogil flag
Le0Developer May 8, 2022
881d754
implement suggestions
Le0Developer May 20, 2022
ef0fb32
use lua allocator; add strict for set_max_memory; add docs
Le0Developer May 22, 2022
6d3463f
add more tests
Le0Developer May 22, 2022
5b4dc02
update docs
Le0Developer May 22, 2022
46e3507
add memory_used
Le0Developer May 22, 2022
68aad66
welp
Le0Developer May 22, 2022
d823152
update test
Le0Developer Aug 3, 2022
00a81fe
revert this change
Le0Developer Aug 3, 2022
9a8d5c1
Merge branch 'master' into feat/max-memory
scoder Aug 7, 2022
3aa4d1d
Minor code cleanups.
scoder Aug 7, 2022
d907edf
Improve docstring.
scoder Aug 7, 2022
bde8543
Fix tests.
scoder Aug 7, 2022
82502a6
ignore luajit
Le0Developer Aug 7, 2022
b1806fc
fix tests
Le0Developer Aug 7, 2022
a7386f3
catch more memory errors
Le0Developer Aug 7, 2022
4143da5
inherit from MemoryError you shall
Le0Developer Aug 7, 2022
c43fbb9
fix python2.x compat & actually skip initializing lua
Le0Developer Aug 8, 2022
aedeb6e
add test for compile and fix memory error assert
Le0Developer Aug 8, 2022
93f61be
possible fix for flaky test on win?
Le0Developer Aug 8, 2022
6d874d5
this should be at the bottom, no?
Le0Developer Aug 8, 2022
4b49057
Merge branch 'master' into feat/max-memory
scoder Aug 14, 2022
7ac2d46
Clean up code and fix a couple of issues.
scoder Aug 14, 2022
b479160
Improve wording in changelog.
scoder Aug 14, 2022
89540d0
Clarify and fix memory calculation logic in allocation function.
scoder Aug 14, 2022
215a262
use struct
Le0Developer Sep 17, 2022
86537ea
dont need these
Le0Developer Sep 17, 2022
17e8c65
update set_max_memory docstring
Le0Developer Sep 23, 2022
437c58d
use keyword arguments for better readability
Le0Developer Sep 23, 2022
95eb2a5
add support for unlimited memory (0)
Le0Developer Sep 23, 2022
4066683
Try to get by without dynamically allocating the memory state.
scoder Sep 26, 2022
f7659e4
count_base -> total
Le0Developer Sep 26, 2022
4287c23
limit fixes
Le0Developer Sep 26, 2022
5e2e673
attempt to document in readme
Le0Developer Sep 26, 2022
48af7ac
Update README.rst
Le0Developer Sep 26, 2022
0fc83c2
Update README.rst
Le0Developer Sep 26, 2022
7b49c69
Move struct declaration before first usage
scoder Sep 26, 2022
396e9dd
Update README.rst
Le0Developer Sep 26, 2022
21f2945
fix issues
Le0Developer Sep 26, 2022
40cf327
Update README.rst
Le0Developer Sep 26, 2022
07a6b84
Remove unnecessary initialisations
scoder Sep 26, 2022
8390d1f
Update README.rst
Le0Developer Sep 26, 2022
5ae6a85
Ignore fully qualified exception name in doctest (Py2/3).
scoder Sep 27, 2022
2b9cd96
size_t cannot be < 0
scoder Nov 16, 2022
79608b8
Prevent usage of our special limit value -1.
scoder Nov 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Lupa change log
2.0a1 (2022-??-??)
------------------

* GH#211: Option to limit memory usage
(patch by Leo Developer)

* GH#171: Python references in Lua are now more safely reference counted
to prevent garbage collection glitches.
(patch by Guilherme Dantas)
Expand Down
73 changes: 70 additions & 3 deletions lupa/_lupa.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ from __future__ import absolute_import
cimport cython

from libc.string cimport strlen, strchr
from libc.stdlib cimport malloc, free, realloc
from libc.stdio cimport fprintf, stderr, fflush
from lupa cimport lua
from .lua cimport lua_State

Expand Down Expand Up @@ -217,6 +219,10 @@ cdef class LuaRuntime:
Normally, it should return the now well-behaved object that can be
converted/wrapped to a Lua type. If the object cannot be precisely
represented in Lua, it should raise an ``OverflowError``.

* ``max_memory``: max memory usage this LuaRuntime can use in bytes.
Values below 40kb may cause lua panics.
(default: None, new in Lupa 2.0)

Example usage::

Expand All @@ -242,12 +248,24 @@ cdef class LuaRuntime:
cdef object _attribute_getter
cdef object _attribute_setter
cdef bint _unpack_returned_tuples
cdef size_t _max_memory
cdef size_t* _memory_left

def __cinit__(self, encoding='UTF-8', source_encoding=None,
attribute_filter=None, attribute_handlers=None,
bint register_eval=True, bint unpack_returned_tuples=False,
bint register_builtins=True, overflow_handler=None):
cdef lua_State* L = lua.luaL_newstate()
bint register_builtins=True, overflow_handler=None,
max_memory=None):
cdef lua_State* L
cdef size_t* memory_left
if max_memory is None:
L = lua.luaL_newstate()
else:
memory_left = <size_t*>malloc(sizeof(size_t))
if memory_left is NULL:
raise LuaError("Failed to allocate Lua runtime memory limiter")
memory_left[0] = <size_t>max_memory
L = lua.lua_newstate(<lua.lua_Alloc>&_lua_alloc_restricted, memory_left)
if L is NULL:
raise LuaError("Failed to initialise Lua runtime")
self._state = L
Expand All @@ -259,6 +277,8 @@ cdef class LuaRuntime:
raise ValueError("attribute_filter must be callable")
self._attribute_filter = attribute_filter
self._unpack_returned_tuples = unpack_returned_tuples
self._max_memory = max_memory
self._memory_left = memory_left

if attribute_handlers:
raise_error = False
Expand All @@ -276,9 +296,11 @@ cdef class LuaRuntime:
raise ValueError("attribute_filter and attribute_handlers are mutually exclusive")
self._attribute_getter, self._attribute_setter = getter, setter

if max_memory is not None:
# luaL_newstate already registers a panic handler
lua.lua_atpanic(L, &_lua_panic)
lua.luaL_openlibs(L)
self.init_python_lib(register_eval, register_builtins)
lua.lua_atpanic(L, <lua.lua_CFunction>1)

self.set_overflow_handler(overflow_handler)

Expand All @@ -287,6 +309,10 @@ cdef class LuaRuntime:
lua.lua_close(self._state)
self._state = NULL

@property
def max_memory(self):
return self._max_memory

@property
def lua_version(self):
"""
Expand Down Expand Up @@ -460,6 +486,13 @@ cdef class LuaRuntime:
lua.lua_settop(L, old_top)
unlock_runtime(self)

def set_max_memory(self, max_memory):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we need this. Do you see a use case for changing the memory limit later on?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using this in the tests atm

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using this to temporarily remove the limit to load trusted library and code and then restricting it once loading a user's code

if self._max_memory is None:
raise RuntimeError("max_memory must be set on LuaRuntime creation")
used = self._max_memory - self._memory_left[0]
self._memory_left[0] = max_memory - used
self._max_memory = max_memory

def set_overflow_handler(self, overflow_handler):
"""Set the overflow handler function that is called on failures to pass large numbers to Lua.
"""
Expand Down Expand Up @@ -1609,6 +1642,40 @@ cdef call_lua(LuaRuntime runtime, lua_State *L, tuple args):
push_lua_arguments(runtime, L, args)
return execute_lua_call(runtime, L, len(args))

# adapted from https://stackoverflow.com/a/9672205
cdef void* _lua_alloc_restricted(void* ud, void* ptr, size_t osize, size_t nsize) nogil:
cdef size_t* left = <size_t*>ud

if ptr is NULL:
# <http://www.lua.org/manual/5.2/manual.html#lua_Alloc>:
# When ptr is NULL, osize encodes the kind of object that Lua is
# allocating.
# Since we don’t care about that, just mark it as 0.
osize = 0

if nsize == 0:
free(ptr)
left[0] += osize # add old size to available memory
return NULL
elif nsize == osize:
return ptr
else:
if nsize > osize and left[0] < nsize - osize: # reached the limit
return NULL
ptr = realloc(ptr, nsize)
if ptr: # reallocation successful?
left[0] -= nsize + osize
return ptr

cdef int _lua_panic(lua_State *L) nogil:
cdef char* msg = lua.lua_tostring(L, -1)
if msg == NULL:
msg = "error object is not a string"
cdef char* message = "PANIC: unprotected error in call to Lua API (%s)\n"
fprintf(stderr, message, msg)
fflush(stderr)
return 0 # return to Lua to abort
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I admit that jumping to address 1 (as done before) isn't exactly a great error handler, but it feels like we should generally improve the way fatal errors are handled, rather than making them fatal but just in other ways.

OTOH, this is an improvement all by itself, so… shouldn't it be a separate PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error handler is the one that luaL_newstate registers


cdef object execute_lua_call(LuaRuntime runtime, lua_State *L, Py_ssize_t nargs):
cdef int result_status
cdef object result
Expand Down