On 5/21/26 11:10, Albert Esteve wrote: > When sharing a dma-buf between components of different trust levels, > the allocator may need to hand out a read-only view of a buffer it > holds with read-write access. Currently there is no mechanism to do > this: the file flags set at allocation time are fixed for the > lifetime of the dma-buf, and dup(2) and dup3(2) cannot change the > access mode of the new fd. > > Add DMA_BUF_IOCTL_DERIVE, which takes a struct dma_buf_derive carrying > the desired access flags and returns a new file descriptor for the same > buffer with those flags applied. Permission escalation is rejected with > EACCES. > > The new fd aliases the same struct dma_buf, same dma_resv, same exporter > ops, same underlying memory. Importers that attach to either fd operate > on the same object and observe the same fence timeline. > > To support multiple struct file instances sharing one struct dma_buf, > two small internal adjustments are required. First, move > __dma_buf_list_del() to dma_buf_release() so that list removal fires > exactly once when the dentry is destroyed. Second, update > dma_buf_file_release() to call dma_buf_put() only for the files that > are not primary dmabuf files, leaving the primary fd's refcount managed > by the normal dentry lifecycle.
> Finally, enforce the access restriction in dma_buf_mmap_internal(): > a shared writable mapping (MAP_SHARED + PROT_WRITE) on a read-only fd > is rejected with -EACCES. Without this check, O_RDONLY on a dma-buf > fd would be cosmetic, as the VFS does not enforce f_mode for writable > mmap on anonymous inodes. Clear NAK to that since that would break the existing uAPI. Regards, Christian. > > Signed-off-by: Albert Esteve <[email protected]> > --- > drivers/dma-buf/dma-buf.c | 58 > +++++++++++++++++++++++++++++++++++++++++++- > include/uapi/linux/dma-buf.h | 28 +++++++++++++++++++++ > 2 files changed, 85 insertions(+), 1 deletion(-) > > diff --git a/drivers/dma-buf/dma-buf.c b/drivers/dma-buf/dma-buf.c > index 71f37544a5c61..34a3872365730 100644 > --- a/drivers/dma-buf/dma-buf.c > +++ b/drivers/dma-buf/dma-buf.c > @@ -180,6 +180,7 @@ static void dma_buf_release(struct dentry *dentry) > */ > BUG_ON(dmabuf->cb_in.active || dmabuf->cb_out.active); > > + __dma_buf_list_del(dmabuf); > dmabuf->ops->release(dmabuf); > > if (dmabuf->resv == (struct dma_resv *)&dmabuf[1]) > @@ -193,10 +194,13 @@ static void dma_buf_release(struct dentry *dentry) > > static int dma_buf_file_release(struct inode *inode, struct file *file) > { > + struct dma_buf *dmabuf = file->private_data; > + > if (!is_dma_buf_file(file)) > return -EINVAL; > > - __dma_buf_list_del(file->private_data); > + if (file != dmabuf->file) > + dma_buf_put(dmabuf); > > return 0; > } > @@ -232,6 +236,11 @@ static int dma_buf_mmap_internal(struct file *file, > struct vm_area_struct *vma) > if (!is_dma_buf_file(file)) > return -EINVAL; > > + if ((vma->vm_flags & VM_WRITE) && > + (vma->vm_flags & VM_SHARED) && > + !(file->f_mode & FMODE_WRITE)) > + return -EACCES; > + > dmabuf = file->private_data; > > /* check if buffer supports mmap */ > @@ -537,6 +546,50 @@ static long dma_buf_import_sync_file(struct dma_buf > *dmabuf, > } > #endif > > +static const struct file_operations dma_buf_fops; > + > +static int dma_buf_ioctl_derive(struct dma_buf *dmabuf, struct file *file, > + void __user *udata) > +{ > + struct dma_buf_derive params; > + struct file *new_file; > + int new_fd; > + > + if (copy_from_user(¶ms, udata, sizeof(params))) > + return -EFAULT; > + > + if (params.flags & ~(O_ACCMODE | O_CLOEXEC)) > + return -EINVAL; > + > + /* Escalating permissions is not allowed. */ > + if ((params.flags & O_ACCMODE) == O_RDWR && > + !(file->f_mode & FMODE_WRITE)) > + return -EACCES; > + > + new_file = alloc_file_clone(dmabuf->file, params.flags, &dma_buf_fops); > + if (IS_ERR(new_file)) > + return PTR_ERR(new_file); > + > + get_dma_buf(dmabuf); > + new_file->private_data = dmabuf; > + > + new_fd = get_unused_fd_flags(params.flags & O_CLOEXEC ? O_CLOEXEC : 0); > + if (new_fd < 0) { > + fput(new_file); > + return new_fd; > + } > + > + params.fd = new_fd; > + if (copy_to_user(udata, ¶ms, sizeof(params))) { > + put_unused_fd(new_fd); > + fput(new_file); > + return -EFAULT; > + } > + > + fd_install(new_fd, new_file); > + return 0; > +} > + > static long dma_buf_ioctl(struct file *file, > unsigned int cmd, unsigned long arg) > { > @@ -587,6 +640,9 @@ static long dma_buf_ioctl(struct file *file, > return dma_buf_import_sync_file(dmabuf, (const void __user > *)arg); > #endif > > + case DMA_BUF_IOCTL_DERIVE: > + return dma_buf_ioctl_derive(dmabuf, file, (void __user *)arg); > + > default: > return -ENOTTY; > } > diff --git a/include/uapi/linux/dma-buf.h b/include/uapi/linux/dma-buf.h > index e827c9d20c5d3..d0cf616228e55 100644 > --- a/include/uapi/linux/dma-buf.h > +++ b/include/uapi/linux/dma-buf.h > @@ -168,6 +168,33 @@ struct dma_buf_import_sync_file { > __s32 fd; > }; > > +/** > + * struct dma_buf_derive - Obtain a dma-buf fd with reduced access > permissions > + * > + * Userspace can perform a DMA_BUF_IOCTL_DERIVE to obtain a second file > + * descriptor for the same dma-buf with a subset of the calling fd's > + * permissions. This allows a producer holding read-write access to hand a > + * read-only view to a less-privileged consumer without giving up its own > + * write access or allocating a separate buffer. > + * > + * Unlike first-export ioctls, the new fd is not a re-export. It shares the > + * same reservation object, exporter ops, and underlying memory as the > + * original. > + * > + * The requested permissions must not exceed those of the calling fd. > + */ > +struct dma_buf_derive { > + /** > + * @flags: Requested access flags. > + * > + * Accepts O_RDONLY or O_RDWR, optionally combined with O_CLOEXEC. > + * All other bits must be zero. > + */ > + __u32 flags; > + /** @fd: Returned file descriptor with the requested permissions */ > + __s32 fd; > +}; > + > #define DMA_BUF_BASE 'b' > #define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync) > > @@ -179,5 +206,6 @@ struct dma_buf_import_sync_file { > #define DMA_BUF_SET_NAME_B _IOW(DMA_BUF_BASE, 1, __u64) > #define DMA_BUF_IOCTL_EXPORT_SYNC_FILE _IOWR(DMA_BUF_BASE, 2, struct > dma_buf_export_sync_file) > #define DMA_BUF_IOCTL_IMPORT_SYNC_FILE _IOW(DMA_BUF_BASE, 3, struct > dma_buf_import_sync_file) > +#define DMA_BUF_IOCTL_DERIVE _IOWR(DMA_BUF_BASE, 4, struct > dma_buf_derive) > > #endif >

