BUG/MEDIUM: lua: defer Lua VM initialisation to the first Lua config keyword

HAProxy used to call hlua_init() unconditionally from step_init_1(),
before any configuration file was parsed.  As a consequence, Lua states
0 and 1 were always created with hlua_openlibs_flags set to its default
value (HLUA_OPENLIBS_ALL), regardless of any tune.lua.openlibs directive
that appeared later in the global section.  With multiple threads, states
2..N were created correctly in hlua_post_init() after the config had been
parsed, while states 0 and 1 retained the full standard-library set.
This produced the observable bug reported in GitHub issue #3396: a script
loaded with lua-load-per-thread could see require() as a function on
thread 1 but nil on thread 2 when tune.lua.openlibs was used to restrict
the available libraries.

The initialisation is now lazy.  hlua_init() is idempotent: it returns
immediately if the states already exist (hlua_states[0] != NULL).  It is
called explicitly from the three config keyword handlers that need the
Lua states to be live before they can do their work (lua-load,
lua-load-per-thread, lua-prepend-path) and from tune.lua.openlibs, after
the hlua_openlibs_flags variable has been updated, so that the states are
always created with the correct library set.

hlua_post_init() calls hlua_init() unconditionally as a safety net,
covering the case where no Lua directive appeared in the configuration at
all (no global section, or only pure-tuning directives such as timeouts
and memory limits), and ensuring correct behaviour with multiple
consecutive global sections.

As a result of this change, tune.lua.openlibs must now appear before
lua-load, lua-load-per-thread, and lua-prepend-path in the configuration;
if any of those keywords is encountered first, the Lua states will already
be initialised and tune.lua.openlibs with a non-default value will return
a parse error.

No backport needed.
This commit is contained in:
William Lallemand 2026-05-27 19:54:48 +02:00 committed by William Lallemand
parent 9a39e55ded
commit 1c59c39171
3 changed files with 26 additions and 5 deletions

View file

@ -4940,8 +4940,8 @@ tune.lua.openlibs [all | none | <lib>[,<lib>...]]
tune.lua.openlibs string,math,table,utf8 # safe subset, no I/O or OS tune.lua.openlibs string,math,table,utf8 # safe subset, no I/O or OS
tune.lua.openlibs all # default, load everything tune.lua.openlibs all # default, load everything
This setting must be set before any "lua-load" or "lua-load-per-thread" This setting must be set before any "lua-load", "lua-load-per-thread" or
directive, otherwise a parse error is returned. "lua-prepend-path" directive, otherwise a parse error is returned.
tune.lua.service-timeout <timeout> tune.lua.service-timeout <timeout>
This is the execution timeout for the Lua services. This is useful for This is the execution timeout for the Lua services. This is useful for

View file

@ -2139,9 +2139,6 @@ static void step_init_1()
if (init_acl() != 0) if (init_acl() != 0)
exit(1); exit(1);
/* Initialise lua. */
hlua_init();
/* set modes given from cmdline */ /* set modes given from cmdline */
global.mode |= (arg_mode & (MODE_DAEMON | MODE_MWORKER | MODE_FOREGROUND | MODE_VERBOSE global.mode |= (arg_mode & (MODE_DAEMON | MODE_MWORKER | MODE_FOREGROUND | MODE_VERBOSE
| MODE_QUIET | MODE_CHECK | MODE_DEBUG | MODE_ZERO_WARNING | MODE_QUIET | MODE_CHECK | MODE_DEBUG | MODE_ZERO_WARNING

View file

@ -13392,6 +13392,16 @@ static int hlua_cfg_parse_openlibs(char **args, int section_type, struct proxy *
return -1; return -1;
} }
/* Reject a non-default restriction if the Lua VM is already initialised,
* which happens when lua-load, lua-load-per-thread or lua-prepend-path
* appeared before this directive.
*/
if (flags != HLUA_OPENLIBS_ALL && hlua_states[0]) {
memprintf(err, "'%s' must appear before any 'lua-load', 'lua-load-per-thread' or 'lua-prepend-path' directive",
args[0]);
return -1;
}
hlua_openlibs_flags = flags; hlua_openlibs_flags = flags;
return 0; return 0;
} }
@ -13492,6 +13502,8 @@ static int hlua_load(char **args, int section_type, struct proxy *curpx,
return -1; return -1;
} }
hlua_init();
/* loading for global state */ /* loading for global state */
hlua_state_id = 0; hlua_state_id = 0;
ha_set_thread(NULL); ha_set_thread(NULL);
@ -13510,6 +13522,8 @@ static int hlua_load_per_thread(char **args, int section_type, struct proxy *cur
return -1; return -1;
} }
hlua_init();
if (per_thread_load == NULL) { if (per_thread_load == NULL) {
/* allocate the first entry large enough to store the final NULL */ /* allocate the first entry large enough to store the final NULL */
per_thread_load = calloc(1, sizeof(*per_thread_load)); per_thread_load = calloc(1, sizeof(*per_thread_load));
@ -13598,6 +13612,8 @@ static int hlua_config_prepend_path(char **args, int section_type, struct proxy
struct prepend_path *p = NULL; struct prepend_path *p = NULL;
size_t i; size_t i;
hlua_init();
if (too_many_args(2, args, err, NULL)) { if (too_many_args(2, args, err, NULL)) {
goto err; goto err;
} }
@ -14006,6 +14022,11 @@ int hlua_post_init()
hlua_body = 0; hlua_body = 0;
/* Ensure the Lua VM is initialised even if no Lua directive appeared
* in the configuration (e.g. no global section at all).
*/
hlua_init();
#if defined(USE_OPENSSL) #if defined(USE_OPENSSL)
/* Initialize SSL server. */ /* Initialize SSL server. */
if (socket_ssl->xprt->prepare_srv) { if (socket_ssl->xprt->prepare_srv) {
@ -14833,6 +14854,9 @@ void hlua_init(void) {
}; };
#endif #endif
if (hlua_states[0])
return; /* already initialised */
/* Init post init function list head */ /* Init post init function list head */
for (i = 0; i < MAX_THREADS + 1; i++) for (i = 0; i < MAX_THREADS + 1; i++)
LIST_INIT(&hlua_init_functions[i]); LIST_INIT(&hlua_init_functions[i]);