diff --git a/src/core/lstack.h b/src/core/lstack.h index 2cd1f098..435f7199 100644 --- a/src/core/lstack.h +++ b/src/core/lstack.h @@ -116,12 +116,12 @@ class LuaStack { /* Convert stack pointer to offset from base */ inline ptrdiff_t save(StkId pt) const noexcept { - return cast_charp(pt) - cast_charp(stack.p); + return pt - stack.p; /* direct pointer arithmetic, no char* round-trip */ } /* Convert offset to stack pointer */ inline StkId restore(ptrdiff_t n) const noexcept { - return reinterpret_cast(cast_charp(stack.p) + n); + return stack.p + n; /* direct pointer arithmetic, safe with LTO */ } /* diff --git a/src/objects/ltable.cpp b/src/objects/ltable.cpp index 22bf2465..05b25c13 100644 --- a/src/objects/ltable.cpp +++ b/src/objects/ltable.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include "lua.h" @@ -109,8 +110,15 @@ class NodeArray { if (withLastfree) { // Large table: allocate Limbox + Node[] // LAYOUT: [Limbox header][Node array of size n] + // Verify no overflow in size calculation + if (n > (MAX_SIZET - sizeof(Limbox)) / sizeof(Node)) { + luaG_runerror(L, "table size overflow"); + } size_t total = sizeof(Limbox) + n * sizeof(Node); char* block = luaM_newblock(L, total); + // Verify alignment assumptions (critical for type punning safety) + lua_assert(reinterpret_cast(block) % alignof(Limbox) == 0); + lua_assert(reinterpret_cast(block + sizeof(Limbox)) % alignof(Node) == 0); // Limbox is at the start, nodes follow // Safe per C++17 ยง8.2.10: reinterpret_cast to properly aligned type Limbox* limbox = reinterpret_cast(block); @@ -131,6 +139,8 @@ class NodeArray { // nodeStart points to element after Limbox, so (nodeStart - 1) conceptually // points to the Limbox (treating the block as Limbox array for arithmetic purposes) Limbox* limbox = reinterpret_cast(nodeStart) - 1; + // Verify we're not accessing uninitialized memory + lua_assert(limbox->lastfree >= nodeStart); return limbox->lastfree; } }; @@ -704,6 +714,9 @@ static Value *resizearray (lua_State *L , Table *t, unsigned tomove = (oldasize < newasize) ? oldasize : newasize; size_t tomoveb = (oldasize < newasize) ? oldasizeb : newasizeb; lua_assert(tomoveb > 0); + lua_assert(tomove <= newasize); /* ensure destination bounds */ + lua_assert(tomove <= oldasize); /* ensure source bounds */ + lua_assert(tomoveb <= newasizeb); /* verify size calculation */ memcpy(np - tomove, op - tomove, tomoveb); luaM_freemem(L, op - oldasize, oldasizeb); /* free old block */ } @@ -727,7 +740,9 @@ static void setnodevector (lua_State *L, Table *t, unsigned size) { } else { unsigned int lsize = luaO_ceillog2(size); - if (lsize > MAXHBITS || (1u << lsize) > MAXHSIZE) + if (lsize > MAXHBITS) + luaG_runerror(L, "table overflow"); + if ((1u << lsize) > MAXHSIZE) luaG_runerror(L, "table overflow"); size = Table::powerOfTwo(lsize); bool needsLastfree = (lsize >= LIMFORLAST); @@ -1240,6 +1255,7 @@ static lua_Unsigned hash_search (lua_State *L, Table *t, unsigned asize) { lua_Unsigned i = asize + 1; /* caller ensures t[i] is present */ unsigned rnd = G(L)->getSeed(); int n = (asize > 0) ? luaO_ceillog2(asize) : 0; /* width of 'asize' */ + lua_assert(n >= 0 && n < 32); /* ensure shift is safe (avoid UB) */ unsigned mask = (1u << n) - 1; /* 11...111 with the width of 'asize' */ unsigned incr = (rnd & mask) + 1; /* first increment (at least 1) */ lua_Unsigned j = (incr <= l_castS2U(LUA_MAXINTEGER) - i) ? i + incr : i + 1; @@ -1247,7 +1263,9 @@ static lua_Unsigned hash_search (lua_State *L, Table *t, unsigned asize) { while (!hashkeyisempty(t, j)) { /* repeat until an absent t[j] */ i = j; /* 'i' is a present index */ if (j <= l_castS2U(LUA_MAXINTEGER)/2 - 1) { + lua_Unsigned old_j = j; j = j*2 + (rnd & 1); /* try again with 2j or 2j+1 */ + lua_assert(j > old_j && j <= l_castS2U(LUA_MAXINTEGER)); /* no wrap */ rnd >>= 1; } else { diff --git a/src/vm/lvm_string.cpp b/src/vm/lvm_string.cpp index 8ab80373..5f24ba76 100644 --- a/src/vm/lvm_string.cpp +++ b/src/vm/lvm_string.cpp @@ -58,10 +58,14 @@ void luaV_concat (lua_State *L, int total) { StkId top = L->getTop().p; int n = 2; /* number of elements handled in this pass (at least 2) */ if (!(ttisstring(s2v(top - 2)) || cvt2str(s2v(top - 2))) || - !tostring(L, s2v(top - 1))) + !tostring(L, s2v(top - 1))) { luaT_tryconcatTM(L); /* may invalidate 'top' */ - else if (isemptystr(s2v(top - 1))) /* second operand is empty? */ + top = L->getTop().p; /* recapture after potential GC */ + } + else if (isemptystr(s2v(top - 1))) { /* second operand is empty? */ cast_void(tostring(L, s2v(top - 2))); /* result is first operand */ + top = L->getTop().p; /* recapture after potential GC */ + } else if (isemptystr(s2v(top - 2))) { /* first operand is empty string? */ *s2v(top - 2) = *s2v(top - 1); /* result is second op. (operator=) */ } @@ -71,6 +75,7 @@ void luaV_concat (lua_State *L, int total) { TString *ts; /* collect total length and number of strings */ for (n = 1; n < total && tostring(L, s2v(top - n - 1)); n++) { + top = L->getTop().p; /* recapture after tostring() which can trigger GC */ size_t l = tsslen(tsvalue(s2v(top - n - 1))); if (l_unlikely(l >= MAX_SIZE - sizeof(TString) - tl)) { L->getStackSubsystem().setTopPtr(top - total); /* pop strings to avoid wasting stack */ @@ -82,9 +87,11 @@ void luaV_concat (lua_State *L, int total) { char buff[LUAI_MAXSHORTLEN]; copy2buff(top, n, buff); /* copy strings to buffer */ ts = luaS_newlstr(L, buff, tl); + top = L->getTop().p; /* recapture after potential GC */ } else { /* long string; copy strings directly to final result */ ts = luaS_createlngstrobj(L, tl); + top = L->getTop().p; /* recapture after potential GC */ copy2buff(top, n, getlngstr(ts)); } setsvalue2s(L, top - n, ts); /* create result */