]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: startup: add automatic chroot feature
authorMaxime Henrion <mhenrion@haproxy.com>
Tue, 12 May 2026 14:21:40 +0000 (10:21 -0400)
committerWilly Tarreau <w@1wt.eu>
Wed, 20 May 2026 06:34:24 +0000 (08:34 +0200)
It is now possible to use "chroot auto" in the configuration. This lets
haproxy create an anonymous (cleaned up after the process terminates)
and read-only directory for chroot. This directory is created in /tmp;
we might want to support creating it in a different directory in the
future, either by respecting $TMPDIR or by allowing an optional
directory after the "auto" keyword.

doc/configuration.txt
src/haproxy.c

index bfed9369834af0fe1493d1b0055914a6ba9e56ea..97cd580ea9c083830c96f302fa9684e4041b0556 100644 (file)
@@ -2126,13 +2126,21 @@ ca-base <dir>
   directives. Absolute locations specified in "ca-file", "ca-verify-file" and
   "crl-file" prevail and ignore "ca-base".
 
-chroot <jail dir>
+chroot { <jail dir> | auto }
   Changes current directory to <jail dir> and performs a chroot() there before
   dropping privileges. This increases the security level in case an unknown
   vulnerability would be exploited, since it would make it very hard for the
-  attacker to exploit the system. This only works when the process is started
-  with superuser privileges. It is important to ensure that <jail_dir> is both
-  empty and non-writable to anyone.
+  attacker to exploit the system. It is important to ensure that <jail dir>
+  is both empty and non-writable to anyone. When the process is started with
+  superuser privileges, the chroot() is performed directly. On Linux, when
+  started unprivileged, haproxy attempts to perform it from inside a new
+  user namespace created with unshare(CLONE_NEWUSER); if that mechanism is
+  unavailable the chroot() will fail with the usual error.
+
+  As a special case, <jail dir> may be set to "auto", in which case haproxy
+  creates an anonymous temporary directory, unlinks it, and chroots into it.
+  The resulting jail has no name in the filesystem and is empty and read-only,
+  removing the need to prepare a dedicated jail directory.
 
 close-spread-time <time>
   Define a time window during which idle connections and active connections
index ab55d2f129bff7d8ad5277637e6e3a9078526a94..3a43187539edeaa7d4b7c4334490a4a37b3eb8c2 100644 (file)
@@ -3327,6 +3327,44 @@ static void setup_user_ns(uid_t euid, gid_t egid)
 }
 #endif
 
+static int do_chroot(const char *prog, const char *path)
+{
+       const char *chroot_dir = path;
+       int error = 0;
+
+       if (strcmp(path, "auto") == 0) {
+               /* When "chroot auto" is used, we attempt to chroot to an
+                * anonymous and read-only directory.
+                */
+               char tmpdir[] = "/tmp/haproxy.XXXXXX";
+               chroot_dir = mkdtemp(tmpdir);
+               if (chroot_dir == NULL) {
+                       ha_alert("[%s.main()] Cannot create(%s) for chroot auto.\n",
+                                prog, tmpdir);
+                       return -1;
+               }
+               error = chdir(tmpdir);
+               /* We can call rmdir() here; we hold a reference to the
+                * directory since it is our CWD (and if chdir() failed we still
+                * want to remove the directory).
+                */
+               DISGUISE(rmdir(tmpdir));
+               if (!error)
+                       error = chroot(".");
+       } else {
+               error = chroot(path);
+       }
+       if (!error)
+               error = chdir("/");
+
+       if (error) {
+               ha_alert("[%s.main()] Cannot chroot(%s).\n", prog, chroot_dir);
+               return -1;
+       }
+
+       return 0;
+}
+
 int main(int argc, char **argv)
 {
        struct rlimit limit;
@@ -3659,14 +3697,11 @@ int main(int argc, char **argv)
 
        /* Must chroot and setgid/setuid in the children */
        /* chroot if needed */
-       if (global.chroot != NULL) {
-               if (chroot(global.chroot) == -1 || chdir("/") == -1) {
-                       ha_alert("[%s.main()] Cannot chroot(%s).\n", argv[0], global.chroot);
-                       if (nb_oldpids)
-                               tell_old_pids(SIGTTIN);
-                       protocol_unbind_all();
-                       exit(1);
-               }
+       if (global.chroot != NULL && do_chroot(argv[0], global.chroot) != 0) {
+               if (nb_oldpids)
+                       tell_old_pids(SIGTTIN);
+               protocol_unbind_all();
+               exit(1);
        }
 
        ha_free(&global.chroot);