<feed xmlns='http://www.w3.org/2005/Atom'>
<title>passt/isolation.c, branch 2026_09_25.df90211</title>
<subtitle>Plug A Simple Socket Transport</subtitle>
<link rel='alternate' type='text/html' href='https://passt.top/passt/'/>
<entry>
<title>isolation: Don't create our userns as nobody</title>
<updated>2026-09-08T14:00:23+00:00</updated>
<author>
<name>David Gibson</name>
<email>david@gibson.dropbear.id.au</email>
</author>
<published>2026-08-20T05:30:33+00:00</published>
<link rel='alternate' type='text/html' href='https://passt.top/passt/commit/?id=7bf1595c924271b74e924426765fa0b5524e4e57'/>
<id>7bf1595c924271b74e924426765fa0b5524e4e57</id>
<content type='text'>
In isolate_user(), we set[ug]id() to our final UID/GID, then join or
create our userns.  This makes sense when we're going to use an already
existing userns, that is, with the --userns option, a PID passed to pasta
or with --netns-only (we use our current userns).  However, it causes a
problem when we create our own new userns.

The set[ug]id() is most often a no-op, but when it isn't it's generally a
switch from root to nobody.  If we create our userns after this, we will
create it owned by nobody.  That means that anything which can operate as
nobody can enter our userns with full capabilities, potentially allowing
it to attack a running passt or pasta.  This sort of attach is unlikely
in practice: something else on the system must have a security defect to
allow arbitrary actions as nobody, and even then it will probably be caught
by LSMs.

Still, it's better to avoid this.  So, rearrange the creation of our
userns so that it is owned by the original user (e.g. root).  This requires
us to always create a UID &amp; GID mapping within the userns so that we can
switch to the correct final user after we've entered the namespace.

Since we now always create that user mapping when we create the ns, we no
longer need to do so as we spawn the pasta shell or command.

Signed-off-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
In isolate_user(), we set[ug]id() to our final UID/GID, then join or
create our userns.  This makes sense when we're going to use an already
existing userns, that is, with the --userns option, a PID passed to pasta
or with --netns-only (we use our current userns).  However, it causes a
problem when we create our own new userns.

The set[ug]id() is most often a no-op, but when it isn't it's generally a
switch from root to nobody.  If we create our userns after this, we will
create it owned by nobody.  That means that anything which can operate as
nobody can enter our userns with full capabilities, potentially allowing
it to attack a running passt or pasta.  This sort of attach is unlikely
in practice: something else on the system must have a security defect to
allow arbitrary actions as nobody, and even then it will probably be caught
by LSMs.

Still, it's better to avoid this.  So, rearrange the creation of our
userns so that it is owned by the original user (e.g. root).  This requires
us to always create a UID &amp; GID mapping within the userns so that we can
switch to the correct final user after we've entered the namespace.

Since we now always create that user mapping when we create the ns, we no
longer need to do so as we spawn the pasta shell or command.

Signed-off-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>isolation: Create helper function to enter user namespace</title>
<updated>2026-09-08T14:00:22+00:00</updated>
<author>
<name>David Gibson</name>
<email>david@gibson.dropbear.id.au</email>
</author>
<published>2026-08-20T05:30:32+00:00</published>
<link rel='alternate' type='text/html' href='https://passt.top/passt/commit/?id=a8aedb8cd6283229da72ccb0d062f6e609bdba0c'/>
<id>a8aedb8cd6283229da72ccb0d062f6e609bdba0c</id>
<content type='text'>
Currently, isolate_user() open codes the steps needed to open and join an
existing user namespace.  Upcoming changes are going to want to re-use
these steps, so move this logic into a helper function,

Signed-off-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Currently, isolate_user() open codes the steps needed to open and join an
existing user namespace.  Upcoming changes are going to want to re-use
these steps, so move this logic into a helper function,

