]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MEDIUM: lua: defer Lua VM initialisation to the first Lua config keyword
authorWilliam Lallemand <wlallemand@irq6.net>
Wed, 27 May 2026 17:54:48 +0000 (19:54 +0200)
committerWilliam Lallemand <wlallemand@haproxy.com>
Thu, 28 May 2026 09:36:02 +0000 (11:36 +0200)
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
src/haproxy.c
src/hlua.c

index d3e78cb9df37f7e8db87440c0cd46e74ca1e31d9..c371147086449d241260cd9344c94425430818f6 100644 (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 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 <timeout>
   This is the execution timeout for the Lua services. This is useful for
index 4d0f1e0da578fe684fd6c24b29e5c3a7ff4b0d6d..1060fbc05522d2ffc3f8e68de9526d7ca72e5e5f 100644 (file)
@@ -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
index 2e06fffa587f0cde195a49e012b3df9ba7e433aa..56acdc7893433c287bb0e31da53bfdf1bdeaf771 100644 (file)
@@ -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]);