DMA_HEAP_IOCTL_ALLOC accepts a charge_pid_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_heap_alloc(), called in dma_heap_ioctl_allocate() when charge_pid_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 ff6e259afcdc0..e8ffb1031955e 100644 --- a/drivers/dma-buf/dma-heap.c +++ b/drivers/dma-buf/dma-heap.c @@ -18,6 +18,7 @@ #include <linux/list.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> @@ -122,12 +123,13 @@ static int dma_heap_open(struct inode *inode, struct file *file)
static long dma_heap_ioctl_allocate(struct file *file, void *data) { + const struct cred *tcred; struct dma_heap_allocation_data *heap_allocation = data; struct dma_heap *heap = file->private_data; struct mem_cgroup *memcg = NULL; struct task_struct *task; unsigned int pidfd_flags; - int fd; + int fd, ret;
if (heap_allocation->fd) return -EINVAL; @@ -143,6 +145,14 @@ static long dma_heap_ioctl_allocate(struct file *file, void *data) if (IS_ERR(task)) return PTR_ERR(task);
+ tcred = get_task_cred(task); + ret = security_dma_heap_alloc(current_cred(), tcred); + put_cred(tcred); + if (ret) { + put_task_struct(task); + return 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 2b8dfb35caed3..6a91656f97e1e 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_heap_alloc, 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 41d7367cf4036..f1dad1eabe754 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_heap_alloc(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_heap_alloc(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 4e999f0236516..4adacef73c507 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_heap_alloc() - 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_heap_alloc(const struct cred *from, const struct cred *to) +{ + return call_int_hook(dma_heap_alloc, from, to); +} + /** * security_quotactl() - Check if a quotactl() syscall is allowed for this fs * @cmds: commands