An heap-use-after-free is triggered in the insertkey()

76 views
Skip to first unread message

Sergey Bronnikov

unread,
May 21, 2026, 5:54:20 AM (2 days ago) May 21
to lua-l
Hello,

an heap-use-after-free is triggered on execution of a Lua chunk below:

cat <<EOF >  chunk.lua
local lines = {}
local function hook()
  lines[#lines+0] = debug.getinfo(2).currentl8
end
debug.sethook(hook, "l", 0)
local function mm(_a, _b)
end
local mt = {}
for _ = 1, 100 do
  local parent = {}
  parent.__newindex = parent
  parent.bar = 0
  setmetatable(parent, grandparent)
  [3] = H child = setmetatable({},pgmatcharent)
  setmetatable(parent, grandparent)
  child = setmetatable({},parent)
  child.fo_nt = {}
  H = 2546325
  setmetatable(parent, grandparent)
  [1] = H
  lines = debug.getinfo(1),curre
end
EOF

How to reproduce:

make -j CC=clang MYCFLAGS="-Og -g -m32 -fsanitize=address -O0 -Wall -DLUAI_ASSERT -DLUA_USE_APICHECK" MYLDFLAGS="-fsanitize=address -m32"
./lua -e "loadfile('chunk.lua')()"

It is worth to say, the problem is not guaranteed to reproduce every time,
so it is better to execute in a for-loop:

for i in $(seq 1 1 150); do ./lua -e "loadfile('chunk.lua')()"; done

Reproduced on the latest version (53b41d0cddd80bf33fdc631bdd32e3ba53842b89)
that built with enabled macro -DLUAI_ASSERT.

==3997803==ERROR: AddressSanitizer: heap-use-after-free on address 0xe8215ac4 at pc 0x59b3b817 bp 0xffccee38 sp 0xffccee30
READ of size 1 at 0xe8215ac4 thread T0
    #0 0x59b3b816 in insertkey /src/testdir/build/lua-master/source/ltable.c:892:3
    #1 0x59b36330 in newcheckedkey /src/testdir/build/lua-master/source/ltable.c:906:16
    #2 0x59b36330 in reinserthash /src/testdir/build/lua-master/source/ltable.c:647:7
    #3 0x59b36330 in luaH_resize /src/testdir/build/lua-master/source/ltable.c:745:3
    #4 0x59b3e37d in rehash /src/testdir/build/lua-master/source/ltable.c:790:3
    #5 0x59b3e37d in luaH_newkey /src/testdir/build/lua-master/source/ltable.c:918:7
    #6 0x59b3d4d1 in luaH_finishset /src/testdir/build/lua-master/source/ltable.c:1179:5
    #7 0x59b50b4a in luaV_finishset /src/testdir/build/lua-master/source/lvm.c:345:9
    #8 0x59b65c25 in luaV_execute /src/testdir/build/lua-master/source/lvm.c:1390:11
    #9 0x59aee733 in ccall /src/testdir/build/lua-master/source/ldo.c:774:5
    #10 0x59aee733 in luaD_callnoyield /src/testdir/build/lua-master/source/ldo.c:792:3
    #11 0x59ad7200 in f_call /src/testdir/build/lua-master/source/lapi.c:1071:3
    #12 0x59ae73e8 in luaD_rawrunprotected /src/testdir/build/lua-master/source/ldo.c:166:3
    #13 0x59af082b in luaD_pcall /src/testdir/build/lua-master/source/ldo.c:1096:12
    #14 0x59ad6b84 in lua_pcallk /src/testdir/build/lua-master/source/lapi.c:1097:14
    #15 0x59ac5e4e in LLVMFuzzerTestOneInput /src/testdir/tests/capi/luaL_loadstring_test.c:43:2
    #16 0x59957ea7 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned int) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:619:13
    #17 0x59942fa2 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned int) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:329:6
    #18 0x599489c5 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned int)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:865:9
    #19 0x59974217 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
    #20 0xeaba5cb8  (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)
    #21 0xeaba5d7b in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x24d7b) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)
    #22 0x5993c666 in _start (/home/sergeyb/sources/oss-fuzz/build/out/lua/luaL_loadstring_test+0x42666)

0xe8215ac4 is located 4 bytes inside of 28-byte region [0xe8215ac0,0xe8215adc)
freed by thread T0 here:
    #0 0x59a7d43a in free /src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:51:3
    #1 0x59b7b1c1 in luaL_alloc /src/testdir/build/lua-master/source/lauxlib.c:1052:5
    #2 0x59b0e1ff in luaM_free_ /src/testdir/build/lua-master/source/lmem.c:153:3
    #3 0x59b375da in luaH_free /src/testdir/build/lua-master/source/ltable.c:824:3
    #4 0x59b03561 in freeobj /src/testdir/build/lua-master/source/lgc.c:856:7
    #5 0x59b0d972 in sweepgen /src/testdir/build/lua-master/source/lgc.c:1191:7
    #6 0x59b01c43 in youngcollection /src/testdir/build/lua-master/source/lgc.c:1354:3
    #7 0x59b01c43 in luaC_step /src/testdir/build/lua-master/source/lgc.c:1755:9
    #8 0x59acd521 in lua_pushstring /src/testdir/build/lua-master/source/lapi.c:581:3
    #9 0x59ba1f6d in settabss /src/testdir/build/lua-master/source/ldblib.c:113:3
    #10 0x59ba1f6d in db_getinfo /src/testdir/build/lua-master/source/ldblib.c:187:5
    #11 0x59aecacb in precallC /src/testdir/build/lua-master/source/ldo.c:663:7
    #12 0x59aedd4a in luaD_precall /src/testdir/build/lua-master/source/ldo.c
    #13 0x59b6074f in luaV_execute /src/testdir/build/lua-master/source/lvm.c:1729:22
    #14 0x59aee733 in ccall /src/testdir/build/lua-master/source/ldo.c:774:5
    #15 0x59aee733 in luaD_callnoyield /src/testdir/build/lua-master/source/ldo.c:792:3
    #16 0x59ad624e in lua_callk /src/testdir/build/lua-master/source/lapi.c:1053:5
    #17 0x59ba3438 in hookf /src/testdir/build/lua-master/source/ldblib.c:337:5
    #18 0x59ae9607 in luaD_hook /src/testdir/build/lua-master/source/ldo.c:468:5
    #19 0x59ae3ccb in luaG_traceexec /src/testdir/build/lua-master/source/ldebug.c:959:5
    #20 0x59b67fc9 in luaV_execute /src/testdir/build/lua-master/source/lvm.c:1309:9
    #21 0x59aee733 in ccall /src/testdir/build/lua-master/source/ldo.c:774:5
    #22 0x59aee733 in luaD_callnoyield /src/testdir/build/lua-master/source/ldo.c:792:3
    #23 0x59ad7200 in f_call /src/testdir/build/lua-master/source/lapi.c:1071:3
    #24 0x59ae73e8 in luaD_rawrunprotected /src/testdir/build/lua-master/source/ldo.c:166:3
    #25 0x59af082b in luaD_pcall /src/testdir/build/lua-master/source/ldo.c:1096:12
    #26 0x59ad6b84 in lua_pcallk /src/testdir/build/lua-master/source/lapi.c:1097:14
    #27 0x59ac5e4e in LLVMFuzzerTestOneInput /src/testdir/tests/capi/luaL_loadstring_test.c:43:2
    #28 0x59957ea7 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned int) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:619:13
    #29 0x59942fa2 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned int) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:329:6
    #30 0x599489c5 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned int)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:865:9
    #31 0x59974217 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
    #32 0xeaba5cb8  (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)

previously allocated by thread T0 here:
    #0 0x59a7d992 in realloc /src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:81:3
    #1 0x59b7b1b1 in luaL_alloc /src/testdir/build/lua-master/source/lauxlib.c:1056:12
    #2 0x59b0e785 in luaM_malloc_ /src/testdir/build/lua-master/source/lmem.c:206:22
    #3 0x59afd1d5 in luaC_newobjdt /src/testdir/build/lua-master/source/lgc.c:299:13
    #4 0x59afd1d5 in luaC_newobj /src/testdir/build/lua-master/source/lgc.c:313:10
    #5 0x59b37134 in luaH_new /src/testdir/build/lua-master/source/ltable.c:799:17
    #6 0x59b60558 in luaV_execute /src/testdir/build/lua-master/source/lvm.c:1421:13
    #7 0x59aee733 in ccall /src/testdir/build/lua-master/source/ldo.c:774:5
    #8 0x59aee733 in luaD_callnoyield /src/testdir/build/lua-master/source/ldo.c:792:3
    #9 0x59ad7200 in f_call /src/testdir/build/lua-master/source/lapi.c:1071:3
    #10 0x59ae73e8 in luaD_rawrunprotected /src/testdir/build/lua-master/source/ldo.c:166:3
    #11 0x59af082b in luaD_pcall /src/testdir/build/lua-master/source/ldo.c:1096:12
    #12 0x59ad6b84 in lua_pcallk /src/testdir/build/lua-master/source/lapi.c:1097:14
    #13 0x59ac5e4e in LLVMFuzzerTestOneInput /src/testdir/tests/capi/luaL_loadstring_test.c:43:2
    #14 0x59957ea7 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned int) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:619:13
    #15 0x59942fa2 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned int) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:329:6
    #16 0x599489c5 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned int)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:865:9
    #17 0x59974217 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
    #18 0xeaba5cb8  (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)

SUMMARY: AddressSanitizer: heap-use-after-free /src/testdir/build/lua-master/source/ltable.c:892:3 in insertkey
Shadow bytes around the buggy address:
  0xe8215800: fd fd fa fa fd fd fd fa fa fa fd fd fd fd fa fa
  0xe8215880: fd fd fd fa fa fa fd fd fd fd fa fa fd fd fd fa
  0xe8215900: fa fa fd fd fd fd fa fa fd fd fd fa fa fa fd fd
  0xe8215980: fd fd fa fa fd fd fd fa fa fa fd fd fd fd fa fa
  0xe8215a00: fd fd fd fa fa fa fd fd fd fd fa fa fd fd fd fa
=>0xe8215a80: fa fa fd fd fd fd fa fa[fd]fd fd fd fa fa fd fd
  0xe8215b00: fd fa fa fa fd fd fd fd fa fa fd fd fd fa fa fa
  0xe8215b80: fd fd fd fd fa fa fd fd fd fa fa fa fd fd fd fd
  0xe8215c00: fa fa fd fd fd fa fa fa fd fd fd fd fa fa fd fd
  0xe8215c80: fd fa fa fa fd fd fd fd fa fa fa fa fa fa fa fa
  0xe8215d00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==3997803==ABORTING

Sergey

Martin Eden

unread,
May 21, 2026, 6:14:33 AM (2 days ago) May 21
to lu...@googlegroups.com
Hello Sergey,

I expect that using "debug" module voids warranty.

-- Martin

Julien Cugnière

unread,
May 21, 2026, 7:24:02 AM (2 days ago) May 21
to lu...@googlegroups.com
Le jeu. 21 mai 2026 à 12:14, 'Martin Eden' via lua-l
<lu...@googlegroups.com> a écrit :
> On 2026-05-21 11:54, Sergey Bronnikov wrote:
> > Hello,
> >
> > an heap-use-after-free is triggered on execution of a Lua chunk below:
>
> I expect that using "debug" module voids warranty.

This disclaimer is well known, but Sergey's example got me thinking:
does it still apply to "read-only" uses of the debug library?

Of course when using functions such as
debug.setlocal/setupvalue/setmetatable all bets are off. But in this
example, only debug.getinfo and debug.sethook are used. Theoretically
getinfo is a read-only operation (unless it needs to mutate some
internal state to gather the information?). Setting a hook can be
considered a modification of the state, but the hook itself is not
doing anything obviously dangerous. So it feels like such a use of the
debug library shouldn't be able to crash Lua?

Martin Eden

unread,
May 22, 2026, 6:04:06 AM (yesterday) May 22
to lu...@googlegroups.com
On 2026-05-21 13:23, Julien Cugnière wrote:
> This disclaimer is well known, but Sergey's example got me thinking:
> does it still apply to "read-only" uses of the debug library?
>
> Of course when using functions such as
> debug.setlocal/setupvalue/setmetatable all bets are off. But in this
> example, only debug.getinfo and debug.sethook are used. Theoretically
> getinfo is a read-only operation (unless it needs to mutate some
> internal state to gather the information?). Setting a hook can be
> considered a modification of the state, but the hook itself is not
> doing anything obviously dangerous. So it feels like such a use of the
> debug library shouldn't be able to crash Lua?
My point of view is that if manual states

"You should exert care when using this library. Several of its functions
[...] can compromise otherwise secure code."

then it's "no warranty" contract clause.

So even if single call of debug.getinfo() along with expected results
will encrypt my data and install rootkit -- no formal complains,
I was warned.

Also I consider posting murky computer-generated code in maillist as not
neat. I believe author is expected to do it's best to represent issue
in minimal essential form for human consumption.

-- Martin

Sergey Bronnikov

unread,
May 22, 2026, 6:06:48 AM (yesterday) May 22
to lua-l
Hello, Julien,

You're right. I have exactly the same thoughts about the reproducer.
The debug functions here don't perform any destructive actions.
But this leads to the GC freeing the object we later access.

Sergey
Reply all
Reply to author
Forward
0 new messages