DMA_HEAP_IOCTL_ALLOC accepts a charge_fd field that, when set, causes the allocation to be charged to an arbitrary process's cgroup rather than the caller's.
Without an access-control point, any process that holds a handle to a dma-heap device node can charge unlimited memory to any other process's cgroup, potentially exhausting that cgroup's limit and triggering OOM kills independent of the victim's own activity or privileges.
Add security_dma_buf_charge_task(), called in dma_heap_ioctl_allocate() when charge_fd refers to another process. The hook receives the credentials of the allocating process (from) and the credentials of the process whose cgroup will be charged (to), giving security modules a controlled enforcement point for cross-cgroup dma-buf attribution policy.
When CONFIG_SECURITY is not set the hook compiles to an inline returning 0, adding no overhead to the fast path.
Signed-off-by: Albert Esteve aesteve@redhat.com --- drivers/dma-buf/dma-heap.c | 12 +++++++++++- include/linux/lsm_hook_defs.h | 1 + include/linux/security.h | 7 +++++++ security/security.c | 16 ++++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-)
diff --git a/drivers/dma-buf/dma-heap.c b/drivers/dma-buf/dma-heap.c index be77a4c168ab..c31573917886 100644 --- a/drivers/dma-buf/dma-heap.c +++ b/drivers/dma-buf/dma-heap.c @@ -19,6 +19,7 @@ #include <linux/minmax.h> #include <linux/nospec.h> #include <linux/pidfd.h> +#include <linux/security.h> #include <linux/syscalls.h> #include <linux/uaccess.h> #include <linux/xarray.h> @@ -81,13 +82,14 @@ static struct dma_buf *dma_heap_ioctl_allocate(struct file *file, void *data) { struct cgroup *cgrp; struct cgroup_subsys_state *css; + const struct cred *tcred; struct dma_heap_allocation_data *heap_allocation = data; struct dma_heap *heap = file->private_data; struct dma_buf *dmabuf; struct mem_cgroup *memcg = NULL; struct task_struct *task; unsigned int pidfd_flags; - int fd; + int fd, ret; size_t len;
if (heap_allocation->fd) @@ -120,6 +122,14 @@ static struct dma_buf *dma_heap_ioctl_allocate(struct file *file, void *data) 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); + } + memcg = get_mem_cgroup_from_mm(task->mm); put_task_struct(task); } diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index 65c9609ec207..0f1138e16dbe 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -43,6 +43,7 @@ LSM_HOOK(int, 0, capset, struct cred *new, const struct cred *old, const kernel_cap_t *permitted) LSM_HOOK(int, 0, capable, const struct cred *cred, struct user_namespace *ns, int cap, unsigned int opts) +LSM_HOOK(int, 0, dma_buf_charge_task, const struct cred *from, const struct cred *to) LSM_HOOK(int, 0, quotactl, int cmds, int type, int id, const struct super_block *sb) LSM_HOOK(int, 0, quota_on, struct dentry *dentry) LSM_HOOK(int, 0, syslog, int type) diff --git a/include/linux/security.h b/include/linux/security.h index 153e9043058f..89a35eb07b8c 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -350,6 +350,7 @@ int security_capable(const struct cred *cred, struct user_namespace *ns, int cap, unsigned int opts); +int security_dma_buf_charge_task(const struct cred *from, const struct cred *to); int security_quotactl(int cmds, int type, int id, const struct super_block *sb); int security_quota_on(struct dentry *dentry); int security_syslog(int type); @@ -701,6 +702,12 @@ static inline int security_capable(const struct cred *cred, return cap_capable(cred, ns, cap, opts); }
+static inline int security_dma_buf_charge_task(const struct cred *from, + const struct cred *to) +{ + return 0; +} + static inline int security_quotactl(int cmds, int type, int id, const struct super_block *sb) { diff --git a/security/security.c b/security/security.c index 2ee276ab15c5..4a16e4076aab 100644 --- a/security/security.c +++ b/security/security.c @@ -660,6 +660,22 @@ int security_capable(const struct cred *cred, return call_int_hook(capable, cred, ns, cap, opts); }
+/** + * security_dma_buf_charge_task() - Check if cross-cgroup dma-heap charging is allowed + * @from: credentials of the allocating process + * @to: credentials of the process to charge + * + * Check whether the process with credentials @from is allowed to allocate + * dma-heap memory and charge it to the cgroup of the process with credentials + * @to. + * + * Return: Returns 0 if permission is granted. + */ +int security_dma_buf_charge_task(const struct cred *from, const struct cred *to) +{ + return call_int_hook(dma_buf_charge_task, from, to); +} + /** * security_quotactl() - Check if a quotactl() syscall is allowed for this fs * @cmds: commands