Unprivileged chroot

Started by Mickaël Salaün on Mar 16, 2021 · 9 messages from 3 authors · Last reply Mar 16, 2021

Series

1 patch, 1 file

+21 −2

Subsystems (2)

Maintainers (3)

STALE2065d SUPERSEDED

Status: Superseded by v5 (posted 2021-03-16). [go to v5]

Revision v4 (5 revisions archived).

Revisions (5)
  1. v1
  2. v2 [diff against previous]
  3. v3 [diff against previous]
  4. v4 current [diff against previous]
  5. v5 [diff against previous]

open the first message on its own page

  1. [PATCH v4 1/1] fs: Allow no_new_privs tasks to call chroot(2)
    Revisions and related patches
    Revisions (5)
    1. v1
    2. v2 [diff against previous]
    3. v3 [diff against previous]
    4. v4 current [diff against previous]
    5. v5 [diff against previous]
Message details and related content
Hi,

This new patch group the current task security checks in a dedicated
helper current_chroot_allowed() and extend the patch description.

The chroot system call is currently limited to be used by processes with
the CAP_SYS_CHROOT capability.  This protects against malicious
procesess willing to trick SUID-like binaries.  The following patch
allows unprivileged users to safely use chroot(2), which may be
complementary to the use of user namespaces.

This patch is a follow-up of a previous one sent by Andy Lutomirski some
time ago:
https://lore.kernel.org/lkml/0e2f0f54e19bff53a3739ecfddb4ffa9a6dbde4d.1327858005.git.luto@amacapital.net/

This patch can be applied on top of v5.12-rc3 .  I would really
appreciate constructive reviews.

Previous versions:
v3: https://lore.kernel.org/r/20210311105242.874506-1-mic@digikod.net
v2: https://lore.kernel.org/r/20210310181857.401675-1-mic@digikod.net
v1: https://lore.kernel.org/r/20210310161000.382796-1-mic@digikod.net

Regards,

Mickaël Salaün (1):
  fs: Allow no_new_privs tasks to call chroot(2)

 fs/open.c | 23 +++++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)


base-commit: 1e28eed17697bcf343c6743f0028cc3b5dd88bf0
-- 
2.30.2

[PATCH v4 1/1] fs: Allow no_new_privs tasks to call chroot(2)

Message details and related content
From: Mickaël Salaün <redacted>

Being able to easily change root directories enables to ease some
development workflow and can be used as a tool to strengthen
unprivileged security sandboxes.  chroot(2) is not an access-control
mechanism per se, but it can be used to limit the absolute view of the
filesystem, and then limit ways to access data and kernel interfaces
(e.g. /proc, /sys, /dev, etc.).

Users may not wish to expose namespace complexity to potentially
malicious processes, or limit their use because of limited resources.
The chroot feature is much more simple (and limited) than the mount
namespace, but can still be useful.  As for containers, users of
chroot(2) should take care of file descriptors or data accessible by
other means (e.g. current working directory, leaked FDs, passed FDs,
devices, mount points, etc.).  There is a lot of literature that discuss
the limitations of chroot, and users of this feature should be aware of
the multiple ways to bypass it.  Using chroot(2) for security purposes
can make sense if it is combined with other features (e.g. dedicated
user, seccomp, LSM access-controls, etc.).

One could argue that chroot(2) is useless without a properly populated
root hierarchy (i.e. without /dev and /proc).  However, there are
multiple use cases that don't require the chrooting process to create
file hierarchies with special files nor mount points, e.g.:
* A process sandboxing itself, once all its libraries are loaded, may
  not need files other than regular files, or even no file at all.
* Some pre-populated root hierarchies could be used to chroot into,
  provided for instance by development environments or tailored
  distributions.
* Processes executed in a chroot may not require access to these special
  files (e.g. with minimal runtimes, or by emulating some special files
  with a LD_PRELOADed library or seccomp).

Unprivileged chroot is especially interesting for userspace developers
wishing to harden their applications.  For instance, chroot(2) and Yama
enable to build a capability-based security (i.e. remove filesystem
ambient accesses) by calling chroot/chdir with an empty directory and
accessing data through dedicated file descriptors obtained with
openat2(2) and RESOLVE_BENEATH/RESOLVE_IN_ROOT/RESOLVE_NO_MAGICLINKS.

65 more lines

diff --git a/fs/open.c b/fs/open.c
index e53af13b5835..da46eb28a3a6 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -532,6 +532,24 @@ SYSCALL_DEFINE1(fchdir, unsigned int, fd)
 	return error;
 }
 
