Skip to content

Commit

Permalink
Avoid conflict between 64 bit lightuserdata and ITERN key.
Browse files Browse the repository at this point in the history
Reported by XmiliaH.

(cherry picked from commit 16d38a4)

This patch fixes the regression introduced in scope of
fa8e7ffefb715abf55dc5b0c708c63251868 ('Add support for full-range
64 bit lightuserdata.').

The maximum available number of lightuserdata segment is 255. So the
high bits of this lightuserdata TValue are 0xfffe7fff. The same high
bits are set for special control variable on the stack for ITERN/ITERC
bytecodes via ISNEXT bytecode. When ITERN bytecode is despecialize to
ITERC bytecode and a table has the lightuserdata with the maximum
available segment number as a key, the special control variable is
considered as this key and iteration is broken.

This patch forbids to use more than 254 lightuserdata segments to
avoid clashing with the aforementioned control variable. In case when
user tries to create lightuserdata with 255th segment number an error
"bad light userdata pointer" is raised.

Sergey Kaplun:
* added the description and the test for the problem

Part of tarantool/tarantool#6548
Follows up tarantool/tarantool#2712

Reviewed-by: Sergey Ostanevich <sergos@tarantool.org>
Reviewed-by: Igor Munkin <imun@tarantool.org>
Signed-off-by: Igor Munkin <imun@tarantool.org>
(cherry picked from commit 62cb24b)
  • Loading branch information
Mike Pall authored and igormunkin committed Jun 29, 2022
1 parent 5c9a24c commit 36c85b1
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/lj_udata.c
Expand Up @@ -49,9 +49,10 @@ void *lj_lightud_intern(lua_State *L, void *p)
if (segmap[seg] == up) /* Fast path. */
return (void *)(((uint64_t)seg << LJ_LIGHTUD_BITS_LO) | lightudlo(u));
segnum++;
/* Leave last segment unused to avoid clash with ITERN key. */
if (segnum >= (1 << LJ_LIGHTUD_BITS_SEG)-1) lj_err_msg(L, LJ_ERR_BADLU);
}
if (!((segnum-1) & segnum) && segnum != 1) {
if (segnum >= (1 << LJ_LIGHTUD_BITS_SEG)) lj_err_msg(L, LJ_ERR_BADLU);
lj_mem_reallocvec(L, segmap, segnum, segnum ? 2*segnum : 2u, uint32_t);
setmref(g->gc.lightudseg, segmap);
}
Expand Down
1 change: 1 addition & 0 deletions test/tarantool-tests/CMakeLists.txt
Expand Up @@ -66,6 +66,7 @@ add_subdirectory(gh-6098-fix-side-exit-patching-on-arm64)
add_subdirectory(gh-6189-cur_L)
add_subdirectory(lj-49-bad-lightuserdata)
add_subdirectory(lj-601-fix-gc-finderrfunc)
add_subdirectory(lj-727-lightuserdata-itern)
add_subdirectory(lj-flush-on-trace)
add_subdirectory(misclib-getmetrics-capi)
add_subdirectory(misclib-sysprof-capi)
Expand Down
45 changes: 45 additions & 0 deletions test/tarantool-tests/lj-727-lightuserdata-itern.test.lua
@@ -0,0 +1,45 @@
local tap = require('tap')

-- Test file to demonstrate next FF incorrect behaviour on LJ_64.
-- See also, https://github.com/LuaJIT/LuaJIT/issues/727.

local test = tap.test('lj-727-lightuserdata-itern')
test:plan(1)

local ud = require('lightuserdata').craft_ptr_wp()

-- Before the patch we have the tagged lightuserdata pointer
-- 0xFFFE7FFF00000002 in `ud` equal to the ITERN control variable
-- after the first iteration.
-- This control variable denotes the current index in the table
-- in loops with `next()` builtin. If the ITERN is then
-- despecialized to ITERC, the full value of the control variable
-- (i.e. magic + index) succeeds the lookup, so some elements can
-- be skipped during such misiteration.

local real_next = next
local next = next

local function itern_test(t, f)
for k, v in next, t do
f(k, v)
end
end

local visited = {}
local t = {1, 2, 3, [ud] = 45}
itern_test(t, function(k, v)
visited[k] = v
if next == real_next then
next = function(tab, key)
return real_next(tab, key)
end
-- Despecialize bytecode.
itern_test({}, function() end)
end
end)

test.strict = true
test:is_deeply(visited, t, 'userdata node is visited')

os.exit(test:check() and 0 or 1)
@@ -0,0 +1 @@
BuildTestCLib(lightuserdata lightuserdata.c)
80 changes: 80 additions & 0 deletions test/tarantool-tests/lj-727-lightuserdata-itern/lightuserdata.c
@@ -0,0 +1,80 @@
#include <lua.h>
#include <lauxlib.h>

#undef NDEBUG
#include <assert.h>
#include <string.h>

/* To stay within 47 bits, lightuserdata is segmented. */
#define LJ_LIGHTUD_BITS_SEG 8
#define NSEGMENTS (1 << LJ_LIGHTUD_BITS_SEG)

/*
* The function to wrap: get a number to form lightuserdata to
* return with the 0xXXXXXfff00000002 format.
* It may raise an error, when the available lightuserdata
* segments are run out.
*/
static int craft_ptr(lua_State *L)
{
const unsigned long long i = lua_tonumber(L, 1);
lua_pushlightuserdata(L, (void *)((i << 44) + 0xfff00000002));
return 1;
}

/*
* The function to generate bunch of lightuserdata of the
* 0xXXXXXfff00000002 format and push the last one on the stack.
*/
static int craft_ptr_wp(lua_State *L)
{
void *ptr = NULL;
/*
* There are only 255 available lightuserdata segments.
* Generate a bunch of pointers to take them all.
* XXX: After this patch the last userdata segment is
* reserved for ISNEXT/ITERC/ITERN control variable, so
* `craft_ptr()` function will raise an error at the last
* iteration.
*/
unsigned long long i = 0;
for (; i < NSEGMENTS; i++) {
lua_pushcfunction(L, craft_ptr);
lua_pushnumber(L, i);
if (lua_pcall(L, 1, 1, 0) == LUA_OK)
ptr = (void *)lua_topointer(L, -1);
else
/*
* The first segment is occupied by NULL
* from `lj_vm_cpcall()`.
* The last segment is reserved for LuaJIT
* internal usage.
* It would be nice to add a corresponding
* assertion kinda i == NSEGMENTS - 1,
* *but* since this test it being used by
* both LuaJIT and Tarantool, nobody can
* rely on the fact that any of them are
* not occupied an additional segment for
* lightuserdata at the start.
* At the same time the error message can
* be checked against LJ_ERR_BADLU text.
*/
assert(!strcmp(lua_tostring(L, -1),
"bad light userdata pointer"));
}
assert(ptr != NULL);
/* Overwrite possible error message. */
lua_pushlightuserdata(L, ptr);
return 1;
}

static const struct luaL_Reg lightuserdata[] = {
{"craft_ptr_wp", craft_ptr_wp},
{NULL, NULL}
};

LUA_API int luaopen_lightuserdata(lua_State *L)
{
luaL_register(L, "lightuserdata", lightuserdata);
return 1;
}

0 comments on commit 36c85b1

Please sign in to comment.