Skip to content

Commit 7aedfd5

Browse files
Mike PallBuristan
Mike Pall
authored andcommitted
Fix command-line argv handling.
(cherry-picked from commit 9ebebc9) Before the patch, there was a situation where `luaL_newstate()` could fail in `main()` and the `argv[0]` could be used as a progname in `l_message()`. However, `argv[0]` is not guaranteed to be non-NULL, so the segmentation fault could occur. This patch fixes the issue by using the predefined name in that case. Moreover, it refactors the `l_message()`, so now there is no need to pass the program name everywhere. The patch is tested with the help of the mocking of `luaL_newstate` by providing an error-injected implementation of it and preloading it. For preload to work, the LuaJIT must be built with dynamic build mode enabled. The corresponding flavor is added to the CI only for x86_64 Debug build to avoid CI jobs outgrowing. The <gh-8594-sysprof-ffunc-crash.test.c> inspects internal symbols from the LuaJIT static library, so it can't be linked for shared build. The test is disabled for the dynamic build mode. Since the Linux kernel 5.18-rc1 release, `argv` is forced to a single empty string if it is empty [1], so the issue is not reproducible on new kernels. You may inspect the `dmesg` log for the corresponding warning entrance. Maxim Kokryashkin: * added the description and the test for the problem [1]: https://lore.kernel.org/all/[email protected]/ Part of tarantool/tarantool#10709 Reviewed-by: Sergey Kaplun <[email protected]> Reviewed-by: Sergey Bronnikov <[email protected]> Signed-off-by: Sergey Kaplun <[email protected]> (cherry picked from commit 026a0e4)
1 parent 4986aab commit 7aedfd5

File tree

8 files changed

+144
-13
lines changed

8 files changed

+144
-13
lines changed

.github/workflows/exotic-builds-testing.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,16 @@ jobs:
3434
BUILDTYPE: [Debug, Release]
3535
ARCH: [ARM64, x86_64]
3636
GC64: [ON, OFF]
37-
FLAVOR: [checkhook, dualnum, gdbjit, nojit, tablebump]
37+
FLAVOR: [checkhook, dualnum, dynamic, gdbjit, nojit, tablebump]
3838
include:
3939
- BUILDTYPE: Debug
4040
CMAKEFLAGS: -DCMAKE_BUILD_TYPE=Debug -DLUA_USE_ASSERT=ON -DLUA_USE_APICHECK=ON
4141
- BUILDTYPE: Release
4242
CMAKEFLAGS: -DCMAKE_BUILD_TYPE=RelWithDebInfo
4343
- FLAVOR: dualnum
4444
FLAVORFLAGS: -DLUAJIT_NUMMODE=2
45+
- FLAVOR: dynamic
46+
FLAVORFLAGS: -DBUILDMODE=dynamic
4547
- FLAVOR: checkhook
4648
FLAVORFLAGS: -DLUAJIT_ENABLE_CHECKHOOK=ON
4749
- FLAVOR: nojit
@@ -56,6 +58,12 @@ jobs:
5658
# DUALNUM is default for ARM64, no need for additional testing.
5759
- FLAVOR: dualnum
5860
ARCH: ARM64
61+
# There is no need to test any scenarios except the most
62+
# common one for the shared library build.
63+
- FLAVOR: dynamic
64+
ARCH: ARM64
65+
- FLAVOR: dynamic
66+
BUILDTYPE: Release
5967
# With table bump optimization enabled (and due to our modification
6068
# related to metrics), some offsets in GG_State stop fitting in 12bit
6169
# immediate. Hence, the build failed due to the DASM error

src/luajit.c

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
static lua_State *globalL = NULL;
4141
static const char *progname = LUA_PROGNAME;
42+
static char *empty_argv[2] = { NULL, NULL };
4243

4344
#if !LJ_TARGET_CONSOLE
4445
static void lstop(lua_State *L, lua_Debug *ar)
@@ -90,9 +91,9 @@ static void print_tools_usage(void)
9091
fflush(stderr);
9192
}
9293

