✦ For everyone, free.

Practical knowledge for real and everyday life

Home

16.2.3 Runtime Permission Problems

A focused guide to Runtime Permission Problems, connecting core concepts with practical Docker and container operations.

Runtime permission problems caused by mandatory access control systems, SELinux and AppArmor specifically, are a distinct category from ordinary Unix file permission errors, since these systems enforce additional restrictions beyond standard read, write, and execute bits, and a denial from one of them often produces a "permission denied" error that looks identical to a basic ownership issue while actually requiring an entirely different diagnostic approach and fix.

Why these errors look like ordinary permission denials

SELinux and AppArmor both intercept and can deny operations that would otherwise be permitted by standard Unix file permissions, which means a process running as the correct user, with files that have entirely correct ownership and mode bits, can still receive a permission denied error if a mandatory access control policy specifically blocks that particular operation:

docker run -v /srv/app/data:/app/data my-api
Error: EACCES: permission denied, open '/app/data/config.json'

Standard troubleshooting steps, checking file ownership, checking the container's user, can all look entirely correct while this error persists, which is precisely the signal that a mandatory access control system, not standard Unix permissions, may be the actual cause.

Confirming whether SELinux is enforcing

The first diagnostic step on a host using SELinux is confirming whether it is actually in enforcing mode, since a permission problem that disappears when SELinux is temporarily set to permissive mode confirms it as the cause directly:

getenforce
setenforce 0
docker run -v /srv/app/data:/app/data my-api
setenforce 1

Temporarily switching to permissive mode for a quick test (never as a permanent production fix, since this disables a meaningful security control) is the fastest way to confirm or rule out SELinux as the actual cause before investigating further.

SELinux volume mount labeling

For bind-mounted volumes specifically, SELinux requires the mounted content to carry a security context label compatible with what the container process is permitted to access, and Docker provides mount flags specifically to relabel content appropriately at mount time:

docker run -v /srv/app/data:/app/data:z my-api
docker run -v /srv/app/data:/app/data:Z my-api

The lowercase :z flag relabels the mounted content to be shareable across multiple containers; the uppercase :Z flag relabels it privately, accessible only to the specific container the mount belongs to; choosing the appropriate one for whether the bind-mounted directory genuinely needs to be shared across more than one container resolves the large majority of SELinux-related bind mount permission errors directly.

Reading SELinux audit logs for specifics

When SELinux is the cause, its own audit log records exactly what was denied and why, which is considerably more specific and actionable than the generic "permission denied" error the application itself reports:

ausearch -m avc -ts recent
type=AVC msg=audit(...): avc:  denied  { read } for  pid=1234 comm="node" name="config.json" scontext=system_u:system_r:container_t:s0 ...

This audit entry confirms specifically that SELinux denied a read operation, naming the exact process, file, and security contexts involved, which is the concrete evidence needed either to construct an appropriately scoped policy exception or to confirm a mount labeling fix is the correct approach.

AppArmor profile restrictions

AppArmor, used on Ubuntu and certain other distributions as an alternative mandatory access control system, applies a profile to the Docker daemon and, by default, a generally permissive default profile to containers, but a custom or more restrictive profile can deny operations the application needs:

docker run --security-opt apparmor=unconfined my-api
aa-status

Temporarily running with AppArmor confinement removed (again, for diagnostic purposes only, not as a permanent fix) confirms whether AppArmor is responsible; aa-status shows which profiles are currently loaded and enforcing, which clarifies what policy might actually be in effect for the container in question.

Adjusting an AppArmor profile rather than disabling it

Once confirmed as the cause, the appropriate fix is adjusting the specific profile to permit the legitimately needed operation, rather than running unconfined indefinitely, which removes meaningful security protection beyond just the one operation that needed to be permitted:

/app/data/** rw,
apparmor_parser -r /etc/apparmor.d/docker-my-api

Adding a narrowly scoped rule permitting exactly the access pattern the application legitimately needs, then reloading the profile, resolves the issue while preserving the security benefit of confinement for everything else the profile still restricts.

Seccomp profile denials as a related but distinct mechanism

Seccomp, a separate Linux kernel mechanism restricting which system calls a process may make, can also produce permission-like denial behavior, though typically manifesting as an unexpected error or crash related to a specific syscall rather than a file access denial specifically:

docker run --security-opt seccomp=unconfined my-api
docker run --security-opt seccomp=/path/to/custom-profile.json my-api

A custom seccomp profile that is more restrictive than the Docker default can block a syscall a specific application legitimately needs, which is diagnosed similarly to AppArmor and SELinux issues, confirming with the profile temporarily disabled, then constructing a more precisely scoped custom profile rather than running entirely unconfined permanently.

When to actually grant broader access versus narrowing the policy

The right long-term fix depends on what is actually generating the permission denial: if the application's access pattern is legitimate and well-understood, narrowing the policy to explicitly permit exactly that pattern is the correct, security-conscious fix; if the access pattern itself seems unexpected or broader than what the application should need, that is worth investigating as a separate question before simply granting whatever access resolves the immediate error.

Common mistakes

  • Spending time investigating standard Unix file ownership and permission bits when a mandatory access control system is the actual cause, despite both producing an identical-looking "permission denied" error.
  • Running with SELinux or AppArmor disabled or unconfined as a permanent production fix rather than only as a temporary diagnostic step followed by a properly scoped policy adjustment.
  • Not using the appropriate :z or :Z mount flag for SELinux-labeled bind mounts, instead disabling SELinux enforcement entirely to work around a labeling issue that the correct flag would have resolved directly.
  • Not checking the SELinux audit log or AppArmor's own status output for specific, actionable denial detail before attempting a fix based on guesswork.
  • Granting broad, unconfined access to resolve an immediate error without first considering whether the access pattern triggering the denial was actually expected and legitimate in the first place.

Runtime permission problems caused by SELinux, AppArmor, or seccomp require a different diagnostic path than ordinary Unix permission errors, confirming the specific mandatory access control system as the cause through temporary, diagnostic-only disabling, then resolving the issue with a narrowly scoped policy or mount labeling adjustment rather than disabling the underlying security mechanism permanently.

Content in this section