+static inline int current_chroot_allowed(void)
+{
+	/*
+	 * Changing the root directory for the calling task (and its future
+	 * children) requires that this task has CAP_SYS_CHROOT in its
+	 * namespace, or be running with no_new_privs and not sharing its
+	 * fs_struct and not escaping its current root (cf. create_user_ns()).
+	 * As for seccomp, checking no_new_privs avoids scenarios where
+	 * unprivileged tasks can affect the behavior of privileged children.
+	 */
+	if (task_no_new_privs(current) && current->fs->users == 1 &&
+			!current_chrooted())
+		return 0;
+	if (ns_capable(current_user_ns(), CAP_SYS_CHROOT))
+		return 0;
+	return -EPERM;
+}
+
 SYSCALL_DEFINE1(chroot, const char __user *, filename)
 {
 	struct path path;
@@ -546,9 +564,10 @@ SYSCALL_DEFINE1(chroot, const char __user *, filename)
 	if (error)
 		goto dput_and_out;
 
-	error = -EPERM;
-	if (!ns_capable(current_user_ns(), CAP_SYS_CHROOT))
+	error = current_chroot_allowed();
+	if (error)
 		goto dput_and_out;
+
 	error = security_path_chroot(&path);
 	if (error)
 		goto dput_and_out;
-- 
2.30.2

Re: [PATCH v4 1/1] fs: Allow no_new_privs tasks to call chroot(2)

Message details and related content
On Tue, Mar 16, 2021 at 06:01:35PM +0100, Mickaël Salaün wrote:
From: Mickaël Salaün <redacted>

Being able to easily change root directories enables to ease some
development workflow and can be used as a tool to strengthen
unprivileged security sandboxes.  chroot(2) is not an access-control
mechanism per se, but it can be used to limit the absolute view of the
filesystem, and then limit ways to access data and kernel interfaces
(e.g. /proc, /sys, /dev, etc.).

Users may not wish to expose namespace complexity to potentially
malicious processes, or limit their use because of limited resources.
The chroot feature is much more simple (and limited) than the mount
namespace, but can still be useful.  As for containers, users of
chroot(2) should take care of file descriptors or data accessible by
other means (e.g. current working directory, leaked FDs, passed FDs,
devices, mount points, etc.).  There is a lot of literature that discuss
the limitations of chroot, and users of this feature should be aware of
the multiple ways to bypass it.  Using chroot(2) for security purposes
can make sense if it is combined with other features (e.g. dedicated
user, seccomp, LSM access-controls, etc.).

One could argue that chroot(2) is useless without a properly populated
root hierarchy (i.e. without /dev and /proc).  However, there are
multiple use cases that don't require the chrooting process to create
file hierarchies with special files nor mount points, e.g.:
* A process sandboxing itself, once all its libraries are loaded, may
  not need files other than regular files, or even no file at all.
* Some pre-populated root hierarchies could be used to chroot into,
  provided for instance by development environments or tailored
  distributions.
* Processes executed in a chroot may not require access to these special
  files (e.g. with minimal runtimes, or by emulating some special files
  with a LD_PRELOADed library or seccomp).

Unprivileged chroot is especially interesting for userspace developers
wishing to harden their applications.  For instance, chroot(2) and Yama
enable to build a capability-based security (i.e. remove filesystem
ambient accesses) by calling chroot/chdir with an empty directory and
accessing data through dedicated file descriptors obtained with
openat2(2) and RESOLVE_BENEATH/RESOLVE_IN_ROOT/RESOLVE_NO_MAGICLINKS.

42 more quoted lines

Thanks for the updates! I find this version much easier to read. :)

Reviewed-by: Kees Cook <redacted>

-- 
Kees Cook

Re: [PATCH v4 1/1] fs: Allow no_new_privs tasks to call chroot(2)

Message details and related content
On Tue, Mar 16, 2021 at 6:02 PM Mickaël Salaün [off-list ref] wrote:
One could argue that chroot(2) is useless without a properly populated
root hierarchy (i.e. without /dev and /proc).  However, there are
multiple use cases that don't require the chrooting process to create
file hierarchies with special files nor mount points, e.g.:
* A process sandboxing itself, once all its libraries are loaded, may
  not need files other than regular files, or even no file at all.
* Some pre-populated root hierarchies could be used to chroot into,
  provided for instance by development environments or tailored
  distributions.
