Public bug reported:
Ubuntu 24.04.4 LTS · apparmor 4.0.1really4.0.1-0ubuntu0.24.04.6 · kernel
6.8.0-138-generic
## Summary
Ubuntu ships 89 `flags=(unconfined)` "shim" profiles under `/etc/apparmor.d/` —
the ones whose
own comment reads "This profile allows everything and only exists to give the
application a name
instead of having the label unconfined". None of the 89 declares a network rule.
When such an application is launched from a context that creates an
unprivileged user namespace,
AppArmor stacks `unprivileged_userns` onto the shim. The resulting label is
`(mixed)`, which
re-enables `net` mediation — and the shim, having no network rule, denies every
`AF_INET`
socket. Both `inet stream` and `inet dgram` are denied, so the application also
loses UDP DNS.
The application starts, looks completely healthy, and can reach nothing. It
does not present
as "a port failed to open." It presents as a process that is up and
inexplicably offline.
The profile that exists only to give an application a *name* is what removes
networking the
unnamed process had.
## This is not the userns bug you have already seen
Nearly every existing report in this area is the `userns_create` / "No usable
sandbox" class: the
namespace is denied, the application fails to start, and the fix is a `userns,`
rule.
This is the inverse. The namespace is granted, the `userns,` rule is present
and working, the
application starts normally — and has no network. Anyone triaging by symptom
will close it as
a duplicate of the sandbox class. It is not one.
## Reproducer
`google-chrome-stable` installed. The two commands differ by one directive; the
argv is
byte-identical.
```
# binds
systemd-run --user --wait --collect --pipe --quiet \
/opt/google/chrome/chrome --headless=new --no-sandbox \
--enable-logging=stderr --remote-debugging-port=9333 about:blank
# no network — one directive added
systemd-run --user -p PrivateTmp=yes --wait --collect --pipe --quiet \
/opt/google/chrome/chrome --headless=new --no-sandbox \
--enable-logging=stderr --remote-debugging-port=9333 about:blank
# ERROR:net/socket/socket_posix.cc:119] CreatePlatformSocket() failed:
Permission denied (13)
# ERROR:content/browser/devtools/devtools_http_handler.cc:311] Cannot start
http server for devtools.
```
The diagnosis is one world-readable read, and it is the whole thing:
```
$ systemd-run --user --wait --collect --pipe --quiet cat /proc/self/attr/current
unconfined
$ systemd-run --user -p PrivateTmp=yes --wait --collect --pipe --quiet cat
/proc/self/attr/current
unprivileged_userns (enforce)
```
It is not about browsers. A copied `python3` under a profile in the same shim
shape, plus
`unshare -U`, reproduces it with no browser involved:
```
profile without userns, no namespace ......... socket ALLOWED
namespace without profile .................... socket ALLOWED
both — the stack ............................. socket DENIED (EACCES)
stack + bare `network,` added to the profile .. socket ALLOWED
flags=(default_allow) instead ................ socket DENIED
explicit `allow all,` rule instead of the flag socket ALLOWED
```
The last two rows are `apparmor.d(5)`'s own sentence, measured: the
`default_allow` flag "does
not provide all the same option that the `allow all,` rule provides." The
AppArmor wiki states
the equivalent for userns — "a profile without a user namespace rule will
result in a DENIAL
despite being tagged unconfined." Substitute `net` for `userns` and that is
this bug.
Single-variable counterfactual. With everything else held fixed, toggling
`kernel.apparmor_restrict_unprivileged_userns` 1 → 0 → 0 → 1 gives no-bind →
binds → binds →
no-bind. Adding a bare `network,` to the shim also fixes it with the sysctl
left at 1, which
localises the cause to the shim's rule content rather than to the restriction
itself.
## Trigger
Any `systemd --user` unit carrying a directive that makes systemd set up a
mount namespace —
anything implying `PrivateMounts=yes`. A `--system` unit with `User=` does not
reproduce it at
all, since PID 1 is privileged and creates no unprivileged namespace.
The condition is the *property*, not a list of directive names. That
distinction is practical:
`ReadWritePaths=` is not a "hardening" directive in most people's mental model,
and an unnoticed
`ReadWritePaths=` is what made this take a month to isolate on our side.
Measured
directive-by-directive by reading `/proc/self/attr/current` inside a transient
`--user` unit
(systemd 255.4-1ubuntu8.17):
```
(none) ...................... unconfined no stack
NoNewPrivileges=yes ......... unconfined no stack
ProtectProc=invisible ....... unconfined no stack
PrivateTmp=yes .............. unprivileged_userns (enforce) STACKS
ProtectSystem=strict ........ unprivileged_userns (enforce) STACKS
ProtectHome=read-only ....... unprivileged_userns (enforce) STACKS
ReadWritePaths=/tmp ......... unprivileged_userns (enforce) STACKS
ReadOnlyPaths=/etc .......... unprivileged_userns (enforce) STACKS
ProtectKernelTunables=yes ... unprivileged_userns (enforce) STACKS
ProtectControlGroups=yes .... unprivileged_userns (enforce) STACKS
PrivateMounts=yes ........... unprivileged_userns (enforce) STACKS
```
(`PrivateDevices=yes` is omitted because it cannot be measured here — under the
unprivileged
`--user` manager the unit fails to start outright, rc 218 `EXIT_NAMESPACE`.)
An LXC/Proxmox unprivileged container is a second, unrelated namespace source
that reaches the
same stack — see the 2024-11 sighting below.
## Blast radius — measured, not estimated
Counted on a stock 24.04.4 install: 89 profiles under `/etc/apparmor.d/` declare
`flags=(unconfined)`, and 89 of 89 declare no network rule. None of them
includes any
`abstractions/` file, so none acquires networking indirectly either. Every one
is the same
four-line template.
It is not only browsers. The affected set includes:
* a VPN client — `surfshark`
* container/runtime tooling — `podman`, `runc`, `crun`, `buildah`, `flatpak`,
the `lxc-*`
family, `mmdebstrap`, `sbuild` and its ~15 `sbuild-*` helpers
* networking helpers — `slirp4netns`, `rootlesskit`, `vpnns`, `userbindmount`
* a server — `uwsgi-core`
* communications and mail — `signal-desktop`, `slack`, `element-desktop`,
`Discord`,
`thunderbird`, `evolution`, `geary`
* browsers — `chrome`, `firefox`, `chromium`-adjacent, `msedge`, `brave`,
`opera`,
`vivaldi-bin`, `qutebrowser`, `epiphany`, `QtWebEngineProcess`
* and `1password`, `steam`, `code`, `github-desktop`, `obsidian`,
`MongoDB_Compass`
## Not fixed in the 5.x line — profile side verified today
The shim is unchanged upstream. `profiles/apparmor.d/chrome` on AppArmor master
(`abi <abi/5.0>`) reads:
```
profile chrome /opt/google/chrome/chrome flags=(unconfined) {
userns,
@{exec_path} mr,
include if exists <local/chrome>
}
```
The only differences from the 4.0.1 profile shipped in 24.04 are the abi bump
and
`@{exec_path} mr,`. Still no network rule. And
`profiles/apparmor.d/unprivileged_userns` on
master still carries both `allow network,` and `allow pix /** ->
&unprivileged_userns,` — so the
stacker still attaches to everything, and the stacked-onto profile still grants
nothing for
`class=net`.
Both profile-side preconditions are therefore intact in the 5.x line. What I
have not been
able to confirm is whether the 5.x *kernel* still re-enables `net` mediation on
a `(mixed)`
label — that needs a 5.x kernel, and there is no AppArmor 5.x packaged for noble
(`apt-cache madison apparmor` on 24.04 offers only 4.0.1-…24.04.7 and
4.0.0-beta3). If the
kernel behaviour did change in 5.x, then this is an SRU request for noble
rather than a live
upstream defect — and noble already has a vehicle in bug #2064672.
## Found three times, filed against this package zero times
* 2024-05-02 — LWN 972109, `gutschke`: https://lwn.net/Articles/972109/
Trigger: on 24.04, apps "unconfined, but have a profile defined" — "the
attempt to open any
type of socket() ends with EPERM".
Outcome: John Johansen answered the adjacent no-profile Proxmox case and said
"file a bug
against lxc or apparmor on launchpad". Never filed.
* 2024-11-25 — Chromium 380912404: https://issues.chromium.org/issues/380912404
Trigger: unprivileged LXC container on Proxmox.
Outcome: the reporter self-diagnosed it to AppArmor in one day, wrote "This
bug report can be
closed", and never carried it downstream. Google closed it Won't Fix
(Intended Behavior)
2025-06-13.
* 2026-08 → 09 — this report
Trigger: `systemd --user` unit with a mount-namespace directive.
Outcome: filed here.
The 2024-11 report already names the denier, from a completely different
namespace source:
```
apparmor="DENIED" operation="create" class="net"
namespace="root//lxc-100_<-var-lib-lxc>"
profile="chrome" family="inet6" sock_type="dgram" requested="create"
denied="create"
apparmor="DENIED" operation="create" class="net"
namespace="root//lxc-100_<-var-lib-lxc>"
profile="chrome" family="inet" sock_type="stream" protocol=6
```
`profile="chrome"` — the shim itself is the denier, not the stack — and both
`inet stream`
and `inet6 dgram`, which is why UDP DNS dies too. That reporter pasted the same
349-byte
`/etc/apparmor.d/chrome`.
Chromium's decline is correct and should not be re-litigated. Chrome reports
the `EACCES` and
degrades properly. The consequence of everyone stopping there is that a defect
found
independently three times, across two unrelated namespace sources, has never
once reached the
project that ships the profile.
Related but not a duplicate: bug #2077413 (filed 2024-08-20, no activity since
that day)
raises the same underlying objection — that `flags=(unconfined)` was presented
as a drop-in
replacement for a legacy `unconfined` label — for the signal class. The
sub-mechanism differs:
that one is about whether `peer=unconfined` matches such a profile's name; this
one is the flag's
blanket allow evaporating outright once the label is `(mixed)`, after which the
profile's own
empty ruleset decides.
## Possible fixes, ranked — though you are far better placed to judge
1. Add `network,` (or `allow all,`) to the `flags=(unconfined)` shims. Smallest
change,
touches no policy semantics, and it is the one that is squarely this
package's call. It also
restores exactly the property the shim template's own comment claims it has.
2. Make `flags=(unconfined)` behave as legacy `unconfined` under stacking. The
general fix,
and the larger call. Filed separately upstream, since it is a semantics
question rather than a
packaging one.
3. Document it. The predicate is stated abstractly in `apparmor.d(5)`, but
nothing connects it
to "your application will have no network when run from a hardened user
unit", and the
diagnosis is a single read of `/proc/self/attr/current` that nobody knows to
make.
## Workaround that works today
For anyone who lands here from a search:
```
echo 'allow all,' | sudo tee /etc/apparmor.d/local/chrome
sudo apparmor_parser -r /etc/apparmor.d/chrome
```
That is the distribution's own sanctioned, upgrade-safe extension point, and it
costs no security
worth having — the hardening that matters, `audit deny capability` and `audit
deny
change_profile`, lives in the `unprivileged_userns` half and is untouched.
Please do not disable `kernel.apparmor_restrict_unprivileged_userns`. That is a
host-wide
control and a far larger hammer than this problem.
## Limits I should state plainly
Verified on Ubuntu 24.04.4 LTS, kernel 6.8.0-138-generic, apparmor
4.0.1really4.0.1-0ubuntu0.24.04.6, systemd 255.4-1ubuntu8.17, Chrome
148.0.7778.178; reproduced
5/5 on demand. The host is one point release behind the current noble candidate
(…24.04.7); I have not re-tested on `.7`, and as noted above there is no 5.x
for noble to test on
at all. The upstream profile check above is a source read, not a runtime test.
The three conditions are established as jointly sufficient and not proven
necessary. No
counterexample survives: I had two earlier captures that appeared to deny with
no namespace
directive at all, and they turned out to be mislabelled on my side — both
carried an unrecorded
`ReadWritePaths=`, which implies `PrivateMounts=yes`, so both satisfied the
namespace condition
after all. A `--user` unit with genuinely no directives reads `unconfined` and
binds. But losing a
counterexample is not proving necessity, and I have not proved it — so please
still don't read the
absence of one condition as safety.
Firefox specifically: Ubuntu's default snap Firefox is not affected, and that
is measured
rather than assumed. `/etc/apparmor.d/firefox` does carry the identical shape,
but it attaches to
`/usr/lib/firefox/…` and `/opt/firefox/…`, which the snap's real binary
(`/snap/firefox/current/usr/lib/firefox/firefox-bin`) does not match; the snap
runs under
`snap.firefox.firefox`, which grants network explicitly. Under a stacked unit,
in one process, the
label read `unprivileged_userns` for both while Chrome was denied and Firefox
fetched a page
normally. A tarball or PPA Firefox installed at the profile's attach paths
should still be
affected by the identical mechanism — that half remains inference from reading
the profile, since
I have no such install to test against.
** Affects: apparmor (Ubuntu)
Importance: Undecided
Status: New
--
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2168612
Title:
flags=(unconfined) shim profiles lose ALL network access when stacked
with unprivileged_userns — 89 shipped profiles affected, app starts
and is silently offline
To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/2168612/+subscriptions
--
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs