5.4.9 API bug (and earlier too I guess)

41 views
Skip to first unread message

eugeny gladkih

unread,
Sep 2, 2026, 9:15:54 AM (yesterday) Sep 2
to lu...@googlegroups.com
Hi,

static int lua_create( lua_State *L ) {
lua_settop( L, 3 );
luaL_checkudata( L, 1, "CURL" ); // CURL global
luaL_checkstring( L, 2 ); // URL
luaL_checktype( L, 3, LUA_TTABLE ); // options

lua_newuserdatauv( L, sizeof(curl_t), 1 );
lua_pushvalue( L, 2 );

lua_setiuservalue( L, -1 /* must be -2 */, 1 );


this code will be executed OK (why lua_setiuservalue returns 0?!), but later lua_gc will crash with SIGSEGV

Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x0000632d17fdef54 in sweeplist (countin=100, L=0x632d396b1448, p=0x632d39719d44, countout=<synthetic pointer>) at ../../../../lua/mk/lib/../../lua-5.4.9/src/lgc.c:836
836 int marked = curr->marked;
(gdb) bt
#0 0x0000632d17fdef54 in sweeplist (countin=100, L=0x632d396b1448, p=0x632d39719d44, countout=<synthetic pointer>) at ../../../../lua/mk/lib/../../lua-5.4.9/src/lgc.c:836
#1 sweepstep (L=0x632d396b1448, g=0x632d396b1510, nextstate=4, nextlist=0x632d396b1590) at ../../../../lua/mk/lib/../../lua-5.4.9/src/lgc.c:1577
#2 singlestep (L=L@entry=0x632d396b1448) at ../../../../lua/mk/lib/../../lua-5.4.9/src/lgc.c:1617
#3 0x0000632d17fdf678 in luaC_runtilstate (L=0x632d396b1448, statesmask=128) at ../../../../lua/mk/lib/../../lua-5.4.9/src/lgc.c:1660
#4 fullinc (L=0x632d396b1448, g=0x632d396b1510) at ../../../../lua/mk/lib/../../lua-5.4.9/src/lgc.c:1722
#5 luaC_fullgc (L=0x632d396b1448, isemergency=<optimized out>) at ../../../../lua/mk/lib/../../lua-5.4.9/src/lgc.c:1740
#6 0x0000632d17fc5c2d in lua_gc(lua_State*, int, ...) [clone .constprop.0] (L=<optimized out>, what=2, what=2) at ../../../../lua/mk/lib/../../lua-5.4.9/src/lapi.c:1153

--
Yours sincerely, Eugeny.


Jure Bagić

unread,
Sep 2, 2026, 9:49:50 AM (yesterday) Sep 2
to 'eugeny gladkih' via lua-l
You even put the correct comment there but wrong index!?
> lua_setiuservalue( L, -1 /* must be -2 */, 1 );
(The second argument is the index of the userdata not the upvalue.)

For explanation why 'lua_setiuservalue' returns 0 read below.

In your case, what happens is that the string value,
which is also an object is being casted to userdata.
And then you are accessing the 'nuvalue':
```
typedef struct Udata {
CommonHeader;
unsigned short nuvalue; /* number of user values */
...
other fields
...
UValue uv[1]; /* user values */
} Udata;
```

Here is the string object:
```
typedef struct TString {
CommonHeader;
lu_byte extra; /* reserved words for short strings; "has hash" for longs */
ls_byte shrlen; /* length for short strings, negative for long strings */
...
other fields
...
char contents[1]; /* string body starts here */
} TString;
```

As you can see, you are casting a string as userdata and by taking 'nuvalue' you
are instead taking ('extra' | 'shrlen'). The following check will fail in
cases where your URL is an empty string or a long string (more than
LUAI_MAXSHORTLEN which is 40 bytes).
```
if (!(cast_uint(n) - 1u < cast_uint(uvalue(o)->nuvalue)))
res = 0; /* 'n' not in [1, uvalue(o)->nuvalue] */
else {
setobj(L, &uvalue(o)->uv[n - 1].uv, s2v(L->top.p - 1));
luaC_barrierback(L, gcvalue(o), s2v(L->top.p - 1));
res = 1;
}
```
So this is why 'lua_setiuservalue' returns 0. If the URL string is not an
empty string but less than 41 bytes, then you will break things as you
are executing the 'else' branch.

I am curious, are you passing different kinds of URL strings, or you are just
testing one URL and the function keeps returning 0?

--
Jure
signature.asc

Jure Bagić

unread,
4:43 AM (18 hours ago) 4:43 AM
to 'eugeny gladkih' via lua-l
Few corrections on my part.
> (The second argument is the index of the userdata not the upvalue.)

I meant to say uservalue not the 'upvalue'.

> As you can see, you are casting a string as userdata and by taking 'nuvalue' you
> are instead taking ('extra' | 'shrlen'). The following check will fail in
> cases where your URL is an empty string or a long string (more than
> LUAI_MAXSHORTLEN which is 40 bytes).

You are taking (('shrlen' << 8) | 'extra'). And the following check will
also fail
for long strings as that value you took is being casted as unsigned integer.

> if (!(cast_uint(n) - 1u < cast_uint(uvalue(o)->nuvalue)))
> res = 0; /* 'n' not in [1, uvalue(o)->nuvalue] */
>
> ...

eugeny gladkih

unread,
7:40 AM (15 hours ago) 7:40 AM
to lu...@googlegroups.com
re,

> On 2 Sep 2026, at 16:48, Jure Bagić <jureb...@gmail.com> wrote:
>
> I am curious, are you passing different kinds of URL strings, or you are just
> testing one URL and the function keeps returning 0?
>

just one small

--
Yours sincerely, Eugeny.


Reply all
Reply to author
Forward
0 new messages