Thank you for your contribution! Sashiko AI review found 1 potential issue(s) 
to consider:
- [High] memcg: task cred and mm->owner mismatch in mem_cgroup_get_from_fd()
--

commit 6c6031732d7f42644e3d99679d750fcc12907447
Author: Albert Esteve <[email protected]>

security: dma-buf: Add dma_buf_charge_task LSM hook

This commit adds the security_dma_buf_charge_task() LSM hook and calls it
in mem_cgroup_get_from_fd() when resolving a pidfd, allowing security
modules to mediate cross-process dma-buf cgroup charging.

> diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> index f66254f52b999..ad195cfd1b5f6 100644
> --- a/mm/memcontrol.c
> +++ b/mm/memcontrol.c
[ ... ]
> @@ -5698,6 +5701,14 @@ struct mem_cgroup *mem_cgroup_get_from_fd(int fd)
>       if (IS_ERR(task))
>               return ERR_CAST(task);
>  
> +     tcred = get_task_cred(task);
> +     ret = security_dma_buf_charge_task(current_cred(), tcred);
> +     put_cred(tcred);
> +     if (ret) {
> +             put_task_struct(task);
> +             return ERR_PTR(ret);
> +     }
> +
>       mm = get_task_mm(task);
>       put_task_struct(task);
>       if (!mm)

[Severity: High]
Can the cgroup returned by mem_cgroup_get_from_fd() diverge from the task
credentials checked by security_dma_buf_charge_task()?

Here security_dma_buf_charge_task() validates get_task_cred(task), and then
mem_cgroup_get_from_fd() resolves the target mem_cgroup via get_task_mm(task)
and get_mem_cgroup_from_mm(mm):

mm/memcontrol.c:mem_cgroup_get_from_fd() {
        ...
        mm = get_task_mm(task);
        put_task_struct(task);
        if (!mm)
                return ERR_PTR(-ESRCH);

        memcg = get_mem_cgroup_from_mm(mm);
        mmput(mm);
        return memcg;
}

However, get_mem_cgroup_from_mm() resolves the mem_cgroup from mm->owner
rather than from task, and falls back to root_mem_cgroup when mm->owner is
NULL:

mm/memcontrol.c:get_mem_cgroup_from_mm() {
        ...
        rcu_read_lock();
        do {
                memcg = mem_cgroup_from_task(rcu_dereference(mm->owner));
                if (unlikely(!memcg))
                        memcg = root_mem_cgroup;
        } while (!css_tryget(&memcg->css));
        rcu_read_unlock();
        return memcg;
}

If task exits after get_task_mm(task) increments mm->mm_users but before
get_mem_cgroup_from_mm(mm) runs, exit_mm() -> mm_update_next_owner() finds
no live owner with mm_users > 1 and clears mm->owner:

kernel/exit.c:mm_update_next_owner() {
        ...
        /*
         * We found no owner yet mm_users > 1: this implies that we are
         * most likely racing with swapoff (try_to_unuse()) or /proc or
         * ptrace or page migration (get_task_mm()).  Mark owner as NULL.
         */
        WRITE_ONCE(mm->owner, NULL);
        ...
}

In that case, get_mem_cgroup_from_mm(mm) returns root_mem_cgroup, where
try_charge() skips charging via mem_cgroup_is_root(memcg). A confined
caller in DMA_HEAP_IOCTL_ALLOC or DMA_BUF_IOCTL_XFER_CHARGE could fork a
short-lived helper child with matching credentials to pass
security_dma_buf_charge_task() and race its exit to charge allocations to
root_mem_cgroup, escaping its own cgroup memory limit.

Similarly, if task shares an mm_struct with another task via clone(CLONE_VM)
that has different credentials or cgroup membership, mm->owner can refer to
that other task while security_dma_buf_charge_task() only checks task's
credentials.

Would it make sense to resolve the task and its credentials from mm->owner
(or look up the mem_cgroup directly from task) so that the LSM check and the
mem_cgroup lookup operate on the same task and cannot fall back to
root_mem_cgroup when mm->owner is cleared?

-- 
Sashiko AI review ยท 
https://sashiko.dev/#/patchset/20261027-v2_20230123_tjmercier_google_com-v3-0-78d2c4cb8...@redhat.com?part=3

Reply via email to