Debian 14 (Forky) — kernel userns hardening + AppArmor confinement
---
The `EACCES` error on `/bin/gettext.sh` is a kernel-level block, not a file permission issue. Guix's build daemon uses unprivileged user namespaces to sandbox each build. Debian 14 (like Ubuntu 24.04) tightened AppArmor defaults to restrict this. Upgrading the daemon binary does not fix it — the kernel sysctl does.
---
# Check AppArmor userns restriction (Debian 14+) cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns # Check generic userns (older mechanism) sysctl kernel.unprivileged_userns_clone # Check if AppArmor is running sudo aa-status 2>/dev/null || echo "AppArmor not running" # Check for a guix-daemon AppArmor profile sudo aa-status | grep -i guix
---
The most targeted fix — disables only the guix-daemon profile, not AppArmor globally.
sudo apt install apparmor-utils sudo aa-disable /etc/apparmor.d/guix-daemon sudo systemctl restart guix-daemon
`aa-disable` creates a symlink in `/etc/apparmor.d/disable/` which survives reboots. AppArmor skips any profile symlinked there at boot.
# Verify ls -la /etc/apparmor.d/disable/ | grep guix
sudo aa-enforce /etc/apparmor.d/guix-daemon # or manually: sudo rm /etc/apparmor.d/disable/guix-daemon sudo apparmor_parser -r /etc/apparmor.d/guix-daemon
---
Use this if there is no guix-daemon AppArmor profile, or if Fix 1 doesn't resolve it.
# AppArmor userns restriction sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 echo 'kernel.apparmor_restrict_unprivileged_userns=0' | sudo tee /etc/sysctl.d/99-guix-userns.conf # Generic userns (if unprivileged_userns_clone = 0) sudo sysctl -w kernel.unprivileged_userns_clone=1 echo 'kernel.unprivileged_userns_clone=1' | sudo tee -a /etc/sysctl.d/99-guix-userns.conf sudo sysctl --system sudo systemctl restart guix-daemon
---
AppArmor confines the daemon by restricting at the kernel level:
- **Filesystem access** — limits readable/writable paths. Guix needs broad access to `/gnu/store/**`, `/var/guix/**`, `/bin/`, `/usr/bin/`, and temp dirs. A strict profile that doesn't whitelist these causes EACCES. - **User namespaces** — the daemon calls `clone()` with `CLONE_NEWUSER` to sandbox builds. AppArmor can block this syscall entirely — the root cause of the original error. - **Capabilities** — `CAP_SYS_ADMIN` (namespace ops), `CAP_CHOWN`, `CAP_SETUID`/`CAP_SETGID` for setting up build users. - **Network access** — controls whether builds can reach the network.
The daemon tried to execute that path during build environment setup. AppArmor blocked the userns creation (or the path access directly) before the kernel even checked file permissions — hence EACCES on a file that is actually readable.
---
Full unprivileged operation is not yet practical (the daemon still needs a few root-level ops for sandboxing), but privilege can be significantly reduced.
Grant only the capabilities the daemon actually needs, rather than running with full root.
# /etc/systemd/system/guix-daemon.service.d/override.conf [Service] AmbientCapabilities=CAP_SYS_ADMIN CAP_CHOWN CAP_SETUID CAP_SETGID CapabilityBoundingSet=CAP_SYS_ADMIN CAP_CHOWN CAP_SETUID CAP_SETGID NoNewPrivileges=false
sudo systemctl daemon-reload sudo systemctl restart guix-daemon
# Edit /etc/systemd/system/guix-daemon.service ExecStart=/var/guix/profiles/per-user/root/current-guix/bin/guix-daemon \ --build-users-group=guixbuild \ --disable-chroot
Builds are less isolated but the daemon no longer needs `CAP_SYS_ADMIN`.
sudo setcap cap_sys_admin,cap_chown,cap_setuid,cap_setgid+ep $(which guix-daemon)
Then run the daemon as a dedicated non-root user.
---
sudo systemctl status guix-daemon guix build hello 2>&1 | head -20
---
| Approach | Security | Effort | |---|---|---| | `aa-disable` guix-daemon profile | Good — AppArmor still protects everything else | Low | | Global sysctl relax | Weaker — userns unrestricted system-wide | Low | | systemd capability bounding | Best — minimal privilege, AppArmor still active | Medium | | `--disable-chroot` | Weakest build isolation | Low |