Skip to content

Stack level spoofing #40

@yogwoggf

Description

@yogwoggf

Problem

Autorun is visible in the stack levels of any Lua function it happens to execute or modify, either intentionally or on accident. I want to find a strong, robust solution to this problem, so I want to clarify on every detail.

Stack levels

Well, internally, these are called cframes in LuaJIT, but in the public API this is referred to as the stack (see lua_getstack). The issue for us is that because things like C detours need to call the callback, the VM sets up a stack frame for us that includes a lot of incriminating information, like chunk names, locals, upvalues, or function pointers. These are all accessible from Lua via these specific functions:

  • getfenv
  • debug.getinfo
  • debug.traceback
  • debug.getlocal
  • (strangely) setfenv
  • error (although unlikely since we handle our own errors)
  • The JIT engine (see lj_snap.c and lj_record.c), although I don't know if any scripts could figure things out with this fact (perhaps via the jit library, although I hope not.)

This is a somewhat-exhaustive list I procured, but apparently evil scripts can force Autorun callbacks out of stack space by wrapping things in 254 closures, and then calling a C function to determine if it's detoured. Once the Lua callback is executed, there will be an error and the C function will malfunction, which may lead to detection.

Solutions

Tons of detours

Technically speaking, we could detour all of the aforementioned functions and check for Autorun (quite simple), but I don't know about this, it feels leaky in a way. If there is a hole in even a single detour, ACs/bad actors have access to us.

Essentially, we'd strip any autorun strings from debug.traceback, and then for any level-based function, bound it to the current amount of levels minus one, and create an error just like how it normally would (invalid level). In the next section you can see a text block that shows how an example stack is set up in terms of the lua_State* and numeric levels used by functions like getfenv

Spoof at the VM level

This is quite dangerous in my opinion, and very shaky since call frames are INCREDIBLY optimized, to extents that mean any relying upon them could be fragile for future LuaJIT updates.

Anyways, the details of this are quite fuzzy, and it's still in the works, but I am thinking of a system which temporarily edits the VM's call frames to (again temporarily) slice off Autorun's call frame when any introspective function is called. This way, even despite anything, the introspection functions themselves would never know that Autorun is running.

E.g:

1. some_random_function_calling_a_detour (L->base) (LEVEL 0)
2. detour_c_function (L->base-1) (C) (LEVEL 1)
3. autorun_callback (L->base-2) (Lua) (L->stack) (LEVEL 2)

(logic src: https://sourcegraph.com/github.com/LuaJIT/LuaJIT@eec7a8016c3381b949b5d84583800d05897fa960/-/blob/src/lj_debug.c?L25 )
In this situation, L->stack would be set to L->stack - frame_sized(autorun_frame).
This would make LuaJIT functions unable to see the Autorun callback, perhaps with destructive side effects, but we'd revert it quickly.

Of course this is very complicated and honestly probably something to do much later, but it would be effective although incredibly complicated to implement.

I'm not sure what any other solutions to this are yet, but I feel as if making tons of detours will eventually be the way to go.

Metadata

Metadata

Assignees

Labels

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions