Hi,
with actual values the opcode / function length limit makes it
impossible to reach the relevant region, but OP_LOADNIL being able to
set multiple values at once means it's possible to build and compile a
table constructor long enough to overflow and wrap around. The required
source file would be over 8GB in size, but it can be generated and
streamed into the loader on the fly:
```
l = string.rep( "1", 1024, "," )
sf = coroutine.wrap( function( )
coroutine.yield( "f = function() return table.unpack( { "..l.." } )
end\n" )
coroutine.yield( "t = { \n\t" )
local ll = l:gsub( "1", "nil" ):rep( 8192, ",\n\t" )
for i = 1, 257 do coroutine.yield( ll ) end
coroutine.yield( ", f()\n}" )
coroutine.yield( nil )
end )
local f = assert( load(sf) )
f()
for k, v in pairs( t ) do print( k ) end
```
Running the code in 5.4 takes around 4 minutes and needs a little over
32GB of RAM.
With 257 repetitions of the inner ll block, the final block of 1s (from
f()) will end up at low indices again (prints 1 thru 1024).
With 256 repetitions, it'll end up at high indices but `next` will
complain about an invalid key when iterating with `pairs` (prints
2147483394 thru
2147483649 (256 values) before "invalid key to 'next'"
error), suggesting more serious corruption.
It's pretty clear that the 32 bit signed math in OP_SETLIST is to blame.
Just shifting to unsigned math is probably not enough, as 5.5 does just
that and still shows weirdness:
In 5.5, with/out assertions (-DLUAI_ASSERT -DLUA_USE_APICHECKS) and with
256/257/512/513 repetitions:
- 256 with/out assert: "table overflow" error (ok)
- 257 / 512 / 513 + assert: triggers lvm.c:1885 (B == 0)
- 257 / 512 / 513 no assert: runs ≈forever, no clue what it does (first
run forgotten about and aborted after 2 hours(!) at still below 1GB)
5.1 (with unpack in place of table.unpack) prints just 2 and exits, so
there's probably also some weirdness going on there.
Don't have 5.2 / 5.3 installed right now and didn't bother testing those.
-- nobody / Marco Schöpl