Memory leak on execution a Lua chunk

231 views
Skip to first unread message

Sergey Bronnikov

unread,
Dec 10, 2025, 8:41:33 AM (6 days ago) Dec 10
to lua-l
Hello,

A memory leak is happening on execution of a Lua chunk below.
It is reproduced on the latest version of PUC Rio Lua (104b0fc7008b1f6b7d818985fbbad05cd37ee654)
that built with enabled AddressSanitizer instrumentation.

How to reproduce:

1. git clone https://github.com/lua/lua
2. cd lua
3. make MYCFLAGS="-O0 -ggdb3 -g -fsanitize=address" MYLDFLAGS=-fsanitize=address -j
4. create a C file:
cat << EOF > loadstring_memleak.c
#include <stdio.h>
#include <assert.h>

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

int main()
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);

int sz = 2048;
char buf[sz];

FILE *file = fopen("chunk.txt", "rb");
assert(file);
fread(buf, sizeof(char), sz, file);

luaL_loadstring(L, buf);
lua_pcall(L, 0, 0, 0);

fclose(file);

lua_settop(L, 0);
lua_close(L);

return 0;
}
EOF

5. Build: clang loadstring_memleak.c -g -ggdb3 -O0 -o loadstring_memleak -I. -L. -llua -lm -fsanitize=address
6. Download a chunk.txt (also attached): curl -O https://gist.githubusercontent.com/ligurio/79f40162cece323ae930c144088f9083/raw/9edaeeef81e5408c23107c70f949c4ba5685108b/chunk.txt
7. Execute: ./loadstring_memleak

Full report:

=================================================================
==3099029==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 1536 byte(s) in 1 object(s) allocated from:
    #0 0x558812989840 in realloc (/home/sergeyb/sources/cache/lua/loadstring_memleak+0xdb840) (BuildId: bc1d2d6bc5ce0e295bdbeafcd4aba1c3941dde60)
    #1 0x558812a338c1 in luaL_alloc /home/sergeyb/sources/cache/lua/lauxlib.c:1056:12
    #2 0x558812a30faa in resizebox /home/sergeyb/sources/cache/lua/lauxlib.c:491:18
    #3 0x558812a314b2 in prepbuffsize /home/sergeyb/sources/cache/lua/lauxlib.c:580:25
    #4 0x558812a315d9 in luaL_prepbuffsize /home/sergeyb/sources/cache/lua/lauxlib.c:593:10
    #5 0x558812a5691b in str_gsub /home/sergeyb/sources/cache/lua/lstrlib.c:975:7
    #6 0x5588129dbd76 in precallC /home/sergeyb/sources/cache/lua/ldo.c:644:8
    #7 0x5588129dc61d in luaD_precall /home/sergeyb/sources/cache/lua/ldo.c:713:7
    #8 0x558812a2b502 in luaV_execute /home/sergeyb/sources/cache/lua/lvm.c:1729:22
    #9 0x5588129dcc83 in ccall /home/sergeyb/sources/cache/lua/ldo.c:755:5
    #10 0x5588129dcd35 in luaD_callnoyield /home/sergeyb/sources/cache/lua/ldo.c:773:3
    #11 0x5588129d7748 in luaG_errormsg /home/sergeyb/sources/cache/lua/ldebug.c:847:5
    #12 0x5588129d0cc5 in lua_error /home/sergeyb/sources/cache/lua/lapi.c:1261:5
    #13 0x558812a45cac in luaB_error /home/sergeyb/sources/cache/lua/lbaselib.c:124:10
    #14 0x558812a46c3a in luaB_assert /home/sergeyb/sources/cache/lua/lbaselib.c:443:12
    #15 0x5588129dbd76 in precallC /home/sergeyb/sources/cache/lua/ldo.c:644:8
    #16 0x5588129dc61d in luaD_precall /home/sergeyb/sources/cache/lua/ldo.c:713:7
    #17 0x558812a2b502 in luaV_execute /home/sergeyb/sources/cache/lua/lvm.c:1729:22
    #18 0x5588129dcc83 in ccall /home/sergeyb/sources/cache/lua/ldo.c:755:5
    #19 0x5588129dcd35 in luaD_callnoyield /home/sergeyb/sources/cache/lua/ldo.c:773:3
    #20 0x5588129d7748 in luaG_errormsg /home/sergeyb/sources/cache/lua/ldebug.c:847:5
    #21 0x5588129d0cc5 in lua_error /home/sergeyb/sources/cache/lua/lapi.c:1261:5
    #22 0x558812a45cac in luaB_error /home/sergeyb/sources/cache/lua/lbaselib.c:124:10
    #23 0x558812a46c3a in luaB_assert /home/sergeyb/sources/cache/lua/lbaselib.c:443:12
    #24 0x5588129dbd76 in precallC /home/sergeyb/sources/cache/lua/ldo.c:644:8
    #25 0x5588129dc61d in luaD_precall /home/sergeyb/sources/cache/lua/ldo.c:713:7
    #26 0x558812a2b502 in luaV_execute /home/sergeyb/sources/cache/lua/lvm.c:1729:22
    #27 0x5588129dcc83 in ccall /home/sergeyb/sources/cache/lua/ldo.c:755:5
    #28 0x5588129dcd35 in luaD_callnoyield /home/sergeyb/sources/cache/lua/ldo.c:773:3
    #29 0x5588129d7748 in luaG_errormsg /home/sergeyb/sources/cache/lua/ldebug.c:847:5

SUMMARY: AddressSanitizer: 1536 byte(s) leaked in 1 allocation(s).

Sergey
chunk.txt

Roberto Ierusalimschy

unread,
Dec 10, 2025, 9:07:08 AM (6 days ago) Dec 10
to lu...@googlegroups.com
> [...]
>
> FILE *file = fopen("chunk.txt", "rb");
> assert(file);
> fread(buf, sizeof(char), sz, file);
>
> luaL_loadstring(L, buf);
> lua_pcall(L, 0, 0, 0);
>
> [...]

If I try to reproduce that I get an earlier error (the call to strlen
inside luaL_loadstring segfaults), because the string in 'buf' is not
properly zero-terminated.

-- Roberto

Martin Eden

unread,
Dec 10, 2025, 1:49:48 PM (6 days ago) Dec 10
to lu...@googlegroups.com

On 2025-12-10 15:41, Sergey Bronnikov wrote:
> A memory leak is happening on execution of a Lua chunk below.

Hello Sergey,

So you're loading some binary data and running it.

Where memory leak occurs: after load() or at running?

Because Lua manual states "It is safe to load malformed binary chunks;
|load| signals an appropriate error. However, Lua does not check the
consistency of the code inside binary chunks; running maliciously
crafted bytecode can crash the interpreter."

-- Martin

Sergey Bronnikov

unread,
Dec 11, 2025, 8:40:53 AM (5 days ago) Dec 11
to lua-l
Hello, Martin, 


> So you're loading some binary data and running it. 

I've checked once again, it seems Lua interprets this data as a binary input,
with luaL_loadbuffer with explicitly set mode "t" it is not reproduced.

> Where memory leak occurs: after load() or at running?

The Lua C function lua_load via luaL_loadstring is used in a repro, memleak occurs at running.

Sergey Bronnikov

unread,
Dec 11, 2025, 8:42:39 AM (5 days ago) Dec 11
to lua-l
Roberto,

it seems it was a false positive. Sorry to bother you.

Sergey

Roberto Ierusalimschy

unread,
Dec 11, 2025, 12:13:58 PM (5 days ago) Dec 11
to lu...@googlegroups.com
> it seems it was a false positive. Sorry to bother you.

It doesn't look like a false positive. After correcting that issue (by
adding a '\0' at the end of the buffer), I am able to reproduce the leak.

