[apparmor] [PATCH AUTOSEL 6.16-6.6] apparmor: shift ouid when mediating hard links in userns
Sasha Levin
sashal at kernel.org
Fri Aug 8 15:30:41 UTC 2025
From: Gabriel Totev <gabriel.totev at zetier.com>
[ Upstream commit c5bf96d20fd787e4909b755de4705d52f3458836 ]
When using AppArmor profiles inside an unprivileged container,
the link operation observes an unshifted ouid.
(tested with LXD and Incus)
For example, root inside container and uid 1000000 outside, with
`owner /root/link l,` profile entry for ln:
/root$ touch chain && ln chain link
==> dmesg
apparmor="DENIED" operation="link" class="file"
namespace="root//lxd-feet_<var-snap-lxd-common-lxd>" profile="linkit"
name="/root/link" pid=1655 comm="ln" requested_mask="l" denied_mask="l"
fsuid=1000000 ouid=0 [<== should be 1000000] target="/root/chain"
Fix by mapping inode uid of old_dentry in aa_path_link() rather than
using it directly, similarly to how it's mapped in __file_path_perm()
later in the file.
Signed-off-by: Gabriel Totev <gabriel.totev at zetier.com>
Signed-off-by: John Johansen <john.johansen at canonical.com>
Signed-off-by: Sasha Levin <sashal at kernel.org>
---
LLM Generated explanations, may be completely bogus:
**Backport Status: YES**
This commit should be backported to stable kernel trees for the
following reasons:
1. **Clear Bug Fix**: This fixes a real bug where AppArmor incorrectly
reports the unshifted uid when mediating hard link operations inside
user namespaces/containers. The commit message provides a concrete
example showing ouid=0 instead of the expected ouid=1000000 in
container logs.
2. **Security Impact**: This is a security-relevant bug that causes
AppArmor policy enforcement to behave incorrectly in containerized
environments. The owner-based AppArmor rules (like `owner /root/link
l,`) won't work correctly because the uid comparison is done with the
wrong (unshifted) value.
3. **Minimal and Contained Fix**: The change is small and surgical - it
only modifies the aa_path_link() function to properly map the inode
uid through the mount's idmapping, following the exact same pattern
already used in __file_path_perm():
- Uses `i_uid_into_vfsuid(mnt_idmap(target.mnt), inode)` to get the
vfsuid
- Converts it back with `vfsuid_into_kuid(vfsuid)` for the path_cond
structure
4. **No New Features or Architecture Changes**: This is purely a bug fix
that makes aa_path_link() consistent with how __file_path_perm()
already handles uid mapping. No new functionality is added.
5. **Container/User Namespace Compatibility**: With the widespread use
of containers (LXD, Incus, Docker with userns), this bug affects many
production systems. The fix ensures AppArmor policies work correctly
in these environments.
6. **Low Risk**: The change follows an established pattern in the
codebase (from __file_path_perm) and only affects the specific case
of hard link permission checks in user namespaces. The risk of
regression is minimal.
7. **Clear Testing**: The commit message indicates this was tested with
LXD and Incus containers, showing the issue is reproducible and the
fix validated.
The code change is straightforward - replacing direct access to
`d_backing_inode(old_dentry)->i_uid` with proper idmapping-aware
conversion that respects user namespace uid shifts. This makes
aa_path_link() consistent with other AppArmor file operations that
already handle idmapped mounts correctly.
security/apparmor/file.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/security/apparmor/file.c b/security/apparmor/file.c
index d52a5b14dad4..62bc46e03758 100644
--- a/security/apparmor/file.c
+++ b/security/apparmor/file.c
@@ -423,9 +423,11 @@ int aa_path_link(const struct cred *subj_cred,
{
struct path link = { .mnt = new_dir->mnt, .dentry = new_dentry };
struct path target = { .mnt = new_dir->mnt, .dentry = old_dentry };
+ struct inode *inode = d_backing_inode(old_dentry);
+ vfsuid_t vfsuid = i_uid_into_vfsuid(mnt_idmap(target.mnt), inode);
struct path_cond cond = {
- d_backing_inode(old_dentry)->i_uid,
- d_backing_inode(old_dentry)->i_mode
+ .uid = vfsuid_into_kuid(vfsuid),
+ .mode = inode->i_mode,
};
char *buffer = NULL, *buffer2 = NULL;
struct aa_profile *profile;
--
2.39.5
More information about the AppArmor
mailing list