Signed-off-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>isolation: Include linux_dep.h for close_range()</title>
<updated>2026-08-10T16:25:34+00:00</updated>
<author>
<name>Huang Jing</name>
<email>rne.kou@icloud.com</email>
</author>
<published>2026-08-07T16:10:32+00:00</published>
<link rel='alternate' type='text/html' href='https://passt.top/passt/commit/?id=defc25b9444c508d21badb6bc9a0b835ddd01126'/>
<id>defc25b9444c508d21badb6bc9a0b835ddd01126</id>
<content type='text'>
Include linux_dep.h for close_range() usage in isolation.c. And now
that the close_open_files() function which makes use of linux_dep.h
has been moved from util.c to isolation.c, we can safely remove the
header inclusion from util.c.

Link: https://github.com/gentoo/gentoo/pull/46644
Fixes: 0a510066e916 ("isolation: Move close_open_files() to isolate_fds()")
Signed-off-by: Jing Huang &lt;rne.kou@icloud.com&gt;
Reviewed-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Include linux_dep.h for close_range() usage in isolation.c. And now
that the close_open_files() function which makes use of linux_dep.h
has been moved from util.c to isolation.c, we can safely remove the
header inclusion from util.c.

Link: https://github.com/gentoo/gentoo/pull/46644
Fixes: 0a510066e916 ("isolation: Move close_open_files() to isolate_fds()")
Signed-off-by: Jing Huang &lt;rne.kou@icloud.com&gt;
Reviewed-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>isolation: Move --fd descriptor to a number of our choosing</title>
<updated>2026-07-18T07:54:31+00:00</updated>
<author>
<name>David Gibson</name>
<email>david@gibson.dropbear.id.au</email>
</author>
<published>2026-07-17T05:46:33+00:00</published>
<link rel='alternate' type='text/html' href='https://passt.top/passt/commit/?id=fd5b0807f5d0706e9247662f26c31b6bc8337f19'/>
<id>fd5b0807f5d0706e9247662f26c31b6bc8337f19</id>
<content type='text'>
Some users of passt pass an fd for the tap interface in, with the --fd
parameter, rather than having passt open it itself.  This requires some
slightly fiddly logic in isolate_fds() so we don't close() it along with
any other file descriptors leaked into us by the parent.

More importantly, this is broken if the passed fd is 0, 1 or 2, since in
that case we will assume it's a standard stream and close it in __daemon().
We explicitly disallow 1 or 2 in conf_tap_fd(), but 0 has been permitted
since aa1cc8922 ("conf: allow --fd 0").  It looks like the use case of the
contributor of that patch didn't involve daemonizing passt.

To fix this more robustly, use dup2() to move to the passed fd to 3.  This
removes the possibility of mixing it up with a standard stream, and as a
bonus makes the close_range() logic much simpler.  With isolate_fds() made
safe for --fd 1 and --fd 2, we can remove the logic excluding those from
conf_fd_tap().

Signed-off-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Some users of passt pass an fd for the tap interface in, with the --fd
parameter, rather than having passt open it itself.  This requires some
slightly fiddly logic in isolate_fds() so we don't close() it along with
any other file descriptors leaked into us by the parent.

More importantly, this is broken if the passed fd is 0, 1 or 2, since in
that case we will assume it's a standard stream and close it in __daemon().
We explicitly disallow 1 or 2 in conf_tap_fd(), but 0 has been permitted
since aa1cc8922 ("conf: allow --fd 0").  It looks like the use case of the
contributor of that patch didn't involve daemonizing passt.

To fix this more robustly, use dup2() to move to the passed fd to 3.  This
removes the possibility of mixing it up with a standard stream, and as a
bonus makes the close_range() logic much simpler.  With isolate_fds() made
safe for --fd 1 and --fd 2, we can remove the logic excluding those from
conf_fd_tap().

Signed-off-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>conf: Make conf_tap_fd() operate more like conf_mode()</title>
<updated>2026-07-18T07:54:29+00:00</updated>
<author>
<name>David Gibson</name>
<email>david@gibson.dropbear.id.au</email>
</author>
<published>2026-07-17T05:46:32+00:00</published>
<link rel='alternate' type='text/html' href='https://passt.top/passt/commit/?id=ab825955836cb04c1e994d17af4c50a243ea67a0'/>
<id>ab825955836cb04c1e994d17af4c50a243ea67a0</id>
<content type='text'>
We have two cases where we need to parse specific options early:
conf_tap_fd() and conf_mode().  conf_tap_fd() has a slightly odd interface,
requiring the caller to use getopt_long() to find the right option, then
pass it in.  Alter it to work like conf_mode() instead, where all the
command line parsing logic is contained within the conf.c function.

This is slightly more lines, but has a clearer division of responsibility.

Signed-off-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
We have two cases where we need to parse specific options early:
conf_tap_fd() and conf_mode().  conf_tap_fd() has a slightly odd interface,
requiring the caller to use getopt_long() to find the right option, then
pass it in.  Alter it to work like conf_mode() instead, where all the
command line parsing logic is contained within the conf.c function.

This is slightly more lines, but has a clearer division of responsibility.

Signed-off-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>isolation, conf: Set c-&gt;fd_tap from early parse of --fd</title>
<updated>2026-07-18T07:54:27+00:00</updated>
<author>
<name>David Gibson</name>
<email>david@gibson.dropbear.id.au</email>
</author>
<published>2026-07-17T05:46:31+00:00</published>
<link rel='alternate' type='text/html' href='https://passt.top/passt/commit/?id=33e0fbc396f494e2fdd8033191b76d2c4aef43ec'/>
<id>33e0fbc396f494e2fdd8033191b76d2c4aef43ec</id>
<content type='text'>
We parse --fd twice: once in isolate_initial() just to avoid clobbering
the passed in fd.  Then we parse it "for real" in conf(), to set c-&gt;fd_tap
and other configuration variables.

Change this, so that we return the value parsed early from
isolate_initial() and set c-&gt;fd_tap from that.  This doesn't accomplish
much immediately, but will make some further cleanups possible.

Signed-off-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
We parse --fd twice: once in isolate_initial() just to avoid clobbering
the passed in fd.  Then we parse it "for real" in conf(), to set c-&gt;fd_tap
and other configuration variables.

Change this, so that we return the value parsed early from
isolate_initial() and set c-&gt;fd_tap from that.  This doesn't accomplish
much immediately, but will make some further cleanups possible.

Signed-off-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>isolation: Move close_open_files() to isolate_fds()</title>
<updated>2026-07-18T07:54:24+00:00</updated>
<author>
<name>David Gibson</name>
<email>david@gibson.dropbear.id.au</email>
</author>
<published>2026-07-17T05:46:30+00:00</published>
<link rel='alternate' type='text/html' href='https://passt.top/passt/commit/?id=0a510066e9169409e9021798224bea5297c1e529'/>
<id>0a510066e9169409e9021798224bea5297c1e529</id>
<content type='text'>
Most functions in util.c are, well, utilities, that are useful in a bunch
of places.  close_open_files(), however, is very specific, it's only called
from isolate_initial(), and performs a very specific step of our self
isolation.  So, it makes more sense as a function in isolate.c - move it
there and rename it to isolate_fds().

In addition, call it directly from main() rather than from
isolate_initial().  That's pretty arbitrary now, but will make some
subsequent changes easier.

Signed-off-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Most functions in util.c are, well, utilities, that are useful in a bunch
of places.  close_open_files(), however, is very specific, it's only called
from isolate_initial(), and performs a very specific step of our self
isolation.  So, it makes more sense as a function in isolate.c - move it
there and rename it to isolate_fds().

In addition, call it directly from main() rather than from
isolate_initial().  That's pretty arbitrary now, but will make some
subsequent changes easier.

