mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
feat(sandbox): report landlock seccomp denials via pmg sandbox violations (#389)
* feat(sandbox): report landlock seccomp denials via pmg sandbox violations The landlock driver's seccomp supervisor already emitted structured deny events over the audit socket, but the driver drained them to io.Discard, so the violation cache was never populated on Linux and violations list / explain always came up empty. Capture the events at the driver, enrich them with access mode and process name, and implement BestEffortViolation mirroring the seatbelt reporter: failure-only collection, seccomp_deny events only, (kind, target) dedupe. The platform-neutral cache/list/explain pipeline picks it up unchanged. Only the seccomp deny-list layer is observable; denials made by the Landlock LSM itself (allow-list boundary, delete/rename, network) fail in-kernel with no userspace signal and are documented as out of scope. Also make the explain renderer driver-neutral: the raw-log label was hardcoded as "Seatbelt log" and an empty correlation ID printed a blank value. * fix: address review findings on landlock violation reporting Report the deny rule that fired, not the requested access: an O_RDWR open denied by a read-only rule now surfaces as a read denial with an effective override suggestion (allow write= prunes only deny_write). The matched rule path is emitted as rule_path and mapped to RuleTarget, bringing the "Matched rule:" line to parity with seatbelt. Dedupe deny events by (kind, path) at capture time so a retry loop on one denied path cannot fill the buffer and evict a later distinct denial; the cap now bounds distinct denials. Stamp deny events with a timestamp (they rendered "ts":0 in the raw log) and default unknown syscalls to generic_deny instead of fs_write. * refactor: single source for the deny dedupe key Capture-time and extract-time dedupe must agree on what identifies a denial; building the key in two places risks them drifting apart. * fix: bound the capture dedupe map by marking keys only on append seen grew for every distinct deny key even after the buffer was full, and keys carry attacker-chosen path bytes — a hostile process looping over crafted unique denied paths could grow the pmg parent's memory for the run's duration, defeating the cap. Marking keys only when the event is appended bounds the map at the cap and keeps the one-time drop warning reachable for distinct denials past it. * docs(sandbox): AppArmor userns fix for the Landlock driver on Ubuntu 23.10+ The shim fails with "install seccomp: ... permission denied" when kernel.apparmor_restrict_unprivileged_userns=1. Document the per-binary AppArmor profile as the recommended fix and the sysctl as the blunt alternative. * docs(sandbox): drop em dashes from the landlock sections * fix(doctor): cover landlock in the AppArmor userns probe The warn detail only named the bwrap failure and the only suggested fix was the system-wide sysctl. Name the landlock shim error too and suggest the per-binary AppArmor profile first, pointing at the new docs section.
This commit is contained in:
+56
-2
@@ -99,6 +99,8 @@ rules. A small set of core variables (`PATH`, `HOME`, `LC_*`, `TZ`, ...) is neve
|
||||
- Linux kernel 5.13+ with Landlock enabled (default, no external dependencies)
|
||||
- Bubblewrap on Linux (fallback for kernels < 5.13, or when `PMG_SANDBOX_DRIVER=bubblewrap` is set)
|
||||
- Seatbelt on MacOS
|
||||
- On Ubuntu 23.10+, an AppArmor profile granting pmg unprivileged user namespaces. See
|
||||
[AppArmor blocks the Landlock driver](#apparmor-blocks-the-landlock-driver-ubuntu-2310)
|
||||
|
||||
<details>
|
||||
<summary>Bubblewrap Installation on Linux</summary>
|
||||
@@ -210,8 +212,17 @@ pmg sandbox profile show npm-restrictive --resolved
|
||||
```
|
||||
|
||||
`pmg sandbox doctor` runs platform-specific checks for the current host. Cached violation reports
|
||||
used by `violations list` and `explain --last` are currently produced by macOS Seatbelt diagnostics;
|
||||
on Linux, Bubblewrap and Landlock denials may only appear as command errors such as `EACCES`.
|
||||
used by `violations list` and `explain --last` are produced by macOS Seatbelt diagnostics and, on
|
||||
Linux, by the Landlock driver's seccomp supervisor.
|
||||
|
||||
Coverage differs by platform. Seatbelt logs every denial, including the default-deny allow-list
|
||||
boundary. The Landlock driver only reports denials made by its seccomp deny-list layer (reads and
|
||||
writes of `deny_*` paths, blocked `deny_exec` binaries): denials made by the Landlock LSM itself
|
||||
(operations outside the allow-list, delete/rename, network rules) fail in-kernel with `EACCES` and
|
||||
produce no report. `deny_write` entries outside writable areas are enforced by Landlock rather than
|
||||
seccomp, so they are likewise not reported. Operational degradation events on the audit socket
|
||||
(`namespace_isolation_unavailable`, `memfd_open_failed`) are not included in violation reports
|
||||
today; they may be added later. Bubblewrap denials only appear as command errors such as `EACCES`.
|
||||
|
||||
### Runtime Allow Overrides
|
||||
|
||||
@@ -614,6 +625,49 @@ bwrap --verbose [arguments...] -- npm install express
|
||||
|
||||
**Note**: Unlike macOS, Bubblewrap does not provide real-time violation logging. Policy violations typically manifest as `EACCES` (Permission denied) errors.
|
||||
|
||||
With the Landlock driver, denials made by the seccomp deny-list layer on a failed run are captured
|
||||
into the violation cache and can be inspected with `pmg sandbox violations list` and
|
||||
`pmg sandbox explain --last` (see Sandbox Debug Commands above for coverage limits).
|
||||
|
||||
### AppArmor blocks the Landlock driver (Ubuntu 23.10+)
|
||||
|
||||
Ubuntu restricts unprivileged user namespaces via AppArmor
|
||||
(`kernel.apparmor_restrict_unprivileged_userns=1`, default since 23.10). The Landlock driver needs
|
||||
one: it re-executes pmg inside a user namespace to install its seccomp filter. With the restriction
|
||||
active, sandboxed commands fail with:
|
||||
|
||||
```
|
||||
Error: shim: install seccomp: SECCOMP_SET_MODE_FILTER without NNP (user-ns CAP_SYS_ADMIN required): permission denied
|
||||
```
|
||||
|
||||
`pmg sandbox doctor` flags this as the "AppArmor user namespaces" check.
|
||||
|
||||
The recommended fix is Ubuntu's own mechanism: an AppArmor profile that grants pmg (and only pmg)
|
||||
the `userns` permission. Create `/etc/apparmor.d/pmg` with the pmg binary path (`command -v pmg`):
|
||||
|
||||
```
|
||||
abi <abi/4.0>,
|
||||
include <tunables/global>
|
||||
|
||||
profile pmg /usr/local/bin/pmg flags=(unconfined) {
|
||||
userns,
|
||||
include if exists <local/pmg>
|
||||
}
|
||||
```
|
||||
|
||||
Load it (persists across reboots; no restart needed):
|
||||
|
||||
```bash
|
||||
sudo apparmor_parser -r /etc/apparmor.d/pmg
|
||||
```
|
||||
|
||||
Alternatively, disable the restriction system-wide. This is simpler but weakens the protection for
|
||||
every binary on the host, so prefer the profile:
|
||||
|
||||
```bash
|
||||
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- <https://github.com/anthropic-experimental/sandbox-runtime>
|
||||
|
||||
Reference in New Issue
Block a user