Escaping the AWS SSM AppArmor profile with systemd-run
On Ubuntu, SSM Session Manager shells inherit the snap-amazon-ssm-agent AppArmor profile. Even root hits silent EACCES on writes. Escape via systemd-run.
On Ubuntu via SSM Session Manager, your shell (and every child it spawns) runs under the snap.amazon-ssm-agent.amazon-ssm-agent AppArmor profile. uid 0 with CAP_DAC_OVERRIDE doesn't get out of it. Writes to paths like /var/log/postgresql/* return EACCES even with a clean lsattr, a writable fs, and the right caps.
# Confirm it's AppArmor
$ cat /proc/self/attr/current
snap.amazon-ssm-agent.amazon-ssm-agent (enforce)
dmesg will probably stay quiet. Snap profiles use audit deny for "expected" paths, which suppresses the kernel audit line. When entries do show up they can lag a couple of minutes.
# Escape via systemd-run
systemd runs as PID 1, outside any AppArmor profile. Anything it starts is unconfined. systemd-run --pipe --wait delegates a command to PID 1 and pipes the result back:
sudo systemd-run --pipe --wait <cmd> [args...]
sudo systemd-run --pipe --wait --uid=postgres bash -c 'truncate -s 0 /var/log/postgresql/postgresql-16-main.log'
sudo systemd-run --pipe --wait tee /etc/foo/bar.conf <<'EOF'
some content
EOF
--pipeforwards stdin/stdout/stderr.--waitis synchronous and propagates the exit code.--uid=<user>drops privilege. PID 1 can become any uid.
# Better alternatives
- SSH with a real keypair. SSH sessions don't inherit the snap profile. Worth the setup if you touch the box more than occasionally.
# Why bother
SSM is the right tool for ad-hoc ops without exposing SSH, or when you've lost the original key. It's also audited, which is nice. Knowing about the AppArmor inheritance saves you the hour of wondering why root can't write to a directory it owns (like I did).
# Related
- Living without docker: podman as a daily driver
- Why your MCP server cannot see env vars from .bashrc