Signed-off-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>isolation: Add --chroot-fallback option</title>
<updated>2026-07-08T19:41:33+00:00</updated>
<author>
<name>Mateusz Andrzejewski</name>
<email>mandrzejewski06@gmail.com</email>
</author>
<published>2026-07-02T07:13:31+00:00</published>
<link rel='alternate' type='text/html' href='https://passt.top/passt/commit/?id=6ef3d1c86ffc690a17a9a4445df4a741446bcd44'/>
<id>6ef3d1c86ffc690a17a9a4445df4a741446bcd44</id>
<content type='text'>
For integrations, which use rootfs on tmpfs or initramfs, it is not
allowed to use pivot_root(). It results with invalid argument (EINVAL)
error. Introduce --chroot-fallback option as a workaround with
MS_MOVE + chroot().

Due to weaker isolation of chroot() method (we don't unmount old root),
user has tu explicitly enable fallback with the new option. First,
always try to sandbox with pivot_root(). In both cases the new root is
placed into an empty tmpfs.

For the solution to work we keep CAP_SYS_CHROOT capability, which is
dropped at the end of the isolate_prefork() function.

Link: https://bugs.passt.top/show_bug.cgi?id=104
Signed-off-by: Mateusz Andrzejewski &lt;mateusz.andrzejewski@mikronika.com.pl&gt;
[sbrivio: Minor tweaks to comments, usage, man page]
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
For integrations, which use rootfs on tmpfs or initramfs, it is not
allowed to use pivot_root(). It results with invalid argument (EINVAL)
error. Introduce --chroot-fallback option as a workaround with
MS_MOVE + chroot().

Due to weaker isolation of chroot() method (we don't unmount old root),
user has tu explicitly enable fallback with the new option. First,
always try to sandbox with pivot_root(). In both cases the new root is
placed into an empty tmpfs.

For the solution to work we keep CAP_SYS_CHROOT capability, which is
dropped at the end of the isolate_prefork() function.

Link: https://bugs.passt.top/show_bug.cgi?id=104
Signed-off-by: Mateusz Andrzejewski &lt;mateusz.andrzejewski@mikronika.com.pl&gt;
[sbrivio: Minor tweaks to comments, usage, man page]
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>treewide: Spell ASSERT() as assert()</title>
<updated>2026-03-20T20:05:29+00:00</updated>
<author>
<name>David Gibson</name>
<email>david@gibson.dropbear.id.au</email>
</author>
<published>2026-03-19T06:11:43+00:00</published>
<link rel='alternate' type='text/html' href='https://passt.top/passt/commit/?id=bc872d91765dfd6ff34b0e9a34bce410fac1cef3'/>
<id>bc872d91765dfd6ff34b0e9a34bce410fac1cef3</id>
<content type='text'>
The standard library assert(3), at least with glibc, hits our seccomp
filter and dies with SIGSYS before it's able to print a message, making it
near useless.  Therefore, since 7a8ed9459dfe ("Make assertions actually
useful") we've instead used our own implementation, named ASSERT().

This makes our code look slightly odd though - ASSERT() has the same
overall effect as assert(), it's just a different implementation.  More
importantly this makes it awkward to share code between passt/pasta proper
and things that compile in a more typical environment.  We're going to want
that for our upcoming dynamic configuration tool.

Address this by overriding the standard library's assert() implementation
with our own, instead of giving ours its own name.

The standard assert() is supposed to be omitted if NDEBUG is defined,
which ours doesn't do.  Implement that as well, so ours doesn't
unexpectedly differ.  For the -DNDEBUG case we do this by *not* overriding
assert(), since it will be a no-op anyway.  This requires a few places to
add a #include &lt;assert.h&gt; to let us compile (albeit with warnings) when
-DNDEBUG.

Signed-off-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
[sbrivio: Fix some conflicts and missing conversions as a result of
 applying "vu_common: Move iovec management into vu_collect()" first]
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The standard library assert(3), at least with glibc, hits our seccomp
filter and dies with SIGSYS before it's able to print a message, making it
near useless.  Therefore, since 7a8ed9459dfe ("Make assertions actually
useful") we've instead used our own implementation, named ASSERT().

This makes our code look slightly odd though - ASSERT() has the same
overall effect as assert(), it's just a different implementation.  More
importantly this makes it awkward to share code between passt/pasta proper
and things that compile in a more typical environment.  We're going to want
that for our upcoming dynamic configuration tool.

Address this by overriding the standard library's assert() implementation
with our own, instead of giving ours its own name.

The standard assert() is supposed to be omitted if NDEBUG is defined,
which ours doesn't do.  Implement that as well, so ours doesn't
unexpectedly differ.  For the -DNDEBUG case we do this by *not* overriding
assert(), since it will be a no-op anyway.  This requires a few places to
add a #include &lt;assert.h&gt; to let us compile (albeit with warnings) when
-DNDEBUG.

Signed-off-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
[sbrivio: Fix some conflicts and missing conversions as a result of
 applying "vu_common: Move iovec management into vu_collect()" first]
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>isolation: keep CAP_DAC_OVERRIDE initially</title>
<updated>2025-10-09T08:11:27+00:00</updated>
<author>
<name>Cole Robinson</name>
<email>crobinso@redhat.com</email>
</author>
<published>2025-10-08T15:01:33+00:00</published>
<link rel='alternate' type='text/html' href='https://passt.top/passt/commit/?id=5da0316f27c9b36b7ee4ba181d38a8dc358b2328'/>
<id>5da0316f27c9b36b7ee4ba181d38a8dc358b2328</id>
<content type='text'>
Reproducer that I'd expect to work:

  $ cd $HOME
  $ sudo passt --runas $UID --socket foo.sock
  Failed to bind UNIX domain socket: Permission denied

A more practical example is for libguestfs apps when run as user=root:

+ libguestfs connects to libvirt qemu:///system
+ libvirt qemu:///system defaults to user=qemu
  + libvirt chowns /run/libvirt/qemu/passt dir to user=qemu
+ libguestfs instead requests the VM run as user=root
  + patches in progress but we are blocked by this issue
+ passt is launched as root, but because CAP_DAC_OVERRIDE has been
  dropped, passt fails to create socket in qemu owned
  /run/libvirt/qemu/passt

Fix it by not dropping CAP_DAC_OVERRIDE in isolate_initial.

This might look sketchy, but isolate_initial already keeps
CAP_SYS_ADMIN and CAP_NET_ADMIN, so we are probably no worse off.

Link: https://github.com/libguestfs/libguestfs/pull/218
Reviewed-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
Signed-off-by: Cole Robinson &lt;crobinso@redhat.com&gt;
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Reproducer that I'd expect to work:

  $ cd $HOME
  $ sudo passt --runas $UID --socket foo.sock
  Failed to bind UNIX domain socket: Permission denied

A more practical example is for libguestfs apps when run as user=root:

+ libguestfs connects to libvirt qemu:///system
+ libvirt qemu:///system defaults to user=qemu
  + libvirt chowns /run/libvirt/qemu/passt dir to user=qemu
+ libguestfs instead requests the VM run as user=root
  + patches in progress but we are blocked by this issue
+ passt is launched as root, but because CAP_DAC_OVERRIDE has been
  dropped, passt fails to create socket in qemu owned
  /run/libvirt/qemu/passt

Fix it by not dropping CAP_DAC_OVERRIDE in isolate_initial.

This might look sketchy, but isolate_initial already keeps
CAP_SYS_ADMIN and CAP_NET_ADMIN, so we are probably no worse off.

Link: https://github.com/libguestfs/libguestfs/pull/218
Reviewed-by: David Gibson &lt;david@gibson.dropbear.id.au&gt;
Signed-off-by: Cole Robinson &lt;crobinso@redhat.com&gt;
Signed-off-by: Stefano Brivio &lt;sbrivio@redhat.com&gt;
</pre>
</div>
</content>
</entry>
</feed>
