[PATCH 1/2] apparmor: shift ouid when mediating hard links in userns

John Johansen john.johansen at canonical.com
Sat May 24 23:16:23 UTC 2025


On 5/19/25 14:56, Gabriel Totev wrote:
> On Sat May 17, 2025 at 4:40 AM EDT, John Johansen wrote:
>> On 4/16/25 15:42, Gabriel Totev wrote:
>>> 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.
>>
>> so unfortunately this isn't correct. Yes some mapping needs to be
>> done but it needs to be relative to different policy namespaces. I
>> need to spend some time on this
> 
> Not sure I understand... I based this patch and its sibling on similar
> code for making path_cond structs from the lsm.c functions:
> - apparmor_path_rename()
> - apparmor_file_open()
> - common_perm_cond()
> - common_perm_rm()
> 
yes, unfortunately those patches didn't go through me, and are also bad
or more correctly bad dependent on the policy.
  
> Are hard links (and Unix sockets) different in terms of figuring out the
> correct uid? Or should all these functions be updated to be relative to

no

> policy namespaces? Perhaps they already are and I can't tell? (not sure
> what this would look like or how uids would be affected)
> 
yep everything needs to be updated to properly deal with policy namespaces
and stacking.

> I'm by no means an AppArmor expert but I'd like to understand and if
> possible help get this fixed as it prevent Snaps from running correctly
> in LXD/Incus containers. I've built and tested with these patches and it
> seems to work: Snaps now don't attract spurious denials and the ouid
> from my example above gets the correct value of 1000000 rather than 0.
> However, I can't be totally sure I'm not introducing any regressions or
> vulnerabilities.
> 
so it depends on the policy being applied and at what level of the policy
stack. There is some long term work that needs to be done here. Its a
bit of a hack but in the short term, I think we can tag each profile with
the correct user and mount namespace and use that for the mappings here.

That should work well enough to address the current bug. I need to spend
some time to do some work on the namespace side, for a longer term fix

> If there's anything I can do to help with this effort, please let me know!
> 
>>>
>>> Signed-off-by: Gabriel Totev <gabriel.totev at zetier.com>
>>> ---
>>>    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 5c984792cbf0..ecd36199337c 100644
>>> --- a/security/apparmor/file.c
>>> +++ b/security/apparmor/file.c
>>> @@ -430,9 +430,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;
> 




More information about the Linux-security-module-archive mailing list