Skip to content

Instantly share code, notes, and snippets.

@MineRobber9000
Created July 16, 2025 23:31
Show Gist options
  • Save MineRobber9000/64ec7fd713091dd87626a1c0e9efae25 to your computer and use it in GitHub Desktop.
Save MineRobber9000/64ec7fd713091dd87626a1c0e9efae25 to your computer and use it in GitHub Desktop.
proof of concept for this thing wojbie said that one time https://discord.com/channels/477910221872824320/477911902152949771/1359486340613406870

Top Level Coroutine Injection in ComputerCraft

Yep, I did that.

Why?

I was bored, plus I randomly came across Wojbie mentioning the idea while searching for TLCO stuff in the Discord.

How?

Basically we get a handle to the top thread (the BIOS itself), and then muck around in runUntilLimit's insides (from parallel) to add a thread to the thread list.

Yeah, but how do you get the top thread handle?

Both methods for getting the top thread handle abuse the function environment of rednet.run (which is how RedRun works). By redefining os.pullEventRaw, we can run code in the context of the rednet.run thread (the "rednet thread", as I'll call it from now on) which has upvalues in the context of our program.

The original "exploit", if you could call it that, gets a handle to the rednet thread before restoring the original os table--the exploit temporarily replaces os in the rednet.run context with a proxy object that redefined pullEventRaw and passed any other accesses to the real os--and awaiting the event as it should. In addition to this, the exploit redefines coroutine.resume globally. If we haven't checked in with the rednet thread yet, it just passes through to the original coroutine.resume. If we have the rednet thread's handle, and the thread we're trying to resume is the rednet thread, we must be in the top thread, so we get a handle to that.

After 051c70a, however, the exploit becomes a lot easier. That commit implemented the can_wrap_errors function in the cc.internal.exception library, which parallel uses to decide if it wants to wrap exceptions or not. As part of this implementation, parallel calls a "try barrier" function, which stores information on the stack that cc.internal.exception can use to find out if parallel was pcall'd or not. This information comes in the form of a handle to the parent thread--so we just nab that off the stack and we're good.

Okay, but what exactly do you do to make parallel accept your injected coroutines?

Pre-88cb03b, runUntilLimit was passed a list of coroutines (called _routines) and a limit (the minimum amount of coroutines that could still be running without parallel.waitForAny/All returning, minus 1; called _limit, though we don't touch it here). It then stores the amount of coroutines it starts with (called count), creates a table to hold event filters (called tFilters, and indexed with the coroutine object itself), and counts the living coroutines (called living, initialized to count). It iterates over for i=1, count, resuming each coroutine if it exists (i.e; not nil) and the event matches its filter. Dead coroutines are replaced with nil, and, if the living count is less than or equal to the _limit, it returns. (It did this in a weird way, with 2 loops for some reason, but that's not important to understanding how this works.) Post-88cb03b, it works mostly the same, with some renames (_routines became threads and _limit became limit) and eliminating tFilters in favor of storing the coroutine and its filter in a table, which is stored in threads.

Basically, we create the coroutine, resume it for the first time, put its filter wherever the version of parallel we're running wants it, and then increase count and living to account for the new coroutine.

-- top level coroutine injection for post-051c70a computercraft
-- (anything released after february 13, 2025)
local function find_local(thread, level, name)
local i, _name, value=1
repeat
_name, value = debug.getlocal(thread, level, i)
if _name==name then return value, i end
i = i + 1
until _name==nil
end
local try_barrier = debug.getregistry().cc_try_barrier
local top_thread
getfenv(rednet.run).os = setmetatable({
pullEventRaw = function(...)
local dbg_lvl, dbg = 3, debug.getinfo(3)
if not (dbg and dbg.func==try_barrier) then
dbg_lvl, dbg = 0, {}
while dbg and dbg.func~=try_barrier do
dbg_lvl = dbg_lvl+1
dbg = debug.getinfo(dbg_lvl)
end
end
if dbg then
local _, parent = debug.getlocal(dbg_lvl, 1)
top_thread = parent.co
else
printError("Did not find try barrier")
end
getfenv(rednet.run).os = _G.os
return _G.os.pullEventRaw(...)
end
},{__index=os})
sleep(0)
print("top thread:", tostring(assert(top_thread)))
local threads = find_local(top_thread,1,"threads")
local count, count_i = find_local(top_thread,1,"count")
local living, living_i = find_local(top_thread,1,"living")
local function injected()
sleep(5)
term.clear()
term.setCursorPos(1,1)
print("This function was injected by the program.")
end
local co = coroutine.create(injected)
local _, filter = coroutine.resume(co)
threads[count+1] = {co = co, filter = filter}
debug.setlocal(top_thread, 1, count_i, count+1)
debug.setlocal(top_thread, 1, living_i, living+1)
print("Injected successfully.")
-- top level coroutine injection for post-88cb03b computercraft
-- (after february 9th, 2025)
-- there's also a `post_051c70a.lua` that should work in all of the same versions
-- but it uses a (only slightly) less cursed way to get the top thread
local function find_local(thread, level, name)
local i, _name, value=1
repeat
_name, value = debug.getlocal(thread, level, i)
if _name==name then return value, i end
i = i + 1
until _name==nil
end
local rednet_thread, top_thread
getfenv(rednet.run).os = setmetatable({
pullEventRaw = function(...)
rednet_thread = coroutine.running()
getfenv(rednet.run).os = _G.os
return _G.os.pullEventRaw(...)
end
},{__index=os})
local _resume = coroutine.resume
function coroutine.resume(co, ...)
if not rednet_thread then return _resume(co, ...) end
if co==rednet_thread then
top_thread = coroutine.running()
coroutine.resume = _resume
end
return _resume(co,...)
end
os.queueEvent("spray")
while top_thread==nil do os.pullEvent() end
print("top thread:", tostring(top_thread))
print("rednet thread:", tostring(rednet_thread))
local threads = find_local(top_thread,1,"threads")
local count, count_i = find_local(top_thread,1,"count")
local living, living_i = find_local(top_thread,1,"living")
local function injected()
sleep(5)
term.clear()
term.setCursorPos(1,1)
print("This function was injected by the program.")
end
local co = coroutine.create(injected)
local _, filter = coroutine.resume(co)
threads[count+1] = {co = co, filter = filter}
debug.setlocal(top_thread, 1, count_i, count+1)
debug.setlocal(top_thread, 1, living_i, living+1)
print("Injected successfully.")
-- top level coroutine injection for pre-88cb03b computercraft
-- (versions released before february 9th, 2025)
local function find_local(thread, level, name)
local i, _name, value=1
repeat
_name, value = debug.getlocal(thread, level, i)
if _name==name then return value, i end
i = i + 1
until _name==nil
end
local rednet_thread, top_thread
getfenv(rednet.run).os = setmetatable({
pullEventRaw = function(...)
rednet_thread = coroutine.running()
getfenv(rednet.run).os = _G.os
return _G.os.pullEventRaw(...)
end
},{__index=os})
local _resume = coroutine.resume
function coroutine.resume(co, ...)
if not rednet_thread then return _resume(co, ...) end
if co==rednet_thread then
top_thread = coroutine.running()
coroutine.resume = _resume
end
return _resume(co,...)
end
os.queueEvent("spray")
while top_thread==nil do os.pullEvent() end
print("top thread:", tostring(top_thread))
print("rednet thread:", tostring(rednet_thread))
local _routines = find_local(top_thread,1,"_routines")
local tFilters = find_local(top_thread,1,"tFilters")
local count, count_i = find_local(top_thread,1,"count")
local living, living_i = find_local(top_thread,1,"living")
local function injected()
sleep(5)
term.clear()
term.setCursorPos(1,1)
print("This function was injected by the program.")
end
_routines[count+1] = coroutine.create(injected)
local _
_, tFilters[_routines[count+1]] = coroutine.resume(_routines[count+1])
debug.setlocal(top_thread, 1, count_i, count+1)
debug.setlocal(top_thread, 1, living_i, living+1)
print("Injected successfully.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment