Skip to content

fix(rootless): Give each user a device grant of their own - #6

Merged
ananos merged 2 commits into
mainfrom
fix/rootless-per-user-device-grant
Sep 25, 2026
Merged

ananos merged 2 commits into
mainfrom
fix/rootless-per-user-device-grant

Conversation

@ananos

@ananos ananos commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

Summary

brig-rootless-setup.sh wrote every user's /dev/kvm and /dev/vhost-vsock
grant to one file, /etc/udev/rules.d/99-brig-kvm.rules. It also decided
whether a grant was needed by comparing that file with the rule it wanted.

On a host where one user already runs rootless brig, a second user's setup
replaced the first user's rule with its own. The first user kept the ACL until
the next udev event or reboot, and then lost both devices. A re-run by any user
not named in that file asked for sudo again, even with the ACL in place from a
rule of their own. So a user without sudo could not finish an install an admin
had prepared, and docs/rootless.md promised that "any number of users run it".

This patch gives each user a rule file of their own, and reads the grant back
from the devices instead of from one file's bytes.

Changes

  • The default grant goes to /etc/udev/rules.d/99-brig-kvm-<user>.rules.
    Whether one is needed is read from the devices: an ACL entry for the user, or
    mode 0666. A grant an admin wrote by hand, under any file name, counts.
  • The modules-load check reads the host the same way: any file under
    /etc/modules-load.d that lists vhost_vsock will do.
  • --open-devices is a mode for the whole host, so it writes
    99-brig-open-devices.rules. That name sorts after every per-user file, so
    its MODE="0666" wins over their MODE="0660" while it exists. A plain run
    leaves it in place, since another user may depend on it.
  • Migration: a host set up by an older bundle keeps its 99-brig-kvm.rules. It
    still grants the user it names, and the setup never writes or removes it.
    Every other user gets a file of their own.
  • docs/rootless.md covers the per-user files, the old shared file, and how
    root removes a grant or closes an open host.
  • tests/rootless-setup.sh, run by a new Script tests step in CI. fix(bundle): Run brig-ctl's ctr inside the rootless namespace #8 and fix(install): Name the bundle in the install summary #9
    add the same step, word for word, so the three merge cleanly in any order.

One behavior changes. Going back from --open-devices used to be a plain
re-run of the setup, which rewrote the one file. With a file per user, a
plain run cannot tell whose choice the open rule was, so closing the devices is
now a root action, and the docs show it.

Testing

tests/rootless-setup.sh generates the setup with the build's own
write_brig_rootless_setup and runs it against a stub sudo, getfacl and
stat. The stubs answer from the rule files the setup "writes" into a scratch
directory, so nothing outside it changes.

$ sh tests/rootless-setup.sh
ok - a first run writes 99-brig-kvm-ananos.rules
ok - a user the devices already admit is not asked for sudo
ok - --open-devices writes 99-brig-open-devices.rules
ok - a plain run leaves the open rule in place

The same test against main's build-bundle.sh:

$ sh tests/rootless-setup.sh
tee /etc/udev/rules.d/99-brig-kvm.rules
udevadm control --reload-rules
udevadm trigger --subsystem-match=misc --sysname-match=kvm
udevadm trigger --subsystem-match=misc --sysname-match=vhost-vsock
udevadm settle --timeout=10
FAIL: the grant did not go to 99-brig-kvm-ananos.rules

sh -n (dash) and shellcheck -s sh pass over install.sh,
build-bundle.sh, the three generated scripts and the test.

Live, on an Ubuntu 24.04 host

The host had two users with rootless brig: ananos through the old shared
99-brig-kvm.rules, and brigtest through a file of their own. A third user,
brigtest2, was created for the test and removed afterwards. The setup was
generated from this branch with the build's own function. main's generator
reproduces the v0.1.0-rc8 release's brig-rootless-setup.sh byte for byte, so
the comparison below is like for like.

The previous setup, as brigtest2, with a sudo shim that logs and refuses:

  devices    granting brigtest2 access to kvm vhost-vsock (needs sudo)
sudo-shim: refused: tee /etc/udev/rules.d/99-brig-kvm.rules

This branch's setup, as brigtest2, with a temporary NOPASSWD sudo:

  devices    granting brigtest2 access to kvm vhost-vsock (needs sudo)
  state      active
90af387b...  /etc/udev/rules.d/99-brig-kvm-brigtest2.rules
982b3773...  /etc/udev/rules.d/99-brig-kvm-brigtest.rules   (unchanged)
998f808f...  /etc/udev/rules.d/99-brig-kvm.rules            (unchanged)
# file: /dev/kvm
user:ananos:rw-
user:brigtest:rw-
user:brigtest2:rw-

With the sudo entry removed, a re-run of brig-ctl rootless asked for nothing:
the shim log stayed empty. The previous setup, on the same granted host, still
asked for tee /etc/udev/rules.d/99-brig-kvm.rules.

On that grant, brig run -d ubuntu ~/proj booted a 6.18 guest with qemu
running as brigtest2, and read the project file from /work/proj. After the
test, brigtest2, its rule, its ACL entry, its AppArmor profile and its subuid
lines were removed. Both other rule files kept the hashes above, and both
users kept their ACLs.

🤖 Generated with Claude Code

brig-rootless-setup.sh wrote every user's /dev/kvm and /dev/vhost-vsock
grant to one file, /etc/udev/rules.d/99-brig-kvm.rules. It also decided
whether a grant was needed by comparing that file with the rule it wanted.

On a host where one user already ran brig rootless, a second user's setup
replaced the first user's rule with its own. The first user kept the ACL
until the next udev event or reboot, and then lost both devices. A re-run
by any user not named in that file asked for sudo again, even with the ACL
in place from a rule of their own. So a user without sudo could not finish
an install an admin had prepared.

The grant now goes to 99-brig-kvm-<user>.rules. Whether one is needed is
read from the devices: an ACL entry for the user, or mode 0666. The
modules-load check reads the host the same way: any file under
/etc/modules-load.d that lists vhost_vsock will do.

--open-devices is a mode for the whole host, so it writes
99-brig-open-devices.rules. That name sorts after every per-user file, so
its mode wins while it exists. A plain run leaves it in place. Closing the
devices again is a root action, and docs/rootless.md shows it.

A host set up by an older bundle keeps its 99-brig-kvm.rules. It still
grants the user it names, and the setup never writes or removes it.

tests/rootless-setup.sh generates the setup with the build's own function
and runs it against a stub sudo, getfacl and stat. It checks that a first
run writes the per-user file and never the shared one, that a user the
devices already admit is not asked for sudo, and that --open-devices and a
later plain run behave as above. Against the previous build-bundle.sh it
fails on the first check, with the setup asking for
`tee /etc/udev/rules.d/99-brig-kvm.rules`. CI runs it as a new step.

Checked live on an Ubuntu 24.04 host where two users already had
rootless brig, one of them through the old shared file. A third user's
setup, given sudo, wrote 99-brig-kvm-<user>.rules. The other two files
stayed byte-identical, and all three users held their ACLs. With sudo
taken away again, a re-run asked for none. The previous setup, run as
the same user, asked for `tee /etc/udev/rules.d/99-brig-kvm.rules` both
before and after the grant.

Signed-off-by: Anastassios Nanos <ananos@nofire.ai>
…y close

A user whose setup ran while --open-devices was in force gets no grant of
their own: the setup reads the grant from the devices, and the open devices
already let that user in. Removing the open rule then leaves them without
access. Say that they run brig-ctl rootless again, which asks sudo for a
grant in 99-brig-kvm-<user>.rules.

Signed-off-by: Anastassios Nanos <ananos@nofire.ai>
@ananos
ananos marked this pull request as ready for review September 25, 2026 22:40
@ananos
ananos merged commit 1d4aa71 into main Sep 25, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant