Introduce fs_revocable_replace() to simplify the use of the revocable APIs with file_operations.
The function should only be used after filp->f_op->open(). It assumes the filp->private_data would be set only once in filp->f_op->open() and wouldn't update in subsequent file operations.
Signed-off-by: Tzung-Bi Shih tzungbi@kernel.org --- v6: - Use filp->private_data for the replacement context. - Prevent file operations from calling if the resource has been revoked. - Support only 1 resource again. - Rename REVOCABLE_TRY_ACCESS_WITH() -> REVOCABLE_TRY_ACCESS_SCOPED(). - Use new REVOCABLE_TRY_ACCESS_WITH() if applicable.
v5: https://lore.kernel.org/chrome-platform/20251016054204.1523139-6-tzungbi@ker... - Rename to "fs_revocable". - Move the replacement context to struct file. - Support multiple revocable providers.
v4: https://lore.kernel.org/chrome-platform/20250923075302.591026-6-tzungbi@kern... - New in the series.
fs/Makefile | 2 +- fs/fs_revocable.c | 156 +++++++++++++++++++++++++++++++++++ include/linux/fs_revocable.h | 14 ++++ 3 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 fs/fs_revocable.c create mode 100644 include/linux/fs_revocable.h
diff --git a/fs/Makefile b/fs/Makefile index a04274a3c854..f1e5d7b52781 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -16,7 +16,7 @@ obj-y := open.o read_write.o file_table.o super.o \ stack.o fs_struct.o statfs.o fs_pin.o nsfs.o \ fs_dirent.o fs_context.o fs_parser.o fsopen.o init.o \ kernel_read_file.o mnt_idmapping.o remap_range.o pidfs.o \ - file_attr.o + file_attr.o fs_revocable.o
obj-$(CONFIG_BUFFER_HEAD) += buffer.o mpage.o obj-$(CONFIG_PROC_FS) += proc_namespace.o diff --git a/fs/fs_revocable.c b/fs/fs_revocable.c new file mode 100644 index 000000000000..9ffa71cb67ed --- /dev/null +++ b/fs/fs_revocable.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 Google LLC + * + * File operation replacement with Revocable + */ + +#include <linux/cleanup.h> +#include <linux/fs_revocable.h> +#include <linux/poll.h> +#include <linux/revocable.h> + +struct fops_replacement { + struct file *filp; + void *orig_private_data; + const struct file_operations *orig_fops; + struct file_operations fops; + struct revocable *rev; +}; + +/* + * Recover the private_data to its original one. + */ +static struct fops_replacement *_recover_private_data(struct file *filp) +{ + struct fops_replacement *fr = filp->private_data; + + filp->private_data = fr->orig_private_data; + return fr; +} + +/* + * Replace the private_data to fops_replacement. + */ +static void _replace_private_data(struct fops_replacement *fr) +{ + fr->filp->private_data = fr; +} + +DEFINE_CLASS(fops_replacement, struct fops_replacement *, + _replace_private_data(_T), _recover_private_data(filp), + struct file *filp) + +static ssize_t fs_revocable_read(struct file *filp, char __user *buffer, + size_t length, loff_t *offset) +{ + void *any; + CLASS(fops_replacement, fr)(filp); + + REVOCABLE_TRY_ACCESS_WITH(fr->rev, any); + if (!any) + return -ENODEV; + + return fr->orig_fops->read(filp, buffer, length, offset); +} + +static __poll_t fs_revocable_poll(struct file *filp, poll_table *wait) +{ + void *any; + CLASS(fops_replacement, fr)(filp); + + REVOCABLE_TRY_ACCESS_WITH(fr->rev, any); + if (!any) + return -ENODEV; + + return fr->orig_fops->poll(filp, wait); +} + +static long fs_revocable_unlocked_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + void *any; + CLASS(fops_replacement, fr)(filp); + + REVOCABLE_TRY_ACCESS_WITH(fr->rev, any); + if (!any) + return -ENODEV; + + return fr->orig_fops->unlocked_ioctl(filp, cmd, arg); +} + +static int fs_revocable_release(struct inode *inode, struct file *filp) +{ + struct fops_replacement *fr = _recover_private_data(filp); + int ret = 0; + void *any; + + filp->f_op = fr->orig_fops; + + if (!fr->orig_fops->release) + goto leave; + + REVOCABLE_TRY_ACCESS_SCOPED(fr->rev, any) { + if (!any) { + ret = -ENODEV; + goto leave; + } + + ret = fr->orig_fops->release(inode, filp); + } + +leave: + kfree(fr); + return ret; +} + +/** + * fs_revocable_replace() - Replace the file operations to be revocable-aware. + * @rp: The revocable resource provider. + * @filp: The opening file. + * + * This replaces @filp->f_op to a set of wrappers. The wrappers return -ENODEV + * if the resource provided by @rp has been revoked. Note that it doesn't + * concern how the file operations access the resource but only care about if + * the resource is still available. + * + * This should only be used after @filp->f_op->open(). It assumes the + * @filp->private_data would be set only once in @filp->f_op->open() and wouldn't + * update in subsequent file operations. + */ +int fs_revocable_replace(struct revocable_provider *rp, struct file *filp) +{ + struct fops_replacement *fr; + + fr = kzalloc(sizeof(*fr), GFP_KERNEL); + if (!fr) + return -ENOMEM; + + fr->rev = revocable_alloc(rp); + if (!fr->rev) + goto free_fr; + + fr->filp = filp; + fr->orig_private_data = filp->private_data; + fr->orig_fops = filp->f_op; + + memcpy(&fr->fops, filp->f_op, sizeof(fr->fops)); + fr->fops.release = fs_revocable_release; + + if (fr->fops.read) + fr->fops.read = fs_revocable_read; + if (fr->fops.poll) + fr->fops.poll = fs_revocable_poll; + if (fr->fops.unlocked_ioctl) + fr->fops.unlocked_ioctl = fs_revocable_unlocked_ioctl; + + filp->f_op = &fr->fops; + filp->private_data = fr; + return 0; +free_fr: + kfree(fr); + if (filp->f_op->release) + filp->f_op->release(filp->f_inode, filp); + return -ENOMEM; +} +EXPORT_SYMBOL_GPL(fs_revocable_replace); diff --git a/include/linux/fs_revocable.h b/include/linux/fs_revocable.h new file mode 100644 index 000000000000..498d035315e6 --- /dev/null +++ b/include/linux/fs_revocable.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2025 Google LLC + */ + +#ifndef __LINUX_FS_REVOCABLE_H +#define __LINUX_FS_REVOCABLE_H + +#include <linux/fs.h> +#include <linux/revocable.h> + +int fs_revocable_replace(struct revocable_provider *rp, struct file *filp); + +#endif /* __LINUX_FS_REVOCABLE_H */