* Processes executed in a chroot may not require access to these special
  files (e.g. with minimal runtimes, or by emulating some special files
  with a LD_PRELOADed library or seccomp).

Unprivileged chroot is especially interesting for userspace developers
wishing to harden their applications.  For instance, chroot(2) and Yama
enable to build a capability-based security (i.e. remove filesystem
ambient accesses) by calling chroot/chdir with an empty directory and
accessing data through dedicated file descriptors obtained with
openat2(2) and RESOLVE_BENEATH/RESOLVE_IN_ROOT/RESOLVE_NO_MAGICLINKS.
I don't entirely understand. Are you writing this with the assumption
that a future change will make it possible to set these RESOLVE flags
process-wide, or something like that?


As long as that doesn't exist, I think that to make this safe, you'd
have to do something like the following - let a child process set up a
new mount namespace for you, and then chroot() into that namespace's
root:

struct shared_data {
  int root_fd;
};
int helper_fn(void *args) {
  struct shared_data *shared = args;
  mount("none", "/tmp", "tmpfs", MS_NOSUID|MS_NODEV, "");
  mkdir("/tmp/old_root", 0700);
  pivot_root("/tmp", "/tmp/old_root");
  umount("/tmp/old_root", "");
  shared->root_fd = open("/", O_PATH);
}
void setup_chroot() {
  struct shared_data shared = {};
  prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
  clone(helper_fn, my_stack,
CLONE_VFORK|CLONE_VM|CLONE_FILES|CLONE_NEWUSER|CLONE_NEWNS|SIGCHLD,
NULL);
  fchdir(shared.root_fd);
  chroot(".");
}

[...]
quoted hunk
diff --git a/fs/open.c b/fs/open.c
[...]
quoted hunk
+static inline int current_chroot_allowed(void)
+{
+       /*
+        * Changing the root directory for the calling task (and its future
+        * children) requires that this task has CAP_SYS_CHROOT in its
+        * namespace, or be running with no_new_privs and not sharing its
+        * fs_struct and not escaping its current root (cf. create_user_ns()).
+        * As for seccomp, checking no_new_privs avoids scenarios where
+        * unprivileged tasks can affect the behavior of privileged children.
+        */
+       if (task_no_new_privs(current) && current->fs->users == 1 &&
this read of current->fs->users should be using READ_ONCE()
quoted hunk
+                       !current_chrooted())
+               return 0;
+       if (ns_capable(current_user_ns(), CAP_SYS_CHROOT))
+               return 0;
+       return -EPERM;
+}
[...]

Overall I think this change is a good idea.

Re: [PATCH v4 1/1] fs: Allow no_new_privs tasks to call chroot(2)

Message details and related content
On Tue, Mar 16, 2021 at 08:04:09PM +0100, Jann Horn wrote:
On Tue, Mar 16, 2021 at 6:02 PM Mickaël Salaün [off-list ref] wrote:
quoted
One could argue that chroot(2) is useless without a properly populated
root hierarchy (i.e. without /dev and /proc).  However, there are
multiple use cases that don't require the chrooting process to create
file hierarchies with special files nor mount points, e.g.:
* A process sandboxing itself, once all its libraries are loaded, may
  not need files other than regular files, or even no file at all.
* Some pre-populated root hierarchies could be used to chroot into,
  provided for instance by development environments or tailored
  distributions.
* Processes executed in a chroot may not require access to these special
  files (e.g. with minimal runtimes, or by emulating some special files
  with a LD_PRELOADed library or seccomp).