Valgrind does not find anything wrong in the code (except for the leak).
But it is something weird: If I remove some comments from the code (or
just reduce the size of some comments), the leak disapears.

-- Roberto

eugeny gladkih

unread,
Dec 11, 2025, 12:48:51 PM (5 days ago) Dec 11
to lu...@googlegroups.com


> On 11 Dec 2025, at 20:13, Roberto Ierusalimschy <rob...@inf.puc-rio.br> wrote:
>
>> it seems it was a false positive. Sorry to bother you.
>
> It doesn't look like a false positive. After correcting that issue (by
> adding a '\0' at the end of the buffer), I am able to reproduce the leak.
>

I have a question - does it mean that the compiled Lua code never contains zero bytes?

--
Yours sincerely, Eugeny.


Luiz Henrique de Figueiredo

unread,
Dec 11, 2025, 1:00:41 PM (5 days ago) Dec 11
to lu...@googlegroups.com
> I have a question - does it mean that the compiled Lua code never contains zero bytes?

Precompiled Lua code contains plenty of zero bytes:

$ luac /dev/null
$ xxd luac.out
00000000: 1b4c 7561 5400 1993 0d0a 1a0a 0408 0878 .LuaT..........x
00000010: 5600 0000 0000 0000 0000 0000 2877 4001 V...........(w@.
00000020: 8b40 2f64 6576 2f6e 756c 6c80 8000 0102 .@/dev/null.....
00000030: 8251 0000 0046 0001 0180 8101 0000 8082 .Q...F..........
00000040: 0100 8080 8185 5f45 4e56 ......_ENV

Roberto Ierusalimschy

unread,
Dec 11, 2025, 1:03:36 PM (5 days ago) Dec 11
to 'eugeny gladkih' via lua-l
It does, but the buffer being read there is text (file 'chunk.txt'), not
compiled Lua code.

-- Roberto

Sergey Bronnikov

unread,
Dec 12, 2025, 3:42:55 AM (5 days ago) Dec 12
to lua-l
> But it is something weird: If I remove some comments from the code (or
just reduce the size of some comments), the leak disapears.

Yes, the testcase was minimized as much as possible.
I can't reduce it, either manually or using creduce.

Roberto Ierusalimschy

unread,
Dec 12, 2025, 8:21:36 AM (4 days ago) Dec 12
to lu...@googlegroups.com
> Yes, the testcase was minimized as much as possible.
> I can't reduce it, either manually or using creduce.

I was able to simplify it a bit, with a lot of trial and error. I also added
some prints. My current version is attached. I also reduced the value of
LUAI_MAXCCALLS to 20 to reduce the runtime traces.

-- Roberto
chunk.txt

Roberto Ierusalimschy

unread,
Dec 13, 2025, 2:09:51 PM (3 days ago) Dec 13
to lu...@googlegroups.com
I found the issue. Some calls to the finalizers that should free that
slot are failing due to (C) stack overflow. Because warnings were off (by
default), we do not see any message.

The leak is tricky because it only appears when a finalizer is called at
very specific points during the program. Any change in the rhythm of the
garbage collector affects it.

Probably the garbage collector should defer calling a finalizer when
there is no stack space to run it.

-- Roberto

Sergey Bronnikov

unread,
Dec 15, 2025, 2:22:26 AM (yesterday) Dec 15
to lua-l
On Saturday, December 13, 2025 at 10:09:51 PM UTC+3 Roberto Ierusalimschy wrote:
> > Yes, the testcase was minimized as much as possible.
> > I can't reduce it, either manually or using creduce.
>
> I was able to simplify it a bit, with a lot of trial and error. I also added
> some prints. My current version is attached. I also reduced the value of
> LUAI_MAXCCALLS to 20 to reduce the runtime traces.

I found the issue. Some calls to the finalizers that should free that
slot are failing due to (C) stack overflow. Because warnings were off (by
default), we do not see any message.

It is impressive work! Thanks for investigation!
Reply all
Reply to author
Forward
0 new messages