* [PATCH] pasta: Add --no-pidns to keep spawned command in caller's PID namespace
@ 2026-09-06 13:17 Christian Korneck
2026-09-08 6:03 ` David Gibson
2026-09-08 9:45 ` Stefano Brivio
0 siblings, 2 replies; 5+ messages in thread
From: Christian Korneck @ 2026-09-06 13:17 UTC (permalink / raw)
To: passt-dev; +Cc: Christian Korneck
This is to allow running pasta inside a container without unmasking
/proc for the whole container (Docker's --security-opt
systempaths=unconfined, Podman's --security-opt unmask=ALL), which is
undesirable as it exposes /proc/sysrq-trigger and other masked paths.
In spawn mode, pasta clones the command with CLONE_NEWPID and mounts a
new procfs instance on /proc, so that it matches the new PID namespace.
Mounting procfs in a new user namespace requires a fully visible,
unobstructed procfs. Container runtimes deliberately obstruct /proc
(Docker, for example, masks /proc/kcore and friends and mounts
/proc/sys read-only), so the mount is refused:
Couldn't mount /proc: Operation not permitted
We only warn and continue, leaving the command in a new PID namespace
while the visible /proc still numbers processes in the outer one.
Anything resolving its own PID through /proc then fails, for example
bubblewrap:
bwrap: open /proc/22/ns/ns failed: No such file or directory
Add a --no-pidns option: skip CLONE_NEWPID for the spawned command and
don't mount /proc, which is then not needed. User, network, mount, UTS
and IPC namespaces, --config-net and port forwarding are unaffected.
The option is rejected together with PID or --netns, as it only makes
sense when we spawn the command ourselves.
Add a test checking that, by default, the command runs in a new PID
namespace, and that --no-pidns keeps it in the caller's one.
Signed-off-by: Christian Korneck <christian@korneck.de>
---
conf.c | 11 +++++++++++
passt.1 | 12 ++++++++++++
passt.h | 2 ++
pasta.c | 18 +++++++++++-------
test/pasta_options/no_pidns | 25 +++++++++++++++++++++++++
test/run | 1 +
6 files changed, 62 insertions(+), 7 deletions(-)
create mode 100644 test/pasta_options/no_pidns
diff --git a/conf.c b/conf.c
index faf2681..16b2f40 100644
--- a/conf.c
+++ b/conf.c
@@ -742,6 +742,7 @@ pasta_opts:
" implied if PATH or NAME are given without --userns\n"
" --no-netns-quit Don't quit if filesystem-bound target\n"
" network namespace is deleted\n"
+ " --no-pidns Don't spawn command in a new PID namespace\n"
" --config-net Configure tap interface in namespace\n"
" --no-copy-routes DEPRECATED:\n"
" Don't copy all routes to namespace\n"
@@ -1348,6 +1349,7 @@ void conf(struct ctx *c, int argc, char **argv)
{"stats", required_argument, NULL, 31 },
{"conf-path", required_argument, NULL, 'c' },
{"chroot-fallback", no_argument, NULL, 32 },
+ {"no-pidns", no_argument, NULL, 33 },
{ 0 },
};
const char *optstring = "+dqfel:hs:c:F:I:p:P:m:a:n:M:g:i:o:D:S:H:461t:u:T:U:";
@@ -1589,6 +1591,12 @@ void conf(struct ctx *c, int argc, char **argv)
case 32:
c->chroot_fallback = true;
break;
+ case 33:
+ if (c->mode != MODE_PASTA)
+ die("--no-pidns is for pasta mode only");
+
+ c->no_pidns = true;
+ break;
case 'd':
c->debug = 1;
c->quiet = 0;
@@ -1948,6 +1956,9 @@ void conf(struct ctx *c, int argc, char **argv)
else if (optind != argc)
die("Extra non-option argument: %s", argv[optind]);
+ if (c->no_pidns && *netns)
+ die("--no-pidns is incompatible with PID or --netns");
+
conf_open_files(c); /* Before any possible setuid() / setgid() */
isolate_user(c, uid, gid, !netns_only, userns);
diff --git a/passt.1 b/passt.1
index 53e072a..0b780fe 100644
--- a/passt.1
+++ b/passt.1
@@ -704,6 +704,18 @@ is bound to the filesystem, and the given path is deleted, or if the target
network namespace is represented by a procfs entry, and that entry is deleted,
representing the fact that a process with the given PID terminated.
+.TP
+.BR \-\-no-pidns
+Don't create a new PID namespace for the spawned command or shell: keep it in
+the PID namespace \fBpasta\fR itself runs in, and don't mount a new
+\fIprocfs\fR instance on \fI/proc\fR for it. This is useful in environments
+where mounting \fIprocfs\fR is not permitted, such as containers, where the
+command would otherwise get a \fI/proc\fR view that doesn't match its own PID
+namespace. Note that, without a PID namespace, processes started by the
+command are not terminated once the command exits.
+
+This option can't be specified with a PID or with \-\-netns.
+
.TP
.BR \-\-config-net
Configure networking in the namespace: set up addresses and routes as configured
diff --git a/passt.h b/passt.h
index 51ccd4f..afd8e9f 100644
--- a/passt.h
+++ b/passt.h
@@ -195,6 +195,7 @@ struct ip6_ctx {
* @pasta_ifn: Name of namespace interface for pasta
* @pasta_ifi: Index of namespace interface for pasta
* @pasta_conf_ns: Configure namespace after creating it
+ * @no_pidns: Don't create a new PID namespace for spawned command
* @fwd: Forwarding tables
* @fwd_pending: Pending forward tables
* @no_tcp: Disable TCP operation
@@ -278,6 +279,7 @@ struct ctx {
char pasta_ifn[IF_NAMESIZE];
unsigned int pasta_ifi;
int pasta_conf_ns;
+ bool no_pidns;
struct fwd_table *fwd[PIF_NUM_TYPES];
struct fwd_table *fwd_pending[PIF_NUM_TYPES];
diff --git a/pasta.c b/pasta.c
index 5aa56b7..2d916e1 100644
--- a/pasta.c
+++ b/pasta.c
@@ -194,15 +194,17 @@ static int pasta_spawn_cmd(void *arg)
if (prctl(PR_SET_PDEATHSIG, SIGKILL))
die_perror("Couldn't set PR_SET_PDEATHSIG");
- /* We run in a detached PID and mount namespace: mount /proc over */
- if (mount("", "/proc", "proc", 0, NULL))
+ a = (const struct pasta_spawn_cmd_arg *)arg;
+
+ /* We run in a detached mount namespace, and, unless --no-pidns was
+ * given, in a detached PID namespace: mount /proc over
+ */
+ if (!a->c->no_pidns && mount("", "/proc", "proc", 0, NULL))
warn_perror("Couldn't mount /proc");
if (write_file("/proc/sys/net/ipv4/ping_group_range", "0 0"))
warn("Cannot set ping_group_range, ICMP requests might fail");
- a = (const struct pasta_spawn_cmd_arg *)arg;
-
conf_hostname_len = strlen(a->c->hostname);
if (conf_hostname_len > 0) {
if (sethostname(a->c->hostname, conf_hostname_len))
@@ -243,6 +245,7 @@ static int pasta_spawn_cmd(void *arg)
void pasta_start_ns(struct ctx *c, uid_t uid, gid_t gid, bool config_idmaps,
int argc, char *argv[])
{
+ int flags = CLONE_NEWIPC | CLONE_NEWNET | CLONE_NEWUTS | CLONE_NEWNS;
char ns_fn_stack[NS_FN_STACK_SIZE]
__attribute__ ((aligned(__alignof__(max_align_t))));
struct pasta_spawn_cmd_arg arg = {
@@ -293,10 +296,11 @@ void pasta_start_ns(struct ctx *c, uid_t uid, gid_t gid, bool config_idmaps,
sigaddset(&set, SIGUSR1);
sigprocmask(SIG_BLOCK, &set, NULL);
+ if (!c->no_pidns)
+ flags |= CLONE_NEWPID;
+
pasta_child_pid = do_clone(pasta_spawn_cmd, ns_fn_stack,
- sizeof(ns_fn_stack),
- CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNET |
- CLONE_NEWUTS | CLONE_NEWNS | SIGCHLD,
+ sizeof(ns_fn_stack), flags | SIGCHLD,
(void *)&arg);
if (pasta_child_pid == -1)
diff --git a/test/pasta_options/no_pidns b/test/pasta_options/no_pidns
new file mode 100644
index 0000000..f565b2f
--- /dev/null
+++ b/test/pasta_options/no_pidns
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+# for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+# for network namespace/tap device mode
+#
+# test/pasta_options/no_pidns - Check --no-pidns handling
+
+htools readlink
+
+test Command is spawned in a new PID namespace by default
+set PIDNS __STATEDIR__/pidns
+set OUT __STATEDIR__/no-pidns.out
+set ERR __STATEDIR__/no-pidns.err
+
+passt readlink /proc/self/ns/pid > __PIDNS__
+passt ./pasta -q -- readlink /proc/self/ns/pid > __OUT__ 2> __ERR__
+check [ "$(cat __PIDNS__)" != "$(cat __OUT__)" ]
+
+test --no-pidns keeps command in the caller's PID namespace
+passt ./pasta -q --no-pidns -- readlink /proc/self/ns/pid > __OUT__ 2> __ERR__
+check [ "$(cat __PIDNS__)" = "$(cat __OUT__)" ]
+check [ ! -s __ERR__ ]
diff --git a/test/run b/test/run
index c4073cc..14ddbbf 100755
--- a/test/run
+++ b/test/run
@@ -86,6 +86,7 @@ run() {
setup pasta_options
test pasta_options/log_to_file
test pasta_options/netns_only
+ test pasta_options/no_pidns
teardown pasta_options
setup build
--
2.55.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] pasta: Add --no-pidns to keep spawned command in caller's PID namespace
2026-09-06 13:17 [PATCH] pasta: Add --no-pidns to keep spawned command in caller's PID namespace Christian Korneck
@ 2026-09-08 6:03 ` David Gibson
2026-09-08 9:45 ` Stefano Brivio
2026-09-08 9:45 ` Stefano Brivio
1 sibling, 1 reply; 5+ messages in thread
From: David Gibson @ 2026-09-08 6:03 UTC (permalink / raw)
To: Christian Korneck; +Cc: passt-dev
[-- Attachment #1: Type: text/plain, Size: 9805 bytes --]
On Sun, Sep 06, 2026 at 03:17:58PM +0200, Christian Korneck wrote:
> This is to allow running pasta inside a container without unmasking
> /proc for the whole container (Docker's --security-opt
> systempaths=unconfined, Podman's --security-opt unmask=ALL), which is
> undesirable as it exposes /proc/sysrq-trigger and other masked paths.
>
> In spawn mode, pasta clones the command with CLONE_NEWPID and mounts a
> new procfs instance on /proc, so that it matches the new PID namespace.
>
> Mounting procfs in a new user namespace requires a fully visible,
> unobstructed procfs. Container runtimes deliberately obstruct /proc
> (Docker, for example, masks /proc/kcore and friends and mounts
> /proc/sys read-only), so the mount is refused:
>
> Couldn't mount /proc: Operation not permitted
>
> We only warn and continue, leaving the command in a new PID namespace
> while the visible /proc still numbers processes in the outer one.
> Anything resolving its own PID through /proc then fails, for example
> bubblewrap:
>
> bwrap: open /proc/22/ns/ns failed: No such file or directory
>
> Add a --no-pidns option: skip CLONE_NEWPID for the spawned command and
> don't mount /proc, which is then not needed. User, network, mount, UTS
> and IPC namespaces, --config-net and port forwarding are unaffected.
> The option is rejected together with PID or --netns, as it only makes
> sense when we spawn the command ourselves.
>
> Add a test checking that, by default, the command runs in a new PID
> namespace, and that --no-pidns keeps it in the caller's one.
I can see that this would be useful. On the other hand, I could say
that for just about any combinatiom of unshare(1) options, and I don't
think we want to reimplement all of unshare(1) in pasta. Note that it
is always possible to create a namespace using unshare(1) with
arbitrary options and then connect it with pasta, rather than having
pasta create its own.
That said, I've never seen the point of creating a pidns by default in
pasta. We're not isolating the filesystem, so I don't see that
there's much to gain by isolating pids. I'd be happy enough to see
CLONE_NEWPID removed unconditionally.
>
> Signed-off-by: Christian Korneck <christian@korneck.de>
> ---
> conf.c | 11 +++++++++++
> passt.1 | 12 ++++++++++++
> passt.h | 2 ++
> pasta.c | 18 +++++++++++-------
> test/pasta_options/no_pidns | 25 +++++++++++++++++++++++++
> test/run | 1 +
> 6 files changed, 62 insertions(+), 7 deletions(-)
> create mode 100644 test/pasta_options/no_pidns
>
> diff --git a/conf.c b/conf.c
> index faf2681..16b2f40 100644
> --- a/conf.c
> +++ b/conf.c
> @@ -742,6 +742,7 @@ pasta_opts:
> " implied if PATH or NAME are given without --userns\n"
> " --no-netns-quit Don't quit if filesystem-bound target\n"
> " network namespace is deleted\n"
> + " --no-pidns Don't spawn command in a new PID namespace\n"
> " --config-net Configure tap interface in namespace\n"
> " --no-copy-routes DEPRECATED:\n"
> " Don't copy all routes to namespace\n"
> @@ -1348,6 +1349,7 @@ void conf(struct ctx *c, int argc, char **argv)
> {"stats", required_argument, NULL, 31 },
> {"conf-path", required_argument, NULL, 'c' },
> {"chroot-fallback", no_argument, NULL, 32 },
> + {"no-pidns", no_argument, NULL, 33 },
> { 0 },
> };
> const char *optstring = "+dqfel:hs:c:F:I:p:P:m:a:n:M:g:i:o:D:S:H:461t:u:T:U:";
> @@ -1589,6 +1591,12 @@ void conf(struct ctx *c, int argc, char **argv)
> case 32:
> c->chroot_fallback = true;
> break;
> + case 33:
> + if (c->mode != MODE_PASTA)
> + die("--no-pidns is for pasta mode only");
> +
> + c->no_pidns = true;
> + break;
> case 'd':
> c->debug = 1;
> c->quiet = 0;
> @@ -1948,6 +1956,9 @@ void conf(struct ctx *c, int argc, char **argv)
> else if (optind != argc)
> die("Extra non-option argument: %s", argv[optind]);
>
> + if (c->no_pidns && *netns)
> + die("--no-pidns is incompatible with PID or --netns");
> +
> conf_open_files(c); /* Before any possible setuid() / setgid() */
>
> isolate_user(c, uid, gid, !netns_only, userns);
> diff --git a/passt.1 b/passt.1
> index 53e072a..0b780fe 100644
> --- a/passt.1
> +++ b/passt.1
> @@ -704,6 +704,18 @@ is bound to the filesystem, and the given path is deleted, or if the target
> network namespace is represented by a procfs entry, and that entry is deleted,
> representing the fact that a process with the given PID terminated.
>
> +.TP
> +.BR \-\-no-pidns
> +Don't create a new PID namespace for the spawned command or shell: keep it in
> +the PID namespace \fBpasta\fR itself runs in, and don't mount a new
> +\fIprocfs\fR instance on \fI/proc\fR for it. This is useful in environments
> +where mounting \fIprocfs\fR is not permitted, such as containers, where the
> +command would otherwise get a \fI/proc\fR view that doesn't match its own PID
> +namespace. Note that, without a PID namespace, processes started by the
> +command are not terminated once the command exits.
> +
> +This option can't be specified with a PID or with \-\-netns.
> +
> .TP
> .BR \-\-config-net
> Configure networking in the namespace: set up addresses and routes as configured
> diff --git a/passt.h b/passt.h
> index 51ccd4f..afd8e9f 100644
> --- a/passt.h
> +++ b/passt.h
> @@ -195,6 +195,7 @@ struct ip6_ctx {
> * @pasta_ifn: Name of namespace interface for pasta
> * @pasta_ifi: Index of namespace interface for pasta
> * @pasta_conf_ns: Configure namespace after creating it
> + * @no_pidns: Don't create a new PID namespace for spawned command
> * @fwd: Forwarding tables
> * @fwd_pending: Pending forward tables
> * @no_tcp: Disable TCP operation
> @@ -278,6 +279,7 @@ struct ctx {
> char pasta_ifn[IF_NAMESIZE];
> unsigned int pasta_ifi;
> int pasta_conf_ns;
> + bool no_pidns;
>
> struct fwd_table *fwd[PIF_NUM_TYPES];
> struct fwd_table *fwd_pending[PIF_NUM_TYPES];
> diff --git a/pasta.c b/pasta.c
> index 5aa56b7..2d916e1 100644
> --- a/pasta.c
> +++ b/pasta.c
> @@ -194,15 +194,17 @@ static int pasta_spawn_cmd(void *arg)
> if (prctl(PR_SET_PDEATHSIG, SIGKILL))
> die_perror("Couldn't set PR_SET_PDEATHSIG");
>
> - /* We run in a detached PID and mount namespace: mount /proc over */
> - if (mount("", "/proc", "proc", 0, NULL))
> + a = (const struct pasta_spawn_cmd_arg *)arg;
> +
> + /* We run in a detached mount namespace, and, unless --no-pidns was
> + * given, in a detached PID namespace: mount /proc over
> + */
> + if (!a->c->no_pidns && mount("", "/proc", "proc", 0, NULL))
> warn_perror("Couldn't mount /proc");
>
> if (write_file("/proc/sys/net/ipv4/ping_group_range", "0 0"))
> warn("Cannot set ping_group_range, ICMP requests might fail");
>
> - a = (const struct pasta_spawn_cmd_arg *)arg;
> -
> conf_hostname_len = strlen(a->c->hostname);
> if (conf_hostname_len > 0) {
> if (sethostname(a->c->hostname, conf_hostname_len))
> @@ -243,6 +245,7 @@ static int pasta_spawn_cmd(void *arg)
> void pasta_start_ns(struct ctx *c, uid_t uid, gid_t gid, bool config_idmaps,
> int argc, char *argv[])
> {
> + int flags = CLONE_NEWIPC | CLONE_NEWNET | CLONE_NEWUTS | CLONE_NEWNS;
> char ns_fn_stack[NS_FN_STACK_SIZE]
> __attribute__ ((aligned(__alignof__(max_align_t))));
> struct pasta_spawn_cmd_arg arg = {
> @@ -293,10 +296,11 @@ void pasta_start_ns(struct ctx *c, uid_t uid, gid_t gid, bool config_idmaps,
> sigaddset(&set, SIGUSR1);
> sigprocmask(SIG_BLOCK, &set, NULL);
>
> + if (!c->no_pidns)
> + flags |= CLONE_NEWPID;
> +
> pasta_child_pid = do_clone(pasta_spawn_cmd, ns_fn_stack,
> - sizeof(ns_fn_stack),
> - CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNET |
> - CLONE_NEWUTS | CLONE_NEWNS | SIGCHLD,
> + sizeof(ns_fn_stack), flags | SIGCHLD,
> (void *)&arg);
>
> if (pasta_child_pid == -1)
> diff --git a/test/pasta_options/no_pidns b/test/pasta_options/no_pidns
> new file mode 100644
> index 0000000..f565b2f
> --- /dev/null
> +++ b/test/pasta_options/no_pidns
> @@ -0,0 +1,25 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# PASST - Plug A Simple Socket Transport
> +# for qemu/UNIX domain socket mode
> +#
> +# PASTA - Pack A Subtle Tap Abstraction
> +# for network namespace/tap device mode
> +#
> +# test/pasta_options/no_pidns - Check --no-pidns handling
> +
> +htools readlink
> +
> +test Command is spawned in a new PID namespace by default
> +set PIDNS __STATEDIR__/pidns
> +set OUT __STATEDIR__/no-pidns.out
> +set ERR __STATEDIR__/no-pidns.err
> +
> +passt readlink /proc/self/ns/pid > __PIDNS__
> +passt ./pasta -q -- readlink /proc/self/ns/pid > __OUT__ 2> __ERR__
> +check [ "$(cat __PIDNS__)" != "$(cat __OUT__)" ]
> +
> +test --no-pidns keeps command in the caller's PID namespace
> +passt ./pasta -q --no-pidns -- readlink /proc/self/ns/pid > __OUT__ 2> __ERR__
> +check [ "$(cat __PIDNS__)" = "$(cat __OUT__)" ]
> +check [ ! -s __ERR__ ]
> diff --git a/test/run b/test/run
> index c4073cc..14ddbbf 100755
> --- a/test/run
> +++ b/test/run
> @@ -86,6 +86,7 @@ run() {
> setup pasta_options
> test pasta_options/log_to_file
> test pasta_options/netns_only
> + test pasta_options/no_pidns
> teardown pasta_options
>
> setup build
> --
> 2.55.0
>
--
David Gibson (he or they) | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you, not the other way
| around.
http://www.ozlabs.org/~dgibson
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] pasta: Add --no-pidns to keep spawned command in caller's PID namespace
2026-09-08 6:03 ` David Gibson
@ 2026-09-08 9:45 ` Stefano Brivio
2026-09-08 11:09 ` David Gibson
0 siblings, 1 reply; 5+ messages in thread
From: Stefano Brivio @ 2026-09-08 9:45 UTC (permalink / raw)
To: David Gibson; +Cc: Christian Korneck, passt-dev
On Tue, 8 Sep 2026 16:03:07 +1000
David Gibson <david@gibson.dropbear.id.au> wrote:
> On Sun, Sep 06, 2026 at 03:17:58PM +0200, Christian Korneck wrote:
> > This is to allow running pasta inside a container without unmasking
> > /proc for the whole container (Docker's --security-opt
> > systempaths=unconfined, Podman's --security-opt unmask=ALL), which is
> > undesirable as it exposes /proc/sysrq-trigger and other masked paths.
> >
> > In spawn mode, pasta clones the command with CLONE_NEWPID and mounts a
> > new procfs instance on /proc, so that it matches the new PID namespace.
> >
> > Mounting procfs in a new user namespace requires a fully visible,
> > unobstructed procfs. Container runtimes deliberately obstruct /proc
> > (Docker, for example, masks /proc/kcore and friends and mounts
> > /proc/sys read-only), so the mount is refused:
> >
> > Couldn't mount /proc: Operation not permitted
> >
> > We only warn and continue, leaving the command in a new PID namespace
> > while the visible /proc still numbers processes in the outer one.
> > Anything resolving its own PID through /proc then fails, for example
> > bubblewrap:
> >
> > bwrap: open /proc/22/ns/ns failed: No such file or directory
> >
> > Add a --no-pidns option: skip CLONE_NEWPID for the spawned command and
> > don't mount /proc, which is then not needed. User, network, mount, UTS
> > and IPC namespaces, --config-net and port forwarding are unaffected.
> > The option is rejected together with PID or --netns, as it only makes
> > sense when we spawn the command ourselves.
> >
> > Add a test checking that, by default, the command runs in a new PID
> > namespace, and that --no-pidns keeps it in the caller's one.
>
> I can see that this would be useful. On the other hand, I could say
> that for just about any combinatiom of unshare(1) options, and I don't
> think we want to reimplement all of unshare(1) in pasta.
Certainly we don't, but we don't get requests like these frequently
(except for the one to drop namespacing altogether, which is another
story), so I think it's acceptable.
The patch looks also pretty good and complete, so really low effort from
my side.
> Note that it
> is always possible to create a namespace using unshare(1) with
> arbitrary options and then connect it with pasta, rather than having
> pasta create its own.
>
> That said, I've never seen the point of creating a pidns by default in
> pasta. We're not isolating the filesystem, so I don't see that
> there's much to gain by isolating pids. I'd be happy enough to see
> CLONE_NEWPID removed unconditionally.
The points are described in the message for 0515adceaa8f
("passt, pasta: Namespace-based sandboxing, defer seccomp policy
application"):
- no need to track child processes: when PID 1 exits, they all
terminate, instead of that crazy / buggy hack I implemented before
this change
- for passt, to avoid possible attacks based on the knowledge of a
target process' PID, but then I realised that would also apply to
pasta's spawned process
Isolating the filesystem would make it almost entirely useless for all
the nice debugging / development features it offers (like running any
network utility or test), but there's not much we would gain in a
general case by keeping the original PID namespace (you don't use pasta
to run strace or gdb), so I think it's an easy, albeit small, win from
a security perspective. I'd rather keep it.
--
Stefano
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] pasta: Add --no-pidns to keep spawned command in caller's PID namespace
2026-09-06 13:17 [PATCH] pasta: Add --no-pidns to keep spawned command in caller's PID namespace Christian Korneck
2026-09-08 6:03 ` David Gibson
@ 2026-09-08 9:45 ` Stefano Brivio
1 sibling, 0 replies; 5+ messages in thread
From: Stefano Brivio @ 2026-09-08 9:45 UTC (permalink / raw)
To: Christian Korneck; +Cc: passt-dev
Christian, thanks for the patch. At a glance, it looks pretty good to
me (see also my reply to David).
There's just one thing I wanted to mention for your awareness before
continuing with it, though: in the past couple of months, we had
several reports of bubblewrap being started by pasta (directly or
indirectly), see in particular:
https://archives.passt.top/passt-user/671252c8-88f6-45b7-b719-b82786e84bb7@gnedt.at/
https://bugs.passt.top/show_bug.cgi?id=204
https://archives.passt.top/passt-dev/20260731132601.422518-1-rlawrence@tamu.edu/
(still pending)
so, while these kind of changes obviously appear useful for somebody,
there's still the question of whether you want to consider spawning
bubblewrap separately and make it join a network namespace connected by
pasta (might need changes in bubblewrap, or something like
https://github.com/reubenfirmin/bubblewrap-tui#why-pasta).
--
Stefano
On Sun, 6 Sep 2026 15:17:58 +0200
Christian Korneck <christian@korneck.de> wrote:
> This is to allow running pasta inside a container without unmasking
> /proc for the whole container (Docker's --security-opt
> systempaths=unconfined, Podman's --security-opt unmask=ALL), which is
> undesirable as it exposes /proc/sysrq-trigger and other masked paths.
>
> In spawn mode, pasta clones the command with CLONE_NEWPID and mounts a
> new procfs instance on /proc, so that it matches the new PID namespace.
>
> Mounting procfs in a new user namespace requires a fully visible,
> unobstructed procfs. Container runtimes deliberately obstruct /proc
> (Docker, for example, masks /proc/kcore and friends and mounts
> /proc/sys read-only), so the mount is refused:
>
> Couldn't mount /proc: Operation not permitted
>
> We only warn and continue, leaving the command in a new PID namespace
> while the visible /proc still numbers processes in the outer one.
> Anything resolving its own PID through /proc then fails, for example
> bubblewrap:
>
> bwrap: open /proc/22/ns/ns failed: No such file or directory
>
> Add a --no-pidns option: skip CLONE_NEWPID for the spawned command and
> don't mount /proc, which is then not needed. User, network, mount, UTS
> and IPC namespaces, --config-net and port forwarding are unaffected.
> The option is rejected together with PID or --netns, as it only makes
> sense when we spawn the command ourselves.
>
> Add a test checking that, by default, the command runs in a new PID
> namespace, and that --no-pidns keeps it in the caller's one.
>
> Signed-off-by: Christian Korneck <christian@korneck.de>
> ---
> conf.c | 11 +++++++++++
> passt.1 | 12 ++++++++++++
> passt.h | 2 ++
> pasta.c | 18 +++++++++++-------
> test/pasta_options/no_pidns | 25 +++++++++++++++++++++++++
> test/run | 1 +
> 6 files changed, 62 insertions(+), 7 deletions(-)
> create mode 100644 test/pasta_options/no_pidns
>
> diff --git a/conf.c b/conf.c
> index faf2681..16b2f40 100644
> --- a/conf.c
> +++ b/conf.c
> @@ -742,6 +742,7 @@ pasta_opts:
> " implied if PATH or NAME are given without --userns\n"
> " --no-netns-quit Don't quit if filesystem-bound target\n"
> " network namespace is deleted\n"
> + " --no-pidns Don't spawn command in a new PID namespace\n"
> " --config-net Configure tap interface in namespace\n"
> " --no-copy-routes DEPRECATED:\n"
> " Don't copy all routes to namespace\n"
> @@ -1348,6 +1349,7 @@ void conf(struct ctx *c, int argc, char **argv)
> {"stats", required_argument, NULL, 31 },
> {"conf-path", required_argument, NULL, 'c' },
> {"chroot-fallback", no_argument, NULL, 32 },
> + {"no-pidns", no_argument, NULL, 33 },
> { 0 },
> };
> const char *optstring = "+dqfel:hs:c:F:I:p:P:m:a:n:M:g:i:o:D:S:H:461t:u:T:U:";
> @@ -1589,6 +1591,12 @@ void conf(struct ctx *c, int argc, char **argv)
> case 32:
> c->chroot_fallback = true;
> break;
> + case 33:
> + if (c->mode != MODE_PASTA)
> + die("--no-pidns is for pasta mode only");
> +
> + c->no_pidns = true;
> + break;
> case 'd':
> c->debug = 1;
> c->quiet = 0;
> @@ -1948,6 +1956,9 @@ void conf(struct ctx *c, int argc, char **argv)
> else if (optind != argc)
> die("Extra non-option argument: %s", argv[optind]);
>
> + if (c->no_pidns && *netns)
> + die("--no-pidns is incompatible with PID or --netns");
> +
> conf_open_files(c); /* Before any possible setuid() / setgid() */
>
> isolate_user(c, uid, gid, !netns_only, userns);
> diff --git a/passt.1 b/passt.1
> index 53e072a..0b780fe 100644
> --- a/passt.1
> +++ b/passt.1
> @@ -704,6 +704,18 @@ is bound to the filesystem, and the given path is deleted, or if the target
> network namespace is represented by a procfs entry, and that entry is deleted,
> representing the fact that a process with the given PID terminated.
>
> +.TP
> +.BR \-\-no-pidns
> +Don't create a new PID namespace for the spawned command or shell: keep it in
> +the PID namespace \fBpasta\fR itself runs in, and don't mount a new
> +\fIprocfs\fR instance on \fI/proc\fR for it. This is useful in environments
> +where mounting \fIprocfs\fR is not permitted, such as containers, where the
> +command would otherwise get a \fI/proc\fR view that doesn't match its own PID
> +namespace. Note that, without a PID namespace, processes started by the
> +command are not terminated once the command exits.
> +
> +This option can't be specified with a PID or with \-\-netns.
> +
> .TP
> .BR \-\-config-net
> Configure networking in the namespace: set up addresses and routes as configured
> diff --git a/passt.h b/passt.h
> index 51ccd4f..afd8e9f 100644
> --- a/passt.h
> +++ b/passt.h
> @@ -195,6 +195,7 @@ struct ip6_ctx {
> * @pasta_ifn: Name of namespace interface for pasta
> * @pasta_ifi: Index of namespace interface for pasta
> * @pasta_conf_ns: Configure namespace after creating it
> + * @no_pidns: Don't create a new PID namespace for spawned command
> * @fwd: Forwarding tables
> * @fwd_pending: Pending forward tables
> * @no_tcp: Disable TCP operation
> @@ -278,6 +279,7 @@ struct ctx {
> char pasta_ifn[IF_NAMESIZE];
> unsigned int pasta_ifi;
> int pasta_conf_ns;
> + bool no_pidns;
>
> struct fwd_table *fwd[PIF_NUM_TYPES];
> struct fwd_table *fwd_pending[PIF_NUM_TYPES];
> diff --git a/pasta.c b/pasta.c
> index 5aa56b7..2d916e1 100644
> --- a/pasta.c
> +++ b/pasta.c
> @@ -194,15 +194,17 @@ static int pasta_spawn_cmd(void *arg)
> if (prctl(PR_SET_PDEATHSIG, SIGKILL))
> die_perror("Couldn't set PR_SET_PDEATHSIG");
>
> - /* We run in a detached PID and mount namespace: mount /proc over */
> - if (mount("", "/proc", "proc", 0, NULL))
> + a = (const struct pasta_spawn_cmd_arg *)arg;
> +
> + /* We run in a detached mount namespace, and, unless --no-pidns was
> + * given, in a detached PID namespace: mount /proc over
> + */
> + if (!a->c->no_pidns && mount("", "/proc", "proc", 0, NULL))
> warn_perror("Couldn't mount /proc");
>
> if (write_file("/proc/sys/net/ipv4/ping_group_range", "0 0"))
> warn("Cannot set ping_group_range, ICMP requests might fail");
>
> - a = (const struct pasta_spawn_cmd_arg *)arg;
> -
> conf_hostname_len = strlen(a->c->hostname);
> if (conf_hostname_len > 0) {
> if (sethostname(a->c->hostname, conf_hostname_len))
> @@ -243,6 +245,7 @@ static int pasta_spawn_cmd(void *arg)
> void pasta_start_ns(struct ctx *c, uid_t uid, gid_t gid, bool config_idmaps,
> int argc, char *argv[])
> {
> + int flags = CLONE_NEWIPC | CLONE_NEWNET | CLONE_NEWUTS | CLONE_NEWNS;
> char ns_fn_stack[NS_FN_STACK_SIZE]
> __attribute__ ((aligned(__alignof__(max_align_t))));
> struct pasta_spawn_cmd_arg arg = {
> @@ -293,10 +296,11 @@ void pasta_start_ns(struct ctx *c, uid_t uid, gid_t gid, bool config_idmaps,
> sigaddset(&set, SIGUSR1);
> sigprocmask(SIG_BLOCK, &set, NULL);
>
> + if (!c->no_pidns)
> + flags |= CLONE_NEWPID;
> +
> pasta_child_pid = do_clone(pasta_spawn_cmd, ns_fn_stack,
> - sizeof(ns_fn_stack),
> - CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNET |
> - CLONE_NEWUTS | CLONE_NEWNS | SIGCHLD,
> + sizeof(ns_fn_stack), flags | SIGCHLD,
> (void *)&arg);
>
> if (pasta_child_pid == -1)
> diff --git a/test/pasta_options/no_pidns b/test/pasta_options/no_pidns
> new file mode 100644
> index 0000000..f565b2f
> --- /dev/null
> +++ b/test/pasta_options/no_pidns
> @@ -0,0 +1,25 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# PASST - Plug A Simple Socket Transport
> +# for qemu/UNIX domain socket mode
> +#
> +# PASTA - Pack A Subtle Tap Abstraction
> +# for network namespace/tap device mode
> +#
> +# test/pasta_options/no_pidns - Check --no-pidns handling
> +
> +htools readlink
> +
> +test Command is spawned in a new PID namespace by default
> +set PIDNS __STATEDIR__/pidns
> +set OUT __STATEDIR__/no-pidns.out
> +set ERR __STATEDIR__/no-pidns.err
> +
> +passt readlink /proc/self/ns/pid > __PIDNS__
> +passt ./pasta -q -- readlink /proc/self/ns/pid > __OUT__ 2> __ERR__
> +check [ "$(cat __PIDNS__)" != "$(cat __OUT__)" ]
> +
> +test --no-pidns keeps command in the caller's PID namespace
> +passt ./pasta -q --no-pidns -- readlink /proc/self/ns/pid > __OUT__ 2> __ERR__
> +check [ "$(cat __PIDNS__)" = "$(cat __OUT__)" ]
> +check [ ! -s __ERR__ ]
> diff --git a/test/run b/test/run
> index c4073cc..14ddbbf 100755
> --- a/test/run
> +++ b/test/run
> @@ -86,6 +86,7 @@ run() {
> setup pasta_options
> test pasta_options/log_to_file
> test pasta_options/netns_only
> + test pasta_options/no_pidns
> teardown pasta_options
>
> setup build
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] pasta: Add --no-pidns to keep spawned command in caller's PID namespace
2026-09-08 9:45 ` Stefano Brivio
@ 2026-09-08 11:09 ` David Gibson
0 siblings, 0 replies; 5+ messages in thread
From: David Gibson @ 2026-09-08 11:09 UTC (permalink / raw)
To: Stefano Brivio; +Cc: Christian Korneck, passt-dev
[-- Attachment #1: Type: text/plain, Size: 4853 bytes --]
On Tue, Sep 08, 2026 at 11:45:07AM +0200, Stefano Brivio wrote:
> On Tue, 8 Sep 2026 16:03:07 +1000
> David Gibson <david@gibson.dropbear.id.au> wrote:
>
> > On Sun, Sep 06, 2026 at 03:17:58PM +0200, Christian Korneck wrote:
> > > This is to allow running pasta inside a container without unmasking
> > > /proc for the whole container (Docker's --security-opt
> > > systempaths=unconfined, Podman's --security-opt unmask=ALL), which is
> > > undesirable as it exposes /proc/sysrq-trigger and other masked paths.
> > >
> > > In spawn mode, pasta clones the command with CLONE_NEWPID and mounts a
> > > new procfs instance on /proc, so that it matches the new PID namespace.
> > >
> > > Mounting procfs in a new user namespace requires a fully visible,
> > > unobstructed procfs. Container runtimes deliberately obstruct /proc
> > > (Docker, for example, masks /proc/kcore and friends and mounts
> > > /proc/sys read-only), so the mount is refused:
> > >
> > > Couldn't mount /proc: Operation not permitted
> > >
> > > We only warn and continue, leaving the command in a new PID namespace
> > > while the visible /proc still numbers processes in the outer one.
> > > Anything resolving its own PID through /proc then fails, for example
> > > bubblewrap:
> > >
> > > bwrap: open /proc/22/ns/ns failed: No such file or directory
> > >
> > > Add a --no-pidns option: skip CLONE_NEWPID for the spawned command and
> > > don't mount /proc, which is then not needed. User, network, mount, UTS
> > > and IPC namespaces, --config-net and port forwarding are unaffected.
> > > The option is rejected together with PID or --netns, as it only makes
> > > sense when we spawn the command ourselves.
> > >
> > > Add a test checking that, by default, the command runs in a new PID
> > > namespace, and that --no-pidns keeps it in the caller's one.
> >
> > I can see that this would be useful. On the other hand, I could say
> > that for just about any combinatiom of unshare(1) options, and I don't
> > think we want to reimplement all of unshare(1) in pasta.
>
> Certainly we don't, but we don't get requests like these frequently
> (except for the one to drop namespacing altogether, which is another
> story), so I think it's acceptable.
Fair point.
> The patch looks also pretty good and complete, so really low effort from
> my side.
>
> > Note that it
> > is always possible to create a namespace using unshare(1) with
> > arbitrary options and then connect it with pasta, rather than having
> > pasta create its own.
> >
> > That said, I've never seen the point of creating a pidns by default in
> > pasta. We're not isolating the filesystem, so I don't see that
> > there's much to gain by isolating pids. I'd be happy enough to see
> > CLONE_NEWPID removed unconditionally.
>
> The points are described in the message for 0515adceaa8f
> ("passt, pasta: Namespace-based sandboxing, defer seccomp policy
> application"):
Sorry, to be clear, I absolutely see the point of CLONE_NEWPID for
passt/pasta itself. It's specifically for the spawned shell/process
that I don't see the benefit.
> - no need to track child processes: when PID 1 exits, they all
> terminate, instead of that crazy / buggy hack I implemented before
> this change
I guess. But is cleaning up the spawned process really pasta's job?
It's not, of itself, a container tool.
> - for passt, to avoid possible attacks based on the knowledge of a
> target process' PID, but then I realised that would also apply to
> pasta's spawned process
Right, for passt itself this is certainly worthwhile. If nothing else
it prevents passt sending a signal elswhere even if entirely
compromised. But we're not really trying to contain the spawned
process, other than for network purposes.
> Isolating the filesystem would make it almost entirely useless for all
> the nice debugging / development features it offers (like running any
> network utility or test), but there's not much we would gain in a
> general case by keeping the original PID namespace (you don't use pasta
> to run strace or gdb),
Not to run strace or gdb on something outside, no. But the PID
namespace makes it more hassle to do things to anything within it,
such as strace, gdb, kill or re-entering the namespace with nsenter.
> so I think it's an easy, albeit small, win from
> a security perspective. I'd rather keep it.
For passt proper, yes. Isolating the spawned process doesn't seem
like it should be passt's concern - podman or bwrap would be much
better tools if that's what you want.
--
David Gibson (he or they) | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you, not the other way
| around.
http://www.ozlabs.org/~dgibson
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-08 11:22 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-06 13:17 [PATCH] pasta: Add --no-pidns to keep spawned command in caller's PID namespace Christian Korneck
2026-09-08 6:03 ` David Gibson
2026-09-08 9:45 ` Stefano Brivio
2026-09-08 11:09 ` David Gibson
2026-09-08 9:45 ` Stefano Brivio
Code repositories for project(s) associated with this public inbox
https://passt.top/passt
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).