Browse Source

Use after free in 'luaV_finishset'

If a metatable is a weak table, its __newindex field could be collected
by an emergency collection while being used in 'luaV_finishset'. (This
bug has similarities with bug 5.3.2-1, fixed in commit a272fa66.)
Roberto Ierusalimschy 4 months ago
parent
commit
22974326ca
3 changed files with 25 additions and 1 deletions
  1. 5 0
      lapi.c
  2. 8 0
      lvm.c
  3. 12 1
      testes/events.lua

+ 5 - 0
lapi.c

@@ -681,6 +681,11 @@ static int auxgetstr (lua_State *L, const TValue *t, const char *k) {
 }
 }
 
 
 
 
+/*
+** The following function assumes that the registry cannot be a weak
+** table, so that en mergency collection while using the global table
+** cannot collect it.
+*/
 static void getGlobalTable (lua_State *L, TValue *gt) {
 static void getGlobalTable (lua_State *L, TValue *gt) {
   Table *registry = hvalue(&G(L)->l_registry);
   Table *registry = hvalue(&G(L)->l_registry);
   lu_byte tag = luaH_getint(registry, LUA_RIDX_GLOBALS, gt);
   lu_byte tag = luaH_getint(registry, LUA_RIDX_GLOBALS, gt);

+ 8 - 0
lvm.c

@@ -325,6 +325,11 @@ lu_byte luaV_finishget (lua_State *L, const TValue *t, TValue *key,
 
 
 /*
 /*
 ** Finish a table assignment 't[key] = val'.
 ** Finish a table assignment 't[key] = val'.
+** About anchoring the table before the call to 'luaH_finishset':
+** This call may trigger an emergency collection. When loop>0,
+** the table being acessed is a field in some metatable. If this
+** metatable is weak and the table is not anchored, this collection
+** could collect that table while it is being updated.
 */
 */
 void luaV_finishset (lua_State *L, const TValue *t, TValue *key,
 void luaV_finishset (lua_State *L, const TValue *t, TValue *key,
                       TValue *val, int hres) {
                       TValue *val, int hres) {
@@ -335,7 +340,10 @@ void luaV_finishset (lua_State *L, const TValue *t, TValue *key,
       Table *h = hvalue(t);  /* save 't' table */
       Table *h = hvalue(t);  /* save 't' table */
       tm = fasttm(L, h->metatable, TM_NEWINDEX);  /* get metamethod */
       tm = fasttm(L, h->metatable, TM_NEWINDEX);  /* get metamethod */
       if (tm == NULL) {  /* no metamethod? */
       if (tm == NULL) {  /* no metamethod? */
+        sethvalue2s(L, L->top.p, h);  /* anchor 't' */
+        L->top.p++;  /* assume EXTRA_STACK */
         luaH_finishset(L, h, key, val, hres);  /* set new value */
         luaH_finishset(L, h, key, val, hres);  /* set new value */
+        L->top.p--;
         invalidateTMcache(h);
         invalidateTMcache(h);
         luaC_barrierback(L, obj2gco(h), val);
         luaC_barrierback(L, obj2gco(h), val);
         return;
         return;

+ 12 - 1
testes/events.lua

@@ -379,6 +379,17 @@ x = 0 .."a".."b"..c..d.."e".."f".."g"
 assert(x.val == "0abcdefg")
 assert(x.val == "0abcdefg")
 
 
 
 
+do
+  -- bug since 5.4.1 (test needs T)
+  local mt = setmetatable({__newindex={}}, {__mode='v'})
+  local t = setmetatable({}, mt)
+
+  if T then T.allocfailnext() end
+
+  -- seg. fault
+  for i=1, 10 do t[i] = 1 end
+end
+
 -- concat metamethod x numbers (bug in 5.1.1)
 -- concat metamethod x numbers (bug in 5.1.1)
 c = {}
 c = {}
 local x
 local x
@@ -481,7 +492,7 @@ assert(not pcall(function (a,b) return a[b] end, a, 10))
 assert(not pcall(function (a,b,c) a[b] = c end, a, 10, true))
 assert(not pcall(function (a,b,c) a[b] = c end, a, 10, true))
 
 
 -- bug in 5.1
 -- bug in 5.1
-T, K, V = nil
+local T, K, V = nil
 grandparent = {}
 grandparent = {}
 grandparent.__newindex = function(t,k,v) T=t; K=k; V=v end
 grandparent.__newindex = function(t,k,v) T=t; K=k; V=v end