Unprivileged chroot is especially interesting for userspace developers
wishing to harden their applications.  For instance, chroot(2) and Yama
enable to build a capability-based security (i.e. remove filesystem
ambient accesses) by calling chroot/chdir with an empty directory and
accessing data through dedicated file descriptors obtained with
openat2(2) and RESOLVE_BENEATH/RESOLVE_IN_ROOT/RESOLVE_NO_MAGICLINKS.
I don't entirely understand. Are you writing this with the assumption
that a future change will make it possible to set these RESOLVE flags
process-wide, or something like that?
I thought it meant "open all out-of-chroot dirs as fds using RESOLVE_...
flags then chroot". As in, there's no way to then escape "up" for the
old opens, and the new opens stay in the chroot.
[...]
quoted
diff --git a/fs/open.c b/fs/open.c
[...]
quoted
+static inline int current_chroot_allowed(void)
+{
+       /*
+        * Changing the root directory for the calling task (and its future
+        * children) requires that this task has CAP_SYS_CHROOT in its
+        * namespace, or be running with no_new_privs and not sharing its
+        * fs_struct and not escaping its current root (cf. create_user_ns()).
+        * As for seccomp, checking no_new_privs avoids scenarios where
+        * unprivileged tasks can affect the behavior of privileged children.
+        */
+       if (task_no_new_privs(current) && current->fs->users == 1 &&
this read of current->fs->users should be using READ_ONCE()
Ah yeah, good call. I should remember this when I think "can this race?"
:P

-- 
Kees Cook

Re: [PATCH v4 1/1] fs: Allow no_new_privs tasks to call chroot(2)

Message details and related content
On 16/03/2021 20:24, Kees Cook wrote:
On Tue, Mar 16, 2021 at 08:04:09PM +0100, Jann Horn wrote:
quoted
On Tue, Mar 16, 2021 at 6:02 PM Mickaël Salaün [off-list ref] wrote:
quoted
One could argue that chroot(2) is useless without a properly populated
root hierarchy (i.e. without /dev and /proc).  However, there are
multiple use cases that don't require the chrooting process to create
file hierarchies with special files nor mount points, e.g.:
* A process sandboxing itself, once all its libraries are loaded, may
  not need files other than regular files, or even no file at all.
* Some pre-populated root hierarchies could be used to chroot into,
  provided for instance by development environments or tailored
  distributions.
* Processes executed in a chroot may not require access to these special
  files (e.g. with minimal runtimes, or by emulating some special files
  with a LD_PRELOADed library or seccomp).

Unprivileged chroot is especially interesting for userspace developers
wishing to harden their applications.  For instance, chroot(2) and Yama
enable to build a capability-based security (i.e. remove filesystem
ambient accesses) by calling chroot/chdir with an empty directory and
accessing data through dedicated file descriptors obtained with
openat2(2) and RESOLVE_BENEATH/RESOLVE_IN_ROOT/RESOLVE_NO_MAGICLINKS.
I don't entirely understand. Are you writing this with the assumption
that a future change will make it possible to set these RESOLVE flags
process-wide, or something like that?
I thought it meant "open all out-of-chroot dirs as fds using RESOLVE_...
flags then chroot". As in, there's no way to then escape "up" for the
old opens, and the new opens stay in the chroot.
Yes, that was the idea.
quoted
[...]
quoted
diff --git a/fs/open.c b/fs/open.c
[...]
quoted
+static inline int current_chroot_allowed(void)
+{
+       /*
+        * Changing the root directory for the calling task (and its future
+        * children) requires that this task has CAP_SYS_CHROOT in its
+        * namespace, or be running with no_new_privs and not sharing its
+        * fs_struct and not escaping its current root (cf. create_user_ns()).
+        * As for seccomp, checking no_new_privs avoids scenarios where
+        * unprivileged tasks can affect the behavior of privileged children.
+        */
+       if (task_no_new_privs(current) && current->fs->users == 1 &&
this read of current->fs->users should be using READ_ONCE()
Ah yeah, good call. I should remember this when I think "can this race?"
:P

Re: [PATCH v4 1/1] fs: Allow no_new_privs tasks to call chroot(2)

Message details and related content
On 16/03/2021 20:04, Jann Horn wrote:
On Tue, Mar 16, 2021 at 6:02 PM Mickaël Salaün [off-list ref] wrote:
quoted
One could argue that chroot(2) is useless without a properly populated
root hierarchy (i.e. without /dev and /proc).  However, there are
multiple use cases that don't require the chrooting process to create
file hierarchies with special files nor mount points, e.g.:
* A process sandboxing itself, once all its libraries are loaded, may
  not need files other than regular files, or even no file at all.
* Some pre-populated root hierarchies could be used to chroot into,
  provided for instance by development environments or tailored
  distributions.
* Processes executed in a chroot may not require access to these special
  files (e.g. with minimal runtimes, or by emulating some special files
  with a LD_PRELOADed library or seccomp).

Unprivileged chroot is especially interesting for userspace developers
wishing to harden their applications.  For instance, chroot(2) and Yama
enable to build a capability-based security (i.e. remove filesystem
ambient accesses) by calling chroot/chdir with an empty directory and
accessing data through dedicated file descriptors obtained with
openat2(2) and RESOLVE_BENEATH/RESOLVE_IN_ROOT/RESOLVE_NO_MAGICLINKS.
I don't entirely understand. Are you writing this with the assumption
that a future change will make it possible to set these RESOLVE flags
process-wide, or something like that?
No, this scenario is for applications willing to sandbox themselves and
only use the FDs to access legitimate data.

As long as that doesn't exist, I think that to make this safe, you'd
have to do something like the following - let a child process set up a
new mount namespace for you, and then chroot() into that namespace's
root:

struct shared_data {
  int root_fd;
};
int helper_fn(void *args) {
  struct shared_data *shared = args;
  mount("none", "/tmp", "tmpfs", MS_NOSUID|MS_NODEV, "");
  mkdir("/tmp/old_root", 0700);
  pivot_root("/tmp", "/tmp/old_root");
  umount("/tmp/old_root", "");
  shared->root_fd = open("/", O_PATH);
}
void setup_chroot() {
  struct shared_data shared = {};
  prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
  clone(helper_fn, my_stack,
CLONE_VFORK|CLONE_VM|CLONE_FILES|CLONE_NEWUSER|CLONE_NEWNS|SIGCHLD,
NULL);
  fchdir(shared.root_fd);
  chroot(".");
}
What about this?
chdir("/proc/self/fdinfo");
chroot(".");
close(all unnecessary FDs);
[...]
quoted
diff --git a/fs/open.c b/fs/open.c
[...]
quoted
+static inline int current_chroot_allowed(void)
+{
+       /*
+        * Changing the root directory for the calling task (and its future
+        * children) requires that this task has CAP_SYS_CHROOT in its
+        * namespace, or be running with no_new_privs and not sharing its
+        * fs_struct and not escaping its current root (cf. create_user_ns()).
+        * As for seccomp, checking no_new_privs avoids scenarios where
+        * unprivileged tasks can affect the behavior of privileged children.
+        */
+       if (task_no_new_privs(current) && current->fs->users == 1 &&
this read of current->fs->users should be using READ_ONCE()
Right, I'll fix this.
quoted
+                       !current_chrooted())
+               return 0;
+       if (ns_capable(current_user_ns(), CAP_SYS_CHROOT))
+               return 0;
+       return -EPERM;
+}
[...]

Overall I think this change is a good idea.

Re: [PATCH v4 1/1] fs: Allow no_new_privs tasks to call chroot(2)

Message details and related content
On Tue, Mar 16, 2021 at 8:26 PM Mickaël Salaün [off-list ref] wrote:
On 16/03/2021 20:04, Jann Horn wrote:
quoted
On Tue, Mar 16, 2021 at 6:02 PM Mickaël Salaün [off-list ref] wrote:
quoted
One could argue that chroot(2) is useless without a properly populated
root hierarchy (i.e. without /dev and /proc).  However, there are
multiple use cases that don't require the chrooting process to create
file hierarchies with special files nor mount points, e.g.:
* A process sandboxing itself, once all its libraries are loaded, may
  not need files other than regular files, or even no file at all.
* Some pre-populated root hierarchies could be used to chroot into,
  provided for instance by development environments or tailored
  distributions.
* Processes executed in a chroot may not require access to these special
  files (e.g. with minimal runtimes, or by emulating some special files
  with a LD_PRELOADed library or seccomp).

Unprivileged chroot is especially interesting for userspace developers
wishing to harden their applications.  For instance, chroot(2) and Yama
enable to build a capability-based security (i.e. remove filesystem
ambient accesses) by calling chroot/chdir with an empty directory and
accessing data through dedicated file descriptors obtained with
openat2(2) and RESOLVE_BENEATH/RESOLVE_IN_ROOT/RESOLVE_NO_MAGICLINKS.
I don't entirely understand. Are you writing this with the assumption
that a future change will make it possible to set these RESOLVE flags
process-wide, or something like that?
No, this scenario is for applications willing to sandbox themselves and
only use the FDs to access legitimate data.
But if you're chrooted to /proc/self/fdinfo and have an fd to some
directory - let's say /home/user/Downloads - there is nothing that
ensures that you only use that fd with RESOLVE_BENEATH, right? If the
application is compromised, it can do something like openat(fd,
"../.bashrc", O_RDWR), right? Or am I missing something?
quoted
As long as that doesn't exist, I think that to make this safe, you'd
have to do something like the following - let a child process set up a
new mount namespace for you, and then chroot() into that namespace's
root:

struct shared_data {
  int root_fd;
};
int helper_fn(void *args) {
  struct shared_data *shared = args;
  mount("none", "/tmp", "tmpfs", MS_NOSUID|MS_NODEV, "");
  mkdir("/tmp/old_root", 0700);
  pivot_root("/tmp", "/tmp/old_root");
  umount("/tmp/old_root", "");
  shared->root_fd = open("/", O_PATH);
}
void setup_chroot() {
  struct shared_data shared = {};
  prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
  clone(helper_fn, my_stack,
CLONE_VFORK|CLONE_VM|CLONE_FILES|CLONE_NEWUSER|CLONE_NEWNS|SIGCHLD,
NULL);
  fchdir(shared.root_fd);
  chroot(".");
}
What about this?
chdir("/proc/self/fdinfo");
chroot(".");
close(all unnecessary FDs);
That breaks down if you can e.g. get a unix domain socket connected to
a process in a different chroot, right? Isn't that a bit too fragile?

Re: [PATCH v4 1/1] fs: Allow no_new_privs tasks to call chroot(2)

Message details and related content
On 16/03/2021 20:31, Jann Horn wrote:
On Tue, Mar 16, 2021 at 8:26 PM Mickaël Salaün [off-list ref] wrote:
quoted
On 16/03/2021 20:04, Jann Horn wrote:
quoted
On Tue, Mar 16, 2021 at 6:02 PM Mickaël Salaün [off-list ref] wrote:
quoted
One could argue that chroot(2) is useless without a properly populated
root hierarchy (i.e. without /dev and /proc).  However, there are
multiple use cases that don't require the chrooting process to create
file hierarchies with special files nor mount points, e.g.:
* A process sandboxing itself, once all its libraries are loaded, may
  not need files other than regular files, or even no file at all.
* Some pre-populated root hierarchies could be used to chroot into,
  provided for instance by development environments or tailored
  distributions.
* Processes executed in a chroot may not require access to these special
  files (e.g. with minimal runtimes, or by emulating some special files
  with a LD_PRELOADed library or seccomp).

Unprivileged chroot is especially interesting for userspace developers
wishing to harden their applications.  For instance, chroot(2) and Yama
enable to build a capability-based security (i.e. remove filesystem
ambient accesses) by calling chroot/chdir with an empty directory and
accessing data through dedicated file descriptors obtained with
openat2(2) and RESOLVE_BENEATH/RESOLVE_IN_ROOT/RESOLVE_NO_MAGICLINKS.
I don't entirely understand. Are you writing this with the assumption
that a future change will make it possible to set these RESOLVE flags
process-wide, or something like that?
No, this scenario is for applications willing to sandbox themselves and
only use the FDs to access legitimate data.
But if you're chrooted to /proc/self/fdinfo and have an fd to some
directory - let's say /home/user/Downloads - there is nothing that
ensures that you only use that fd with RESOLVE_BENEATH, right? If the
application is compromised, it can do something like openat(fd,
"../.bashrc", O_RDWR), right? Or am I missing something?
You're totally right, I was mistaken, this simple use case doesn't work
without a broker. Perhaps when seccomp will be able to check referenced
structs, or with a new FD limitation…
quoted
quoted
As long as that doesn't exist, I think that to make this safe, you'd
have to do something like the following - let a child process set up a
new mount namespace for you, and then chroot() into that namespace's
root:

struct shared_data {
  int root_fd;
};
int helper_fn(void *args) {
  struct shared_data *shared = args;
  mount("none", "/tmp", "tmpfs", MS_NOSUID|MS_NODEV, "");
  mkdir("/tmp/old_root", 0700);
  pivot_root("/tmp", "/tmp/old_root");
  umount("/tmp/old_root", "");
  shared->root_fd = open("/", O_PATH);
}
void setup_chroot() {
  struct shared_data shared = {};
  prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
  clone(helper_fn, my_stack,
CLONE_VFORK|CLONE_VM|CLONE_FILES|CLONE_NEWUSER|CLONE_NEWNS|SIGCHLD,
NULL);
  fchdir(shared.root_fd);
  chroot(".");
}
What about this?
chdir("/proc/self/fdinfo");
chroot(".");
close(all unnecessary FDs);
That breaks down if you can e.g. get a unix domain socket connected to
a process in a different chroot, right? Isn't that a bit too fragile?
This relies on other (trusted) components, and yes it is fragile if the
process communicates with a service able send FDs.
Keyboard shortcuts
hback out one level
jnext message in thread / next series-diff position
kprevious message in thread / previous series-diff position
ldrill in
Escclose help / fold thread tree
?toggle this help