BUG/MINOR: startup: unbreak chroot with CAP_SYS_CHROOT

The use of the unshare() mechanism to get the ability to chroot as an
unprivileged user produced a warning on some configurations where the
haproxy process has the CAP_SYS_CHROOT capability. We now only attempt
to use it when a previous chroot() call failed because of insufficient
privileges.

This should fix GitHub issue #3395. No backport needed.
This commit is contained in:
Maxime Henrion 2026-06-01 10:49:02 -04:00 committed by Willy Tarreau
parent 9993688954
commit c24db7c76a

View file

@ -3361,16 +3361,18 @@ static void setup_user_ns(uid_t euid, gid_t egid)
static int do_chroot(const char *prog, const char *path)
{
const char *chroot_dir = path;
int error = 0;
const char *dir, *chroot_dir;
int error, chroot_error;
error = chroot_error = 0;
dir = chroot_dir = path;
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) {
dir = mkdtemp(tmpdir);
if (dir == NULL) {
ha_alert("[%s.main()] Cannot create(%s) for chroot auto.\n",
prog, tmpdir);
return -1;
@ -3381,16 +3383,32 @@ static int do_chroot(const char *prog, const char *path)
* want to remove the directory).
*/
DISGUISE(rmdir(tmpdir));
chroot_dir = ".";
if (!error)
error = chroot(".");
chroot_error = chroot(".");
} else if (strcmp(path, "/") != 0) {
error = chroot(path);
chroot_error = chroot(path);
}
if (!error)
#ifdef CLONE_NEWUSER
/* If the chroot failed because of insufficient privileges and
* unshare(CLONE_NEWUSER) is available, we attempt it to gain the
* abilty to chroot as an unprivileged user. If that worked, we
* try the chroot again.
*/
if (chroot_error && errno == EPERM) {
uid_t euid = geteuid();
gid_t egid = getegid();
if (unshare(CLONE_NEWUSER) == 0) {
setup_user_ns(euid, egid);
chroot_error = chroot(chroot_dir);
}
}
#endif
if (!error && !chroot_error)
error = chdir("/");
if (error) {
ha_alert("[%s.main()] Cannot chroot(%s).\n", prog, chroot_dir);
if (error || chroot_error) {
ha_alert("[%s.main()] Cannot chroot(%s).\n", prog, dir);
return -1;
}
@ -3734,20 +3752,6 @@ int main(int argc, char **argv)
}
}
#ifdef CLONE_NEWUSER
/* When we aren't root and intend to chroot, we try the Linux-only
* unshare(CLONE_NEWUSER) mechanism if available to allow chroot as an
* unprivileged user. If that doesn't work, we just let the subsequent
* chroot() fail as it would have previously.
*/
if (geteuid() != 0 && global.chroot != NULL) {
uid_t euid = geteuid();
gid_t egid = getegid();
if (unshare(CLONE_NEWUSER) == 0)
setup_user_ns(euid, egid);
}
#endif
/* Must chroot and setgid/setuid in the children */
/* chroot if needed */
if (global.chroot != NULL && do_chroot(argv[0], global.chroot) != 0) {