freezing globals & tables

99 views
Skip to first unread message

Abigail Brady

unread,
Aug 6, 2025, 7:48:51 AMAug 6
to lua-l
Hi!

I'm maintaining a long-term project and am looking to upgrade the Lua we are currently using to either 5.4.8 (or possibly just 5.5).

We had previously been using a slightly forked Lua 5.2.2 to add bitwise operators and an opcount counter. I am really pleased that we can get rid of those bits (thanks for much for LUA_MASKCOUNT_NUMOPS)

We have one remaining divergence from vanilla 5.2.2, which I would really love to get rid of, which is a patch adding a __usedindex metamethod, which is invoked by our freezing mechanism. I have a limited understanding of how this works exactly, and the code it touches in lvm.c has changed (luaV_settable vs luaV_finishset). My colleague explained our setup a little on the list here a few decades ago:


Anyway, the thing we accomplished with this was to freeze the globals entirely, so that chunks (which are semi-trusted code) run independently, and cannot alter the global table, either directory or indirectly, and furthermore are not allowed to alter system packages by e.g. pointing math.abs at something else, either directly or indirectly.

Thoughts about how to accomplish this in 5.4.8 or 5.5 soundly would be welcome.

Sainan

unread,
Aug 6, 2025, 8:32:52 AMAug 6
to lu...@googlegroups.com
I think the most common approach is to just give each untrusted script its own Lua state.

There's also the possibility to load chunks with their own _ENV/_G table, e.g.: load([[print"Hello"]], "", "bt", { print = function(...) print("Script says", ...) end })() --> Script says Hello

Or maybe more hacky would just be making a deep clone of the _G table to save/restore its state.

-- Sainan

Gé Weijers

unread,
Aug 6, 2025, 12:39:09 PMAug 6
to lu...@googlegroups.com


On Wed, Aug 6, 2025 at 4:48 AM Abigail Brady <elcat...@gmail.com> wrote:
Hi!

[...]

We have one remaining divergence from vanilla 5.2.2, which I would really love to get rid of, which is a patch adding a __usedindex metamethod, which is invoked by our freezing mechanism. I have a limited understanding of how this works exactly, and the code it touches in lvm.c has changed (luaV_settable vs luaV_finishset). My colleague explained our setup a little on the list here a few decades ago:


Anyway, the thing we accomplished with this was to freeze the globals entirely, so that chunks (which are semi-trusted code) run independently, and cannot alter the global table, either directory or indirectly, and furthermore are not allowed to alter system packages by e.g. pointing math.abs at something else, either directly or indirectly.

The problem you are running into here is that the global environment is no longer that special, it's just the first upvalue of a chunk. I can think of two approaches to get to your goal:

  1. Add some kind of 'flag' to the internal table structure that marks it as read_only, and disallow writing to them through regular access and through 'rawset', and block changing their metatables.
  2. Make lazy copies of the global environment while the semi-trusted script runs. The script should never be able to access the 'pristine' copy of the environment. Assuming that most scripts don't use more than a small percentage of the functions provided that may be a win.
#2 can be done in Lua, but it has way more overhead, and is probably not perfect.

I would look into #1 first, it's probably the approach with the lowest overhead. See if there is a way to add a read-only bit to the structure, and modify the interpreter to stop any writing.


Reply all
Reply to author
Forward
0 new messages