| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * eCryptfs: Linux filesystem encryption layer | |
| * | |
| * Copyright (C) 2008 International Business Machines Corp. | |
| * Author(s): Michael A. Halcrow <mahalcro@us.ibm.com> | |
| */ | |
| #include <linux/kthread.h> | |
| #include <linux/freezer.h> | |
| #include <linux/slab.h> | |
| #include <linux/wait.h> | |
| #include <linux/mount.h> | |
| #include "ecryptfs_kernel.h" | |
| struct ecryptfs_open_req { | |
| struct file **lower_file; | |
| struct path path; | |
| struct completion done; | |
| struct list_head kthread_ctl_list; | |
| }; | |
| static struct ecryptfs_kthread_ctl { | |
| #define ECRYPTFS_KTHREAD_ZOMBIE 0x00000001 | |
| u32 flags; | |
| struct mutex mux; | |
| struct list_head req_list; | |
| wait_queue_head_t wait; | |
| } ecryptfs_kthread_ctl; | |
| static struct task_struct *ecryptfs_kthread; | |
| /** | |
| * ecryptfs_threadfn | |
| * @ignored: ignored | |
| * | |
| * The eCryptfs kernel thread that has the responsibility of getting | |
| * the lower file with RW permissions. | |
| * | |
| * Returns zero on success; non-zero otherwise | |
| */ | |
| static int ecryptfs_threadfn(void *ignored) | |
| { | |
| set_freezable(); | |
| while (1) { | |
| struct ecryptfs_open_req *req; | |
| wait_event_freezable( | |
| ecryptfs_kthread_ctl.wait, | |
| (!list_empty(&ecryptfs_kthread_ctl.req_list) | |
| || kthread_should_stop())); | |
| mutex_lock(&ecryptfs_kthread_ctl.mux); | |
| if (ecryptfs_kthread_ctl.flags & ECRYPTFS_KTHREAD_ZOMBIE) { | |
| mutex_unlock(&ecryptfs_kthread_ctl.mux); | |
| goto out; | |
| } | |
| while (!list_empty(&ecryptfs_kthread_ctl.req_list)) { | |
| req = list_first_entry(&ecryptfs_kthread_ctl.req_list, | |
| struct ecryptfs_open_req, | |
| kthread_ctl_list); | |
| list_del(&req->kthread_ctl_list); | |
| *req->lower_file = dentry_open(&req->path, | |
| (O_RDWR | O_LARGEFILE), current_cred()); | |
| complete(&req->done); | |
| } | |
| mutex_unlock(&ecryptfs_kthread_ctl.mux); | |
| } | |
| out: | |
| return 0; | |
| } | |
| int __init ecryptfs_init_kthread(void) | |
| { | |
| int rc = 0; | |
| mutex_init(&ecryptfs_kthread_ctl.mux); | |
| init_waitqueue_head(&ecryptfs_kthread_ctl.wait); | |
| INIT_LIST_HEAD(&ecryptfs_kthread_ctl.req_list); | |
| ecryptfs_kthread = kthread_run(&ecryptfs_threadfn, NULL, | |
| "ecryptfs-kthread"); | |
| if (IS_ERR(ecryptfs_kthread)) { | |
| rc = PTR_ERR(ecryptfs_kthread); | |
| printk(KERN_ERR "%s: Failed to create kernel thread; rc = [%d]" | |
| "\n", __func__, rc); | |
| } | |
| return rc; | |
| } | |
| void ecryptfs_destroy_kthread(void) | |
| { | |
| struct ecryptfs_open_req *req, *tmp; | |
| mutex_lock(&ecryptfs_kthread_ctl.mux); | |
| ecryptfs_kthread_ctl.flags |= ECRYPTFS_KTHREAD_ZOMBIE; | |
| list_for_each_entry_safe(req, tmp, &ecryptfs_kthread_ctl.req_list, | |
| kthread_ctl_list) { | |
| list_del(&req->kthread_ctl_list); | |
| *req->lower_file = ERR_PTR(-EIO); | |
| complete(&req->done); | |
| } | |
| mutex_unlock(&ecryptfs_kthread_ctl.mux); | |
| kthread_stop(ecryptfs_kthread); | |
| wake_up(&ecryptfs_kthread_ctl.wait); | |
| } | |
| /** | |
| * ecryptfs_privileged_open | |
| * @lower_file: Result of dentry_open by root on lower dentry | |
| * @lower_dentry: Lower dentry for file to open | |
| * @lower_mnt: Lower vfsmount for file to open | |
| * @cred: credential to use for this call | |
| * | |
| * This function gets a r/w file opened against the lower dentry. | |
| * | |
| * Returns zero on success; non-zero otherwise | |
| */ | |
| int ecryptfs_privileged_open(struct file **lower_file, | |
| struct dentry *lower_dentry, | |
| struct vfsmount *lower_mnt, | |
| const struct cred *cred) | |
| { | |
| struct ecryptfs_open_req req; | |
| int flags = O_LARGEFILE; | |
| int rc = 0; | |
| init_completion(&req.done); | |
| req.lower_file = lower_file; | |
| req.path.dentry = lower_dentry; | |
| req.path.mnt = lower_mnt; | |
| /* Corresponding dput() and mntput() are done when the | |
| * lower file is fput() when all eCryptfs files for the inode are | |
| * released. */ | |
| flags |= IS_RDONLY(d_inode(lower_dentry)) ? O_RDONLY : O_RDWR; | |
| (*lower_file) = dentry_open(&req.path, flags, cred); | |
| if (!IS_ERR(*lower_file)) | |
| goto out; | |
| if ((flags & O_ACCMODE) == O_RDONLY) { | |
| rc = PTR_ERR((*lower_file)); | |
| goto out; | |
| } | |
| mutex_lock(&ecryptfs_kthread_ctl.mux); | |
| if (ecryptfs_kthread_ctl.flags & ECRYPTFS_KTHREAD_ZOMBIE) { | |
| rc = -EIO; | |
| mutex_unlock(&ecryptfs_kthread_ctl.mux); | |
| printk(KERN_ERR "%s: We are in the middle of shutting down; " | |
| "aborting privileged request to open lower file\n", | |
| __func__); | |
| goto out; | |
| } | |
| list_add_tail(&req.kthread_ctl_list, &ecryptfs_kthread_ctl.req_list); | |
| mutex_unlock(&ecryptfs_kthread_ctl.mux); | |
| wake_up(&ecryptfs_kthread_ctl.wait); | |
| wait_for_completion(&req.done); | |
| if (IS_ERR(*lower_file)) | |
| rc = PTR_ERR(*lower_file); | |
| out: | |
| return rc; | |
| } |