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 aesteve@redhat.com
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?