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

Reply via email to