From 1c59c39171529b5c6bb7e661e2bcbc933747f65b Mon Sep 17 00:00:00 2001 From: William Lallemand Date: Wed, 27 May 2026 19:54:48 +0200 Subject: [PATCH] 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. --- doc/configuration.txt | 4 ++-- src/haproxy.c | 3 --- src/hlua.c | 24 ++++++++++++++++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index d3e78cb9d..c37114708 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -4940,8 +4940,8 @@ tune.lua.openlibs [all | none | [,...]] tune.lua.openlibs string,math,table,utf8 # safe subset, no I/O or OS tune.lua.openlibs all # default, load everything - This setting must be set before any "lua-load" or "lua-load-per-thread" - directive, otherwise a parse error is returned. + This setting must be set before any "lua-load", "lua-load-per-thread" or + "lua-prepend-path" directive, otherwise a parse error is returned. tune.lua.service-timeout This is the execution timeout for the Lua services. This is useful for diff --git a/src/haproxy.c b/src/haproxy.c index 4d0f1e0da..1060fbc05 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -2139,9 +2139,6 @@ static void step_init_1() if (init_acl() != 0) exit(1); - /* Initialise lua. */ - hlua_init(); - /* set modes given from cmdline */ global.mode |= (arg_mode & (MODE_DAEMON | MODE_MWORKER | MODE_FOREGROUND | MODE_VERBOSE | MODE_QUIET | MODE_CHECK | MODE_DEBUG | MODE_ZERO_WARNING diff --git a/src/hlua.c b/src/hlua.c index 2e06fffa5..56acdc789 100644 --- a/src/hlua.c +++ b/src/hlua.c @@ -13392,6 +13392,16 @@ static int hlua_cfg_parse_openlibs(char **args, int section_type, struct proxy * 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; return 0; } @@ -13492,6 +13502,8 @@ static int hlua_load(char **args, int section_type, struct proxy *curpx, return -1; } + hlua_init(); + /* loading for global state */ hlua_state_id = 0; ha_set_thread(NULL); @@ -13510,6 +13522,8 @@ static int hlua_load_per_thread(char **args, int section_type, struct proxy *cur return -1; } + hlua_init(); + if (per_thread_load == NULL) { /* allocate the first entry large enough to store the final NULL */ 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; size_t i; + hlua_init(); + if (too_many_args(2, args, err, NULL)) { goto err; } @@ -14006,6 +14022,11 @@ int hlua_post_init() 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) /* Initialize SSL server. */ if (socket_ssl->xprt->prepare_srv) { @@ -14833,6 +14854,9 @@ void hlua_init(void) { }; #endif + if (hlua_states[0]) + return; /* already initialised */ + /* Init post init function list head */ for (i = 0; i < MAX_THREADS + 1; i++) LIST_INIT(&hlua_init_functions[i]); -- 2.47.3