93-
static void l_message(const char *pname, const char *msg)
94+
static void l_message(const char *msg)
9495
{
95-
if (pname) { fputs(pname, stderr); fputc(':', stderr); fputc(' ', stderr); }
96+
if (progname) { fputs(progname, stderr); fputc(':', stderr); fputc(' ', stderr); }
9697
fputs(msg, stderr); fputc('\n', stderr);
9798
fflush(stderr);
9899
}
@@ -102,7 +103,7 @@ static int report(lua_State *L, int status)
102103
if (status && !lua_isnil(L, -1)) {
103104
const char *msg = lua_tostring(L, -1);
104105
if (msg == NULL) msg = "(error object is not a string)";
105-
l_message(progname, msg);
106+
l_message(msg);
106107
lua_pop(L, 1);
107108
}
108109
return status;
@@ -268,9 +269,8 @@ static void dotty(lua_State *L)
268269
lua_getglobal(L, "print");
269270
lua_insert(L, 1);
270271
if (lua_pcall(L, lua_gettop(L)-1, 0, 0) != 0)
271-
l_message(progname,
272-
lua_pushfstring(L, "error calling " LUA_QL("print") " (%s)",
273-
lua_tostring(L, -1)));
272+
l_message(lua_pushfstring(L, "error calling " LUA_QL("print") " (%s)",
273+
lua_tostring(L, -1)));
274274
}
275275
}
276276
lua_settop(L, 0); /* clear stack */
@@ -322,8 +322,7 @@ static int loadjitmodule(lua_State *L)
322322
lua_getfield(L, -1, "start");
323323
if (lua_isnil(L, -1)) {
324324
nomodule:
325-
l_message(progname,
326-
"unknown luaJIT command or jit.* modules not installed");
325+
l_message("unknown luaJIT command or jit.* modules not installed");
327326
return 1;
328327
}
329328
lua_remove(L, -2); /* Drop module table. */
@@ -383,7 +382,7 @@ static int runtoolcmd(lua_State *L, const char *tool_name)
383382
if (msg) {
384383
if (!strncmp(msg, "module ", 7))
385384
msg = "unknown luaJIT command or tools not installed";
386-
l_message(progname, msg);
385+
l_message(msg);
387386
}
388387
return 1;
389388
}
@@ -567,7 +566,6 @@ static int pmain(lua_State *L)
567566
int argn;
568567
int flags = 0;
569568
globalL = L;
570-
if (argv[0] && argv[0][0]) progname = argv[0];
571569

572570
LUAJIT_VERSION_SYM(); /* Linker-enforced version check. */
573571

@@ -623,9 +621,11 @@ static int pmain(lua_State *L)
623621
int main(int argc, char **argv)
624622
{
625623
int status;
626-
lua_State *L = lua_open();
624+
lua_State *L;
625+
if (!argv[0]) argv = empty_argv; else if (argv[0][0]) progname = argv[0];
626+
L = lua_open(); /* create state */
627627
if (L == NULL) {
628-
l_message(argv[0], "cannot create state: not enough memory");
628+
l_message("cannot create state: not enough memory");
629629
return EXIT_FAILURE;
630630
}
631631
smain.argc = argc;

test/tarantool-c-tests/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ file(GLOB tests "${CMAKE_CURRENT_SOURCE_DIR}/*${CTEST_SRC_SUFFIX}")
4141
foreach(test_source ${tests})
4242
# Get test name without suffix. Needed to set OUTPUT_NAME.
4343
get_filename_component(exe ${test_source} NAME_WE)
44+
45+
# Test requires static build, since it inspects internal
46+
# symbols.
47+
if(exe STREQUAL "gh-8594-sysprof-ffunc-crash" AND
48+
BUILDMODE STREQUAL "dynamic")
49+
continue()
50+
endif()
51+
4452
add_executable(${exe} EXCLUDE_FROM_ALL ${test_source})
4553
target_include_directories(${exe} PRIVATE
4654
${CMAKE_CURRENT_SOURCE_DIR}

test/tarantool-tests/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ add_subdirectory(profilers/gh-5813-resolving-of-c-symbols/gnuhash)
6565
add_subdirectory(profilers/gh-5813-resolving-of-c-symbols/hash)
6666
add_subdirectory(profilers/gh-5813-resolving-of-c-symbols/stripped)
6767

68+
if(BUILDMODE STREQUAL "dynamic")
69+
add_subdirectory(fix-argv-handling)
70+
endif()
71+
6872
# JIT, profiler, and bytecode toolchains are located in the <src/>
6973
# directory, <jit/vmdef.lua> is the autogenerated file also
7074
# located in the <src/> directory, but in the scope of the binary
@@ -97,6 +101,12 @@ if(LUAJIT_USE_VALGRIND)
97101
list(APPEND LUA_TEST_ENV_MORE LUAJIT_TEST_USE_VALGRIND=1)
98102
endif()
99103

104+
# Needed to skip the <fix-argv-handling.test.lua> for a
105+
# non-dynamic build.
106+
if(BUILDMODE STREQUAL "dynamic")
107+
list(APPEND LUA_TEST_ENV_MORE LUAJIT_BUILDMODE=dynamic)
108+
endif()
109+
100110
set(TEST_SUITE_NAME "tarantool-tests")
101111

102112
# XXX: The call produces both test and target
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
local tap = require('tap')
2+
local test = tap.test('fix-argv-handling'):skipcond({
3+
['DYLD_INSERT_LIBRARIES does not work on macOS'] = jit.os == 'OSX',
4+
['Skipped for static build'] = os.getenv('LUAJIT_BUILDMODE') ~= 'dynamic',
5+
})
6+
7+
test:plan(1)
8+
9+
-- XXX: Since the Linux kernel 5.18-rc1 release, `argv` is forced
10+
-- to a single empty string if it is empty [1], so the issue is
11+
-- not reproducible on new kernels.
12+
--
13+
-- [1]: https://lore.kernel.org/all/[email protected]/
14+
15+
local utils = require('utils')
16+
local execlib = require('execlib')
17+
local cmd = utils.exec.luabin(arg)
18+
19+
-- Start the LuaJIT with an empty argv array and mocked
20+
-- `luaL_newstate`.
21+
local output = execlib.empty_argv_exec(cmd)
22+
23+
-- Without the patch, the test fails with a segmentation fault
24+
-- instead of returning an error.
25+
test:like(output, 'cannot create state', 'correct argv handling')
26+
test:done(true)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
get_filename_component(test_name ${CMAKE_CURRENT_SOURCE_DIR} NAME)
2+
BuildTestCLib(mynewstate mynewstate.c ${test_name}.test.lua)
3+
BuildTestCLib(execlib execlib.c ${test_name}.test.lua)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#define _GNU_SOURCE
2+
#include "lua.h"
3+
#include "lauxlib.h"
4+
5+
#include <fcntl.h>
6+
#include <stdio.h>
7+
#include <stdlib.h>
8+
#include <sys/wait.h>
9+
#include <unistd.h>
10+
11+
/* 1Kb should be enough. */
12+
#define BUF_SIZE 1024
13+
#define CHECKED(call) \
14+
do { \
15+
if ((call) == -1) { \
16+
perror(#call); \
17+
exit(1); \
18+
} \
19+
} while(0)
20+
21+
static int empty_argv_exec(struct lua_State *L)
22+
{
23+
const char *path = luaL_checkstring(L, -1);
24+
int pipefds[2] = {};
25+
char *const argv[] = {NULL};
26+
char buf[BUF_SIZE];
27+
28+
CHECKED(pipe2(pipefds, O_CLOEXEC));
29+
30+
pid_t pid = fork();
31+
CHECKED(pid);
32+
33+
if (pid == 0) {
34+
/*
35+
* Mock the `luaL_newstate` with an error-injected
36+
* version.
37+
*/
38+
setenv("LD_PRELOAD", "mynewstate.so", 1);
39+
CHECKED(dup2(pipefds[1], STDOUT_FILENO));
40+
CHECKED(dup2(pipefds[1], STDERR_FILENO));
41+
/*
42+
* Pipes are closed on the exec call because of
43+
* the O_CLOEXEC flag.
44+
*/
45+
CHECKED(execvp(path, argv));
46+
}
47+
48+
close(pipefds[1]);
49+
CHECKED(waitpid(pid, NULL, 0));
50+
51+
CHECKED(read(pipefds[0], buf, BUF_SIZE));
52+
close(pipefds[0]);
53+
54+
lua_pushstring(L, buf);
55+
return 1;
56+
}
57+
58+
static const struct luaL_Reg execlib[] = {
59+
{"empty_argv_exec", empty_argv_exec},
60+
{NULL, NULL}
61+
};
62+
63+
LUA_API int luaopen_execlib(lua_State *L)
64+
{
65+
luaL_register(L, "execlib", execlib);
66+
return 1;
67+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include <stddef.h>
2+
3+
struct lua_State;
4+
5+
/* Error-injected mock. */
6+
struct lua_State *luaL_newstate(void)
7+
{
8+
return NULL;
9+
}

0 commit comments

Comments
 (0)