diff options
| author | Mark Brown <broonie@kernel.org> | 2026-07-26 21:03:00 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-07-26 21:03:00 +0100 |
| commit | 9162650d34ab9dfe48f73cd255c41a6084669df3 (patch) | |
| tree | 75578b321f8e890195a7238c296c13498cac58e2 /fs | |
| parent | 4834b6805ec130608e7416797f779a76d6a11fd7 (diff) | |
| parent | ce76d9d4f5f36501013bd94bb65dd067f04b2913 (diff) | |
| download | linux-next-9162650d34ab9dfe48f73cd255c41a6084669df3.tar.gz linux-next-9162650d34ab9dfe48f73cd255c41a6084669df3.zip | |
next-20260723/vfs-brauner
Diffstat (limited to 'fs')
107 files changed, 2345 insertions, 2075 deletions
diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 5783d0336f96..3829554ca369 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -645,7 +645,6 @@ error: * @dir: The parent directory * @dentry: The name of file to be created * @mode: The UNIX file mode to set - * @excl: True if the file must not yet exist * * open(.., O_CREAT) is handled in v9fs_vfs_atomic_open(). This is only called * for mknod(2). @@ -654,7 +653,7 @@ error: static int v9fs_vfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dir); u32 perm = unixmode2p9mode(v9ses, mode); @@ -689,7 +688,7 @@ static struct dentry *v9fs_vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, p9_debug(P9_DEBUG_VFS, "name %pd\n", dentry); v9ses = v9fs_inode2v9ses(dir); - perm = unixmode2p9mode(v9ses, mode | S_IFDIR); + perm = unixmode2p9mode(v9ses, mode); fid = v9fs_create(v9ses, dir, dentry, NULL, perm, P9_OREAD); if (IS_ERR(fid)) return ERR_CAST(fid); diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index f7396d20cb6c..116b29e95f21 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -213,12 +213,11 @@ int v9fs_open_to_dotl_flags(int flags) * @dir: directory inode that is being created * @dentry: dentry that is being deleted * @omode: create permissions - * @excl: True if the file must not yet exist * */ static int v9fs_vfs_create_dotl(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t omode, bool excl) + struct dentry *dentry, umode_t omode) { return v9fs_vfs_mknod_dotl(idmap, dir, dentry, omode, 0); } @@ -362,7 +361,6 @@ static struct dentry *v9fs_vfs_mkdir_dotl(struct mnt_idmap *idmap, p9_debug(P9_DEBUG_VFS, "name %pd\n", dentry); v9ses = v9fs_inode2v9ses(dir); - omode |= S_IFDIR; if (dir->i_mode & S_ISGID) omode |= S_ISGID; diff --git a/fs/Kconfig b/fs/Kconfig index cf6ae64776e6..64ff193fb42c 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -315,7 +315,6 @@ source "fs/hfs/Kconfig" source "fs/hfsplus/Kconfig" source "fs/befs/Kconfig" source "fs/bfs/Kconfig" -source "fs/efs/Kconfig" source "fs/jffs2/Kconfig" # UBIFS File system configuration source "fs/ubifs/Kconfig" diff --git a/fs/Kconfig.binfmt b/fs/Kconfig.binfmt index 1949e25c7741..daeac4889d03 100644 --- a/fs/Kconfig.binfmt +++ b/fs/Kconfig.binfmt @@ -168,6 +168,20 @@ config BINFMT_MISC you have use for it; the module is called binfmt_misc. If you don't know what to answer at this point, say Y. +config BINFMT_MISC_BPF + bool "BPF-selected interpreters for misc binaries" + depends on BINFMT_MISC=y + depends on BPF_SYSCALL && BPF_JIT && DEBUG_INFO_BTF + help + Allow binfmt_misc binary type handlers to be implemented as bpf + struct_ops programs. Instead of matching a fixed magic and + redirecting to a fixed interpreter recorded at registration time + such handlers match binaries programmatically and compute the + interpreter to use per binary, e.g. relative to the location of + the binary itself. + + If you don't know what to answer at this point, say N. + config COREDUMP bool "Enable core dump support" if EXPERT default y diff --git a/fs/Makefile b/fs/Makefile index 89a8a9d207d1..447a1f7c5732 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_FS_ENCRYPTION) += crypto/ obj-$(CONFIG_FS_VERITY) += verity/ obj-$(CONFIG_FILE_LOCKING) += locks.o obj-$(CONFIG_BINFMT_MISC) += binfmt_misc.o +obj-$(CONFIG_BINFMT_MISC_BPF) += binfmt_misc_bpf.o obj-$(CONFIG_BINFMT_SCRIPT) += binfmt_script.o obj-$(CONFIG_BINFMT_ELF) += binfmt_elf.o obj-$(CONFIG_COMPAT_BINFMT_ELF) += compat_binfmt_elf.o @@ -92,7 +93,6 @@ obj-$(CONFIG_HPFS_FS) += hpfs/ obj-$(CONFIG_NTFS_FS) += ntfs/ obj-$(CONFIG_NTFS3_FS) += ntfs3/ obj-$(CONFIG_UFS_FS) += ufs/ -obj-$(CONFIG_EFS_FS) += efs/ obj-$(CONFIG_JFFS2_FS) += jffs2/ obj-$(CONFIG_UBIFS_FS) += ubifs/ obj-$(CONFIG_AFFS_FS) += affs/ diff --git a/fs/affs/affs.h b/fs/affs/affs.h index 44a3f69d275f..d6b3393633f2 100644 --- a/fs/affs/affs.h +++ b/fs/affs/affs.h @@ -169,7 +169,7 @@ extern int affs_hash_name(struct super_block *sb, const u8 *name, unsigned int l extern struct dentry *affs_lookup(struct inode *dir, struct dentry *dentry, unsigned int); extern int affs_unlink(struct inode *dir, struct dentry *dentry); extern int affs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool); + struct dentry *dentry, umode_t mode); extern struct dentry *affs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode); extern int affs_rmdir(struct inode *dir, struct dentry *dentry); diff --git a/fs/affs/namei.c b/fs/affs/namei.c index c3c6532da4b0..5311828b19e8 100644 --- a/fs/affs/namei.c +++ b/fs/affs/namei.c @@ -243,7 +243,7 @@ affs_unlink(struct inode *dir, struct dentry *dentry) int affs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; struct inode *inode; @@ -287,7 +287,7 @@ affs_mkdir(struct mnt_idmap *idmap, struct inode *dir, if (!inode) return ERR_PTR(-ENOSPC); - inode->i_mode = S_IFDIR | mode; + inode->i_mode = mode; affs_mode_to_prot(inode); inode->i_op = &affs_dir_inode_operations; diff --git a/fs/affs/super.c b/fs/affs/super.c index b232251aa7bb..4f331f784db2 100644 --- a/fs/affs/super.c +++ b/fs/affs/super.c @@ -88,7 +88,7 @@ void affs_mark_sb_dirty(struct super_block *sb) spin_lock(&sbi->work_lock); if (!sbi->work_queued) { delay = msecs_to_jiffies(dirty_writeback_interval * 10); - queue_delayed_work(system_long_wq, &sbi->sb_work, delay); + queue_delayed_work(system_dfl_long_wq, &sbi->sb_work, delay); sbi->work_queued = 1; } spin_unlock(&sbi->work_lock); diff --git a/fs/afs/dir.c b/fs/afs/dir.c index 6df56fe9163f..81565366d937 100644 --- a/fs/afs/dir.c +++ b/fs/afs/dir.c @@ -34,7 +34,7 @@ static bool afs_lookup_filldir(struct dir_context *ctx, const char *name, int nl u64 ino, u32 uniquifier); #define AFS_LOOKUP ((filldir_t)0x137UL) static int afs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl); + struct dentry *dentry, umode_t mode); static struct dentry *afs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode); static int afs_rmdir(struct inode *dir, struct dentry *dentry); @@ -1333,7 +1333,7 @@ static struct dentry *afs_mkdir(struct mnt_idmap *idmap, struct inode *dir, op->file[0].modification = true; op->file[0].update_ctime = true; op->dentry = dentry; - op->create.mode = S_IFDIR | mode; + op->create.mode = mode; op->create.reason = afs_edit_dir_for_mkdir; op->mtime = current_time(dir); op->ops = &afs_mkdir_operation; @@ -1633,7 +1633,7 @@ static const struct afs_operation_ops afs_create_operation = { * create a regular file on an AFS filesystem */ static int afs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct afs_operation *op; struct afs_vnode *dvnode = AFS_FS_I(dir); diff --git a/fs/autofs/root.c b/fs/autofs/root.c index 186e960f1e23..b36439f4521e 100644 --- a/fs/autofs/root.c +++ b/fs/autofs/root.c @@ -741,7 +741,7 @@ static struct dentry *autofs_dir_mkdir(struct mnt_idmap *idmap, autofs_del_active(dentry); - inode = autofs_get_inode(dir->i_sb, S_IFDIR | mode); + inode = autofs_get_inode(dir->i_sb, mode); if (!inode) return ERR_PTR(-ENOMEM); diff --git a/fs/bad_inode.c b/fs/bad_inode.c index acf8613f5e36..486c40f73e51 100644 --- a/fs/bad_inode.c +++ b/fs/bad_inode.c @@ -29,7 +29,7 @@ static const struct file_operations bad_file_ops = static int bad_inode_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, bool excl) + umode_t mode) { return -EIO; } diff --git a/fs/bfs/dir.c b/fs/bfs/dir.c index 5b40ab09a796..4c3b4db08cde 100644 --- a/fs/bfs/dir.c +++ b/fs/bfs/dir.c @@ -83,7 +83,7 @@ const struct file_operations bfs_dir_operations = { }; static int bfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { int err; struct inode *inode; diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c index 5de615ca7a75..c06cea00a6d6 100644 --- a/fs/binfmt_misc.c +++ b/fs/binfmt_misc.c @@ -10,45 +10,49 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/hex.h> -#include <linux/init.h> -#include <linux/sched/mm.h> -#include <linux/magic.h> +#include <linux/binfmt_misc.h> #include <linux/binfmts.h> -#include <linux/slab.h> +#include <linux/bitops.h> +#include <linux/bits.h> +#include <linux/bug.h> +#include <linux/cleanup.h> +#include <linux/cred.h> #include <linux/ctype.h> -#include <linux/string_helpers.h> #include <linux/file.h> -#include <linux/pagemap.h> -#include <linux/namei.h> -#include <linux/mount.h> -#include <linux/fs_context.h> -#include <linux/syscalls.h> #include <linux/fs.h> +#include <linux/fs_context.h> +#include <linux/init.h> +#include <linux/kstrtox.h> +#include <linux/magic.h> +#include <linux/module.h> +#include <linux/printk.h> +#include <linux/rculist.h> +#include <linux/refcount.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/srcu.h> +#include <linux/string.h> +#include <linux/string_helpers.h> #include <linux/uaccess.h> +#include <linux/user_namespace.h> -#include "internal.h" - -#ifdef DEBUG -# define USE_DEBUG 1 -#else -# define USE_DEBUG 0 -#endif - -enum { - VERBOSE_STATUS = 1 /* make it zero to save 400 bytes kernel memory */ +/* Entry status and match type bit numbers. */ +enum binfmt_misc_entry_bits { + MISC_FMT_ENABLED_BIT = 0, + MISC_FMT_MAGIC_BIT = 1, + MISC_FMT_BPF_BIT = 2, }; -enum {Enabled, Magic}; -#define MISC_FMT_PRESERVE_ARGV0 (1UL << 31) -#define MISC_FMT_OPEN_BINARY (1UL << 30) -#define MISC_FMT_CREDENTIALS (1UL << 29) -#define MISC_FMT_OPEN_FILE (1UL << 28) +/* Entry behavior flags, fixed at registration time. */ +enum binfmt_misc_entry_flags { + MISC_FMT_PRESERVE_ARGV0 = (1U << 31), + MISC_FMT_OPEN_BINARY = (1U << 30), + MISC_FMT_CREDENTIALS = (1U << 29), + MISC_FMT_OPEN_FILE = (1U << 28), +}; -typedef struct { - struct list_head list; +struct binfmt_misc_entry { + struct hlist_node node; unsigned long flags; /* type, status, etc. */ int offset; /* offset of magic */ int size; /* size of magic/mask */ @@ -58,10 +62,12 @@ typedef struct { char *name; struct dentry *dentry; struct file *interp_file; + const struct binfmt_misc_ops *bpf_ops; /* bpf-backed handler ('B') */ + const char *bpf_ops_name; refcount_t users; /* sync removal with load_misc_binary() */ -} Node; - -static struct file_system_type bm_fs_type; + struct rcu_head rcu; + char buf[]; /* register string, fields point in here */ +}; /* * Max length of the register string. Determined by: @@ -74,54 +80,83 @@ static struct file_system_type bm_fs_type; * - interp: ~50 bytes * - flags: 5 bytes * Round that up a bit, and then back off to hold the internal data - * (like struct Node). + * (like struct binfmt_misc_entry). */ #define MAX_REGISTER_LENGTH 1920 +/* Trailing delimiter pad so field parsing always terminates at a delimiter. */ +#define MISC_DELIM_PAD 8 + +/* Protects the entry walk in load_misc_binary(), which may sleep in it. */ +DEFINE_STATIC_SRCU_FAST(bm_entries_srcu); + +/* Check if @e's magic matches @bprm's buffer, applying the mask if set. */ +static bool entry_matches_magic(const struct binfmt_misc_entry *e, + const struct linux_binprm *bprm) +{ + const char *s = bprm->buf + e->offset; + int i; + + if (!e->mask) + return !memcmp(s, e->magic, e->size); + + for (i = 0; i < e->size; i++) + if ((s[i] ^ e->magic[i]) & e->mask[i]) + return false; + return true; +} + +/* Check if @e's registered extension matches @ext, NULL if there is none. */ +static bool entry_matches_extension(const struct binfmt_misc_entry *e, + const char *ext) +{ + return ext && !strcmp(e->magic, ext); +} + /** * search_binfmt_handler - search for a binary handler for @bprm * @misc: handle to binfmt_misc instance * @bprm: binary for which we are looking for a handler * * Search for a binary type handler for @bprm in the list of registered binary - * type handlers. + * type handlers. A 'B' entry's match program decides whether the handler + * applies; it may sleep to read the binary. The matched entry is returned + * with a reference taken while the walk still held it; a dying entry - + * unlinked with its last reference gone - cannot be matched and the walk + * moves on. * - * Return: binary type list entry on success, NULL on failure + * The caller must hold the bm_entries_srcu read lock, which allows an + * entry's evaluation to sleep. + * + * Return: referenced binary type list entry on success, NULL on failure */ -static Node *search_binfmt_handler(struct binfmt_misc *misc, - struct linux_binprm *bprm) +static struct binfmt_misc_entry * +search_binfmt_handler(struct binfmt_misc *misc, struct linux_binprm *bprm) { - char *p = strrchr(bprm->interp, '.'); - Node *e; + char *dot = strrchr(bprm->interp, '.'); + const char *ext = dot ? dot + 1 : NULL; + struct binfmt_misc_entry *e; /* Walk all the registered handlers. */ - list_for_each_entry(e, &misc->entries, list) { - char *s; - int j; - + hlist_for_each_entry_rcu(e, &misc->entries, node, + srcu_read_lock_held(&bm_entries_srcu)) { /* Make sure this one is currently enabled. */ - if (!test_bit(Enabled, &e->flags)) - continue; - - /* Do matching based on extension if applicable. */ - if (!test_bit(Magic, &e->flags)) { - if (p && !strcmp(e->magic, p + 1)) - return e; + if (!test_bit(MISC_FMT_ENABLED_BIT, &e->flags)) continue; - } - /* Do matching based on magic & mask. */ - s = bprm->buf + e->offset; - if (e->mask) { - for (j = 0; j < e->size; j++) - if ((*s++ ^ e->magic[j]) & e->mask[j]) - break; + if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) { + if (!e->bpf_ops->match(bprm)) + continue; + } else if (test_bit(MISC_FMT_MAGIC_BIT, &e->flags)) { + if (!entry_matches_magic(e, bprm)) + continue; } else { - for (j = 0; j < e->size; j++) - if ((*s++ ^ e->magic[j])) - break; + if (!entry_matches_extension(e, ext)) + continue; } - if (j == e->size) + + /* A dying entry cannot be matched, walk on. */ + if (refcount_inc_not_zero(&e->users)) return e; } @@ -133,157 +168,244 @@ static Node *search_binfmt_handler(struct binfmt_misc *misc, * @misc: handle to binfmt_misc instance * @bprm: binary for which we are looking for a handler * - * Try to find a binfmt handler for the binary type. If one is found take a - * reference to protect against removal via bm_{entry,status}_write(). + * Try to find a binfmt handler for the binary type. If one is found it is + * returned with a reference protecting it against removal via + * bm_{entry,status}_write(). * * Return: binary type list entry on success, NULL on failure */ -static Node *get_binfmt_handler(struct binfmt_misc *misc, - struct linux_binprm *bprm) +static struct binfmt_misc_entry *get_binfmt_handler(struct binfmt_misc *misc, + struct linux_binprm *bprm) +{ + guard(srcu_fast)(&bm_entries_srcu); + return search_binfmt_handler(misc, bprm); +} + +static void bm_entry_free_rcu(struct rcu_head *rcu) { - Node *e; - - read_lock(&misc->entries_lock); - e = search_binfmt_handler(misc, bprm); - if (e) - refcount_inc(&e->users); - read_unlock(&misc->entries_lock); - return e; + struct binfmt_misc_entry *e = container_of(rcu, struct binfmt_misc_entry, rcu); + + /* No walker that could sleep in the handler's programs is left. */ + if (e->bpf_ops) + binfmt_misc_put_ops(e->bpf_ops); + kfree(e); } /** - * put_binfmt_handler - put binary handler node - * @e: node to put + * put_binfmt_handler - put binary handler entry + * @e: entry to put * - * Free node syncing with load_misc_binary() and defer final free to + * Free entry syncing with load_misc_binary() and defer final free to * load_misc_binary() in case it is using the binary type handler we were * requested to remove. */ -static void put_binfmt_handler(Node *e) +static void put_binfmt_handler(struct binfmt_misc_entry *e) { if (refcount_dec_and_test(&e->users)) { - if (e->flags & MISC_FMT_OPEN_FILE) + if (e->flags & MISC_FMT_OPEN_FILE) { + exe_file_allow_write_access(e->interp_file); filp_close(e->interp_file, NULL); - kfree(e); + } + /* Walkers may still dereference this entry, even sleeping. */ + call_srcu(&bm_entries_srcu, &e->rcu, bm_entry_free_rcu); } } +DEFINE_FREE(put_binfmt_handler, struct binfmt_misc_entry *, if (_T) put_binfmt_handler(_T)) + /** - * load_binfmt_misc - load the binfmt_misc of the caller's user namespace + * current_binfmt_misc - get the binfmt_misc instance of the caller's user namespace * - * To be called in load_misc_binary() to load the relevant struct binfmt_misc. - * If a user namespace doesn't have its own binfmt_misc mount it can make use - * of its ancestor's binfmt_misc handlers. This mimicks the behavior of - * pre-namespaced binfmt_misc where all registered binfmt_misc handlers where - * available to all user and user namespaces on the system. + * If a user namespace doesn't have its own binfmt_misc mount it uses the + * handlers of its closest ancestor with one. This mimics the behavior of + * pre-namespaced binfmt_misc where all registered handlers were available + * to all users and user namespaces on the system. The init user namespace + * instance is statically set up so the fallback is never reached in + * practice. * * Return: the binfmt_misc instance of the caller's user namespace */ -static struct binfmt_misc *load_binfmt_misc(void) +static struct binfmt_misc *current_binfmt_misc(void) { const struct user_namespace *user_ns; struct binfmt_misc *misc; - user_ns = current_user_ns(); - while (user_ns) { + for (user_ns = current_user_ns(); user_ns; user_ns = user_ns->parent) { /* Pairs with smp_store_release() in bm_fill_super(). */ misc = smp_load_acquire(&user_ns->binfmt_misc); if (misc) return misc; - - user_ns = user_ns->parent; } return &init_binfmt_misc; } +/** + * entry_select_interpreter - get the interpreter for the matched @e + * @e: matched binary type handler + * @bprm: binary that is being executed + * + * A static entry carries its interpreter path, for a 'B' entry the + * handler's load program selects it. The match is committed, so a failing + * program fails the exec. + * + * Return: the interpreter on success, an ERR_PTR on failure + */ +static const char *entry_select_interpreter(const struct binfmt_misc_entry *e, + struct linux_binprm *bprm) +{ + int retval; + + if (!test_bit(MISC_FMT_BPF_BIT, &e->flags)) + return e->interpreter; + + /* Drop any interpreter or flags a previous chain level staged. */ + kfree(bprm->bpf_interp); + bprm->bpf_interp = NULL; + bprm->bpf_flags = 0; + + retval = e->bpf_ops->load(bprm); + if (retval) { + /* Keep a program-supplied error within errno range. */ + if (retval > 0 || retval < -MAX_ERRNO) + retval = -ENOEXEC; + goto drop_staged; + } + + /* Selecting an interpreter is part of the contract. */ + if (!bprm->bpf_interp) { + retval = -ENOEXEC; + goto drop_staged; + } + + return bprm->bpf_interp; + +drop_staged: + /* A failing load leaves nothing behind for later entries. */ + kfree(bprm->bpf_interp_arg); + bprm->bpf_interp_arg = NULL; + bprm->bpf_flags = 0; + return ERR_PTR(retval); +} + /* * the loader itself */ static int load_misc_binary(struct linux_binprm *bprm) { - Node *fmt; - struct file *interp_file = NULL; - int retval = -ENOEXEC; + struct binfmt_misc_entry *fmt __free(put_binfmt_handler) = NULL; + const char *interpreter; + struct file *interp_file; struct binfmt_misc *misc; + bool preserve_argv0, want_execfd, want_creds; + int retval; - misc = load_binfmt_misc(); - if (!misc->enabled) - return retval; + misc = current_binfmt_misc(); + if (!READ_ONCE(misc->enabled)) + return -ENOEXEC; fmt = get_binfmt_handler(misc, bprm); if (!fmt) - return retval; + return -ENOEXEC; /* Need to be able to load the file after exec */ - retval = -ENOENT; if (bprm->interp_flags & BINPRM_FLAGS_PATH_INACCESSIBLE) - goto ret; + return -ENOENT; + + interpreter = entry_select_interpreter(fmt, bprm); + if (IS_ERR(interpreter)) + return PTR_ERR(interpreter); + + /* + * The invocation flags are fixed at registration for a static handler + * and chosen per exec by the load program, via bpf_binprm_set_flags(), + * for a bpf one. + */ + if (test_bit(MISC_FMT_BPF_BIT, &fmt->flags)) { + u64 f = bprm->bpf_flags; - if (fmt->flags & MISC_FMT_PRESERVE_ARGV0) { + /* Clear so it can't accumulate into a nested interpreter level. */ + bprm->bpf_flags = 0; + + preserve_argv0 = f & BPF_BINPRM_PRESERVE_ARGV0; + want_creds = f & BPF_BINPRM_CREDENTIALS; + want_execfd = f & (BPF_BINPRM_CREDENTIALS | BPF_BINPRM_EXECFD); + } else { + preserve_argv0 = fmt->flags & MISC_FMT_PRESERVE_ARGV0; + want_creds = fmt->flags & MISC_FMT_CREDENTIALS; + want_execfd = fmt->flags & MISC_FMT_OPEN_BINARY; + } + + /* The entry's own choice - not one accumulated from an earlier level. */ + if (preserve_argv0) { bprm->interp_flags |= BINPRM_FLAGS_PRESERVE_ARGV0; } else { retval = remove_arg_zero(bprm); if (retval) - goto ret; + return retval; } - /* make argv[1] be the path to the binary */ + /* make the binary the last argument to the interpreter */ retval = copy_string_kernel(bprm->interp, bprm); if (retval < 0) - goto ret; + return retval; bprm->argc++; + /* + * A single optional argument to the interpreter, inserted between it + * and the binary just like the argument of a #! interpreter line. + */ + if (bprm->bpf_interp_arg) { + retval = copy_string_kernel(bprm->bpf_interp_arg, bprm); + if (retval < 0) + return retval; + bprm->argc++; + /* Consumed - don't let it leak into a nested interpreter's argv. */ + kfree(bprm->bpf_interp_arg); + bprm->bpf_interp_arg = NULL; + } + /* add the interp as argv[0] */ - retval = copy_string_kernel(fmt->interpreter, bprm); + retval = copy_string_kernel(interpreter, bprm); if (retval < 0) - goto ret; + return retval; bprm->argc++; /* Update interp in case binfmt_script needs it. */ - retval = bprm_change_interp(fmt->interpreter, bprm); + retval = bprm_change_interp(interpreter, bprm); if (retval < 0) - goto ret; + return retval; if (fmt->flags & MISC_FMT_OPEN_FILE) { interp_file = file_clone_open(fmt->interp_file); - if (!IS_ERR(interp_file)) - deny_write_access(interp_file); + if (!IS_ERR(interp_file)) { + int err = exe_file_deny_write_access(interp_file); + + if (err) { + fput(interp_file); + interp_file = ERR_PTR(err); + } + } } else { - interp_file = open_exec(fmt->interpreter); + interp_file = open_exec(interpreter); } - retval = PTR_ERR(interp_file); if (IS_ERR(interp_file)) - goto ret; + return PTR_ERR(interp_file); bprm->interpreter = interp_file; - if (fmt->flags & MISC_FMT_OPEN_BINARY) + if (want_execfd) bprm->have_execfd = 1; - if (fmt->flags & MISC_FMT_CREDENTIALS) + if (want_creds) bprm->execfd_creds = 1; - - retval = 0; -ret: - - /* - * If we actually put the node here all concurrent calls to - * load_misc_binary() will have finished. We also know - * that for the refcount to be zero someone must have concurently - * removed the binary type handler from the list and it's our job to - * free it. - */ - put_binfmt_handler(fmt); - - return retval; + return 0; } /* Command parsers */ /* - * parses and copies one argument enclosed in del from *sp to *dp, - * recognising the \x special. - * returns pointer to the copied argument or NULL in case of an - * error (and sets err) or null argument length. + * Scan the argument starting at @s up to the delimiter @del, recognising + * the \x escape. Terminates the argument with a NUL and returns a pointer + * past it or NULL on a malformed escape. */ static char *scanarg(char *s, char del) { @@ -298,45 +420,143 @@ static char *scanarg(char *s, char del) return NULL; } } - s[-1] ='\0'; + s[-1] = '\0'; return s; } -static char *check_special_flags(char *sfs, Node *e) +static char *check_special_flags(char *p, struct binfmt_misc_entry *e) { - char *p = sfs; - int cont = 1; - - /* special flags */ - while (cont) { + for (;; p++) { switch (*p) { case 'P': pr_debug("register: flag: P (preserve argv0)\n"); - p++; e->flags |= MISC_FMT_PRESERVE_ARGV0; break; case 'O': pr_debug("register: flag: O (open binary)\n"); - p++; e->flags |= MISC_FMT_OPEN_BINARY; break; case 'C': pr_debug("register: flag: C (preserve creds)\n"); - p++; - /* this flags also implies the - open-binary flag */ - e->flags |= (MISC_FMT_CREDENTIALS | - MISC_FMT_OPEN_BINARY); + /* C implies O */ + e->flags |= MISC_FMT_CREDENTIALS | MISC_FMT_OPEN_BINARY; break; case 'F': pr_debug("register: flag: F: open interpreter file now\n"); - p++; e->flags |= MISC_FMT_OPEN_FILE; break; default: - cont = 0; + return p; } } +} + +/* Parse the 'offset', 'magic' and 'mask' fields of an 'M' entry. */ +static char *parse_magic_fields(struct binfmt_misc_entry *e, char *p, char del) +{ + char *s; + + /* Parse the 'offset' field. */ + s = strchr(p, del); + if (!s) + return NULL; + *s = '\0'; + if (p != s) { + if (kstrtoint(p, 10, &e->offset) || e->offset < 0) + return NULL; + } + p = s + 1; + pr_debug("register: offset: %#x\n", e->offset); + + /* Parse the 'magic' field. */ + e->magic = p; + p = scanarg(p, del); + if (!p || !e->magic[0]) + return NULL; + print_hex_dump_debug( + KBUILD_MODNAME ": register: magic[raw]: ", + DUMP_PREFIX_NONE, 16, 1, e->magic, p - e->magic, true); + + /* Parse the 'mask' field. */ + e->mask = p; + p = scanarg(p, del); + if (!p) + return NULL; + if (!e->mask[0]) { + e->mask = NULL; + pr_debug("register: mask[raw]: none\n"); + } else { + print_hex_dump_debug( + KBUILD_MODNAME ": register: mask[raw]: ", + DUMP_PREFIX_NONE, 16, 1, e->mask, p - e->mask, true); + } + + /* + * Decode the magic & mask fields. Note: while we might have accepted + * embedded NUL bytes from above, the unescape helpers will stop at + * the first one they encounter. + */ + e->size = string_unescape_inplace(e->magic, UNESCAPE_HEX); + if (e->mask && string_unescape_inplace(e->mask, UNESCAPE_HEX) != e->size) + return NULL; + if (e->size > BINPRM_BUF_SIZE || BINPRM_BUF_SIZE - e->size < e->offset) + return NULL; + pr_debug("register: magic/mask length: %i\n", e->size); + print_hex_dump_debug( + KBUILD_MODNAME ": register: magic[decoded]: ", + DUMP_PREFIX_NONE, 16, 1, e->magic, e->size, true); + if (e->mask) + print_hex_dump_debug( + KBUILD_MODNAME ": register: mask[decoded]: ", + DUMP_PREFIX_NONE, 16, 1, e->mask, e->size, true); + return p; +} + +/* Parse the 'magic' field of an 'E' entry: the filename extension. */ +static char *parse_extension_fields(struct binfmt_misc_entry *e, char *p, + char del) +{ + /* Skip the 'offset' field. */ + p = strchr(p, del); + if (!p) + return NULL; + *p++ = '\0'; + + /* Parse the 'magic' field. */ + e->magic = p; + p = strchr(p, del); + if (!p) + return NULL; + *p++ = '\0'; + if (!e->magic[0] || strchr(e->magic, '/')) + return NULL; + pr_debug("register: extension: {%s}\n", e->magic); + + /* Skip the 'mask' field. */ + p = strchr(p, del); + if (!p) + return NULL; + *p++ = '\0'; + return p; +} + +/* + * Parse the fields of a 'B' entry: the 'offset', 'magic' and 'mask' fields + * must be empty. The handler name is carried in the 'interpreter' field. + */ +static char *parse_bpf_fields(struct binfmt_misc_entry *e, char *p, char del) +{ + /* The 'offset' field must be empty. */ + if (*p++ != del) + return NULL; + + /* The 'magic' field must be empty. */ + if (*p++ != del) + return NULL; + + /* The 'mask' field must be empty. */ + if (*p++ != del) + return NULL; return p; } @@ -346,50 +566,52 @@ static char *check_special_flags(char *sfs, Node *e) * ':name:type:offset:magic:mask:interpreter:flags' * where the ':' is the IFS, that can be chosen with the first char */ -static Node *create_entry(const char __user *buffer, size_t count) +static struct binfmt_misc_entry *create_entry(const char __user *buffer, + size_t count) { - Node *e; - int memsize, err; + struct binfmt_misc_entry *e __free(kfree) = NULL; char *buf, *p; char del; pr_debug("register: received %zu bytes\n", count); /* some sanity checks */ - err = -EINVAL; if ((count < 11) || (count > MAX_REGISTER_LENGTH)) - goto out; + return ERR_PTR(-EINVAL); - err = -ENOMEM; - memsize = sizeof(Node) + count + 8; - e = kmalloc(memsize, GFP_KERNEL_ACCOUNT); + e = kmalloc(struct_size(e, buf, count + MISC_DELIM_PAD), + GFP_KERNEL_ACCOUNT); if (!e) - goto out; + return ERR_PTR(-ENOMEM); - p = buf = (char *)e + sizeof(Node); + p = buf = e->buf; - memset(e, 0, sizeof(Node)); + memset(e, 0, sizeof(*e)); if (copy_from_user(buf, buffer, count)) - goto efault; + return ERR_PTR(-EFAULT); - del = *p++; /* delimeter */ + del = *p++; /* delimiter */ pr_debug("register: delim: %#x {%c}\n", del, del); + /* A flag-char delimiter runs the flag scan off the buffer. */ + if (del == 'P' || del == 'O' || del == 'C' || del == 'F') + return ERR_PTR(-EINVAL); + /* Pad the buffer with the delim to simplify parsing below. */ - memset(buf + count, del, 8); + memset(buf + count, del, MISC_DELIM_PAD); /* Parse the 'name' field. */ e->name = p; p = strchr(p, del); if (!p) - goto einval; + return ERR_PTR(-EINVAL); *p++ = '\0'; if (!e->name[0] || !strcmp(e->name, ".") || !strcmp(e->name, "..") || strchr(e->name, '/')) - goto einval; + return ERR_PTR(-EINVAL); pr_debug("register: name: {%s}\n", e->name); @@ -397,159 +619,84 @@ static Node *create_entry(const char __user *buffer, size_t count) switch (*p++) { case 'E': pr_debug("register: type: E (extension)\n"); - e->flags = 1 << Enabled; + e->flags = BIT(MISC_FMT_ENABLED_BIT); break; case 'M': pr_debug("register: type: M (magic)\n"); - e->flags = (1 << Enabled) | (1 << Magic); + e->flags = BIT(MISC_FMT_ENABLED_BIT) | BIT(MISC_FMT_MAGIC_BIT); + break; + case 'B': + pr_debug("register: type: B (bpf)\n"); + if (!IS_ENABLED(CONFIG_BINFMT_MISC_BPF)) + return ERR_PTR(-EINVAL); + e->flags = BIT(MISC_FMT_ENABLED_BIT) | BIT(MISC_FMT_BPF_BIT); break; default: - goto einval; + return ERR_PTR(-EINVAL); } if (*p++ != del) - goto einval; - - if (test_bit(Magic, &e->flags)) { - /* Handle the 'M' (magic) format. */ - char *s; - - /* Parse the 'offset' field. */ - s = strchr(p, del); - if (!s) - goto einval; - *s = '\0'; - if (p != s) { - int r = kstrtoint(p, 10, &e->offset); - if (r != 0 || e->offset < 0) - goto einval; - } - p = s; - if (*p++) - goto einval; - pr_debug("register: offset: %#x\n", e->offset); - - /* Parse the 'magic' field. */ - e->magic = p; - p = scanarg(p, del); - if (!p) - goto einval; - if (!e->magic[0]) - goto einval; - if (USE_DEBUG) - print_hex_dump_bytes( - KBUILD_MODNAME ": register: magic[raw]: ", - DUMP_PREFIX_NONE, e->magic, p - e->magic); - - /* Parse the 'mask' field. */ - e->mask = p; - p = scanarg(p, del); - if (!p) - goto einval; - if (!e->mask[0]) { - e->mask = NULL; - pr_debug("register: mask[raw]: none\n"); - } else if (USE_DEBUG) - print_hex_dump_bytes( - KBUILD_MODNAME ": register: mask[raw]: ", - DUMP_PREFIX_NONE, e->mask, p - e->mask); - - /* - * Decode the magic & mask fields. - * Note: while we might have accepted embedded NUL bytes from - * above, the unescape helpers here will stop at the first one - * it encounters. - */ - e->size = string_unescape_inplace(e->magic, UNESCAPE_HEX); - if (e->mask && - string_unescape_inplace(e->mask, UNESCAPE_HEX) != e->size) - goto einval; - if (e->size > BINPRM_BUF_SIZE || - BINPRM_BUF_SIZE - e->size < e->offset) - goto einval; - pr_debug("register: magic/mask length: %i\n", e->size); - if (USE_DEBUG) { - print_hex_dump_bytes( - KBUILD_MODNAME ": register: magic[decoded]: ", - DUMP_PREFIX_NONE, e->magic, e->size); - - if (e->mask) { - int i; - char *masked = kmalloc(e->size, GFP_KERNEL_ACCOUNT); - - print_hex_dump_bytes( - KBUILD_MODNAME ": register: mask[decoded]: ", - DUMP_PREFIX_NONE, e->mask, e->size); - - if (masked) { - for (i = 0; i < e->size; ++i) - masked[i] = e->magic[i] & e->mask[i]; - print_hex_dump_bytes( - KBUILD_MODNAME ": register: magic[masked]: ", - DUMP_PREFIX_NONE, masked, e->size); - - kfree(masked); - } - } - } - } else { - /* Handle the 'E' (extension) format. */ - - /* Skip the 'offset' field. */ - p = strchr(p, del); - if (!p) - goto einval; - *p++ = '\0'; - - /* Parse the 'magic' field. */ - e->magic = p; - p = strchr(p, del); - if (!p) - goto einval; - *p++ = '\0'; - if (!e->magic[0] || strchr(e->magic, '/')) - goto einval; - pr_debug("register: extension: {%s}\n", e->magic); - - /* Skip the 'mask' field. */ - p = strchr(p, del); - if (!p) - goto einval; - *p++ = '\0'; - } + return ERR_PTR(-EINVAL); + + if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) + p = parse_bpf_fields(e, p, del); + else if (test_bit(MISC_FMT_MAGIC_BIT, &e->flags)) + p = parse_magic_fields(e, p, del); + else + p = parse_extension_fields(e, p, del); + if (!p) + return ERR_PTR(-EINVAL); /* Parse the 'interpreter' field. */ e->interpreter = p; p = strchr(p, del); if (!p) - goto einval; + return ERR_PTR(-EINVAL); *p++ = '\0'; - if (!e->interpreter[0]) - goto einval; - pr_debug("register: interpreter: {%s}\n", e->interpreter); + if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) { + /* The 'interpreter' field carries the handler name. */ + e->bpf_ops_name = e->interpreter; + e->interpreter = NULL; + if (!e->bpf_ops_name[0]) + return ERR_PTR(-EINVAL); + pr_debug("register: bpf handler: {%s}\n", e->bpf_ops_name); + } else if (!e->interpreter[0]) { + return ERR_PTR(-EINVAL); + } else { + pr_debug("register: interpreter: {%s}\n", e->interpreter); + } /* Parse the 'flags' field. */ p = check_special_flags(p, e); if (*p == '\n') p++; if (p != buf + count) - goto einval; - - return e; + return ERR_PTR(-EINVAL); -out: - return ERR_PTR(err); + /* + * A bpf handler decides the invocation flags per exec with + * bpf_binprm_set_flags() rather than fixing them at registration, so a + * 'B' entry carries no flags: 'P', 'C' and 'O' become per-exec choices + * and 'F' (pre-open a fixed interpreter) is meaningless for it. + */ + if (test_bit(MISC_FMT_BPF_BIT, &e->flags) && + (e->flags & (MISC_FMT_PRESERVE_ARGV0 | MISC_FMT_OPEN_BINARY | + MISC_FMT_CREDENTIALS | MISC_FMT_OPEN_FILE))) + return ERR_PTR(-EINVAL); -efault: - kfree(e); - return ERR_PTR(-EFAULT); -einval: - kfree(e); - return ERR_PTR(-EINVAL); + return no_free_ptr(e); } +/* Commands accepted by the /status and /<entry> files. */ +enum bm_command { + BM_CMD_IGNORE, /* empty write */ + BM_CMD_DISABLE, /* "0" */ + BM_CMD_ENABLE, /* "1" */ + BM_CMD_REMOVE, /* "-1" */ +}; + /* - * Set status of entry/binfmt_misc: - * '1' enables, '0' disables and '-1' clears entry/binfmt_misc + * Parse what userspace wrote to /status or an entry file: '1' enables, + * '0' disables and '-1' removes the entry or all entries. */ static int parse_command(const char __user *buffer, size_t count) { @@ -560,62 +707,69 @@ static int parse_command(const char __user *buffer, size_t count) if (copy_from_user(s, buffer, count)) return -EFAULT; if (!count) - return 0; + return BM_CMD_IGNORE; if (s[count - 1] == '\n') count--; if (count == 1 && s[0] == '0') - return 1; + return BM_CMD_DISABLE; if (count == 1 && s[0] == '1') - return 2; + return BM_CMD_ENABLE; if (count == 2 && s[0] == '-' && s[1] == '1') - return 3; + return BM_CMD_REMOVE; return -EINVAL; } /* generic stuff */ -static void entry_status(Node *e, char *page) +static void bm_seq_hex(struct seq_file *m, const u8 *data, int size) { - char *dp = page; - const char *status = "disabled"; + for (int i = 0; i < size; i++) + seq_printf(m, "%02x", data[i]); +} - if (test_bit(Enabled, &e->flags)) - status = "enabled"; +static int bm_entry_show(struct seq_file *m, void *unused) +{ + struct binfmt_misc_entry *e = m->private; - if (!VERBOSE_STATUS) { - sprintf(page, "%s\n", status); - return; - } + if (test_bit(MISC_FMT_ENABLED_BIT, &e->flags)) + seq_puts(m, "enabled\n"); + else + seq_puts(m, "disabled\n"); - dp += sprintf(dp, "%s\ninterpreter %s\n", status, e->interpreter); + if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) + seq_printf(m, "bpf %s\n", e->bpf_ops->name); + else + seq_printf(m, "interpreter %s\n", e->interpreter); /* print the special flags */ - dp += sprintf(dp, "flags: "); + seq_puts(m, "flags: "); if (e->flags & MISC_FMT_PRESERVE_ARGV0) - *dp++ = 'P'; + seq_putc(m, 'P'); if (e->flags & MISC_FMT_OPEN_BINARY) - *dp++ = 'O'; + seq_putc(m, 'O'); if (e->flags & MISC_FMT_CREDENTIALS) - *dp++ = 'C'; + seq_putc(m, 'C'); if (e->flags & MISC_FMT_OPEN_FILE) - *dp++ = 'F'; - *dp++ = '\n'; + seq_putc(m, 'F'); + seq_putc(m, '\n'); - if (!test_bit(Magic, &e->flags)) { - sprintf(dp, "extension .%s\n", e->magic); + if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) { + /* The program does the matching. */ + } else if (!test_bit(MISC_FMT_MAGIC_BIT, &e->flags)) { + seq_printf(m, "extension .%s\n", e->magic); } else { - dp += sprintf(dp, "offset %i\nmagic ", e->offset); - dp = bin2hex(dp, e->magic, e->size); + seq_printf(m, "offset %i\nmagic ", e->offset); + bm_seq_hex(m, e->magic, e->size); if (e->mask) { - dp += sprintf(dp, "\nmask "); - dp = bin2hex(dp, e->mask, e->size); + seq_puts(m, "\nmask "); + bm_seq_hex(m, e->mask, e->size); } - *dp++ = '\n'; - *dp = '\0'; + seq_putc(m, '\n'); } + return 0; } -static struct inode *bm_get_inode(struct super_block *sb, int mode) +static struct inode *bm_get_inode(struct super_block *sb, umode_t mode) { struct inode *inode = new_inode(sb); @@ -651,14 +805,14 @@ static struct binfmt_misc *i_binfmt_misc(struct inode *inode) * entry is removed or the filesystem is unmounted and the super block is * shutdown. * - * If the ->evict call was not caused by a super block shutdown but by a write - * to remove the entry or all entries via bm_{entry,status}_write() the entry - * will have already been removed from the list. We keep the list_empty() check - * to make that explicit. + * If the ->evict call was not caused by a super block shutdown but by + * removing the entry via bm_{entry,status}_write() or unlink(2) the entry + * will have already been removed from the list. We keep the hlist_unhashed() + * check to make that explicit. */ static void bm_evict_inode(struct inode *inode) { - Node *e = inode->i_private; + struct binfmt_misc_entry *e = inode->i_private; clear_inode(inode); @@ -666,89 +820,132 @@ static void bm_evict_inode(struct inode *inode) struct binfmt_misc *misc; misc = i_binfmt_misc(inode); - write_lock(&misc->entries_lock); - if (!list_empty(&e->list)) - list_del_init(&e->list); - write_unlock(&misc->entries_lock); + spin_lock(&misc->entries_lock); + if (!hlist_unhashed(&e->node)) + hlist_del_init_rcu(&e->node); + spin_unlock(&misc->entries_lock); put_binfmt_handler(e); } } /** + * unlink_binfmt_handler - unhash a binary type handler + * @misc: handle to binfmt_misc instance + * @e: binary type handler to unhash + * + * Adding and removing entries via bm_{entry,register,status}_write() and + * unlink(2) happens under the exclusively held inode lock of the root + * dentry keeping the list stable for writers. load_misc_binary() walks it + * concurrently under SRCU. The entries_lock is only held around the actual + * unlink to serialize against bm_evict_inode() which unlinks entries + * during umount without holding the root inode lock. + */ +static void unlink_binfmt_handler(struct binfmt_misc *misc, + struct binfmt_misc_entry *e) +{ + spin_lock(&misc->entries_lock); + hlist_del_init_rcu(&e->node); + spin_unlock(&misc->entries_lock); +} + +/** * remove_binfmt_handler - remove a binary type handler * @misc: handle to binfmt_misc instance * @e: binary type handler to remove * * Remove a binary type handler from the list of binary type handlers and - * remove its associated dentry. This is called from - * binfmt_{entry,status}_write(). In the future, we might want to think about - * adding a proper ->unlink() method to binfmt_misc instead of forcing caller's - * to use writes to files in order to delete binary type handlers. But it has - * worked for so long that it's not a pressing issue. + * remove its associated dentry. */ -static void remove_binfmt_handler(struct binfmt_misc *misc, Node *e) +static void remove_binfmt_handler(struct binfmt_misc *misc, + struct binfmt_misc_entry *e) { - write_lock(&misc->entries_lock); - list_del_init(&e->list); - write_unlock(&misc->entries_lock); + unlink_binfmt_handler(misc, e); locked_recursive_removal(e->dentry, NULL); } -/* /<entry> */ +/* Remove @e unless it was already removed. */ +static void bm_remove_entry(struct binfmt_misc_entry *e, struct super_block *sb) +{ + struct inode *root = d_inode(sb->s_root); -static ssize_t -bm_entry_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) + inode_lock_nested(root, I_MUTEX_PARENT); + if (!hlist_unhashed(&e->node)) + remove_binfmt_handler(i_binfmt_misc(root), e); + inode_unlock(root); +} + +/* Remove all entries of the binfmt_misc instance @misc belonging to @sb. */ +static void bm_remove_all_entries(struct binfmt_misc *misc, + struct super_block *sb) { - Node *e = file_inode(file)->i_private; - ssize_t res; - char *page; + struct inode *root = d_inode(sb->s_root); + struct binfmt_misc_entry *e; + struct hlist_node *next; + + inode_lock_nested(root, I_MUTEX_PARENT); + hlist_for_each_entry_safe(e, next, &misc->entries, node) + remove_binfmt_handler(misc, e); + inode_unlock(root); +} - page = kmalloc(PAGE_SIZE, GFP_KERNEL); - if (!page) - return -ENOMEM; +/** + * bm_unlink - remove a binary type handler via unlink(2) + * @dir: inode of the root directory + * @dentry: entry file to remove + * + * Removing the entry file removes its binary type handler, exactly like + * writing -1 to it does. The status and register control files can't be + * removed. The VFS calls this with the root inode lock held which + * serializes against the write based add and remove paths. + */ +static int bm_unlink(struct inode *dir, struct dentry *dentry) +{ + struct binfmt_misc_entry *e = d_inode(dentry)->i_private; + + if (!e) + return -EPERM; + + unlink_binfmt_handler(i_binfmt_misc(dir), e); + return simple_unlink(dir, dentry); +} + +static const struct inode_operations bm_dir_inode_operations = { + .lookup = simple_lookup, + .unlink = bm_unlink, +}; + +/* /<entry> */ - entry_status(e, page); +static int bm_entry_open(struct inode *inode, struct file *file) +{ + int ret; - res = simple_read_from_buffer(buf, nbytes, ppos, page, strlen(page)); + ret = single_open(file, bm_entry_show, inode->i_private); + if (ret) + return ret; - kfree(page); - return res; + /* seq_open() clears FMODE_PWRITE, bm_entry_write() takes any offset */ + if (file->f_mode & FMODE_WRITE) + file->f_mode |= FMODE_PWRITE; + return 0; } static ssize_t bm_entry_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { struct inode *inode = file_inode(file); - Node *e = inode->i_private; + struct binfmt_misc_entry *e = inode->i_private; int res = parse_command(buffer, count); switch (res) { - case 1: - /* Disable this handler. */ - clear_bit(Enabled, &e->flags); + case BM_CMD_DISABLE: + clear_bit(MISC_FMT_ENABLED_BIT, &e->flags); break; - case 2: - /* Enable this handler. */ - set_bit(Enabled, &e->flags); + case BM_CMD_ENABLE: + set_bit(MISC_FMT_ENABLED_BIT, &e->flags); break; - case 3: - /* Delete this handler. */ - inode = d_inode(inode->i_sb->s_root); - inode_lock_nested(inode, I_MUTEX_PARENT); - - /* - * In order to add new element or remove elements from the list - * via bm_{entry,register,status}_write() inode_lock() on the - * root inode must be held. - * The lock is exclusive ensuring that the list can't be - * modified. Only load_misc_binary() can access but does so - * read-only. So we only need to take the write lock when we - * actually remove the entry from the list. - */ - if (!list_empty(&e->list)) - remove_binfmt_handler(i_binfmt_misc(inode), e); - - inode_unlock(inode); + case BM_CMD_REMOVE: + bm_remove_entry(e, inode->i_sb); break; default: return res; @@ -758,15 +955,17 @@ static ssize_t bm_entry_write(struct file *file, const char __user *buffer, } static const struct file_operations bm_entry_operations = { - .read = bm_entry_read, + .open = bm_entry_open, + .read = seq_read, .write = bm_entry_write, - .llseek = default_llseek, + .llseek = seq_lseek, + .release = single_release, }; /* /register */ /* add to filesystem */ -static int add_entry(Node *e, struct super_block *sb) +static int add_entry(struct binfmt_misc_entry *e, struct super_block *sb) { struct dentry *dentry = simple_start_creating(sb->s_root, e->name); struct inode *inode; @@ -788,9 +987,9 @@ static int add_entry(Node *e, struct super_block *sb) d_make_persistent(dentry, inode); misc = i_binfmt_misc(inode); - write_lock(&misc->entries_lock); - list_add(&e->list, &misc->entries); - write_unlock(&misc->entries_lock); + spin_lock(&misc->entries_lock); + hlist_add_head_rcu(&e->node, &misc->entries); + spin_unlock(&misc->entries_lock); simple_done_creating(dentry); return 0; } @@ -798,16 +997,24 @@ static int add_entry(Node *e, struct super_block *sb) static ssize_t bm_register_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { - Node *e; + struct binfmt_misc_entry *e __free(kfree) = NULL; struct super_block *sb = file_inode(file)->i_sb; - int err = 0; struct file *f = NULL; + int err; e = create_entry(buffer, count); - if (IS_ERR(e)) return PTR_ERR(e); + if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) { + e->bpf_ops = binfmt_misc_get_ops(sb->s_user_ns, e->bpf_ops_name); + if (!e->bpf_ops) { + pr_notice("register: no bpf handler named %s\n", + e->bpf_ops_name); + return -ENOENT; + } + } + if (e->flags & MISC_FMT_OPEN_FILE) { /* * Now that we support unprivileged binfmt_misc mounts make @@ -821,7 +1028,6 @@ static ssize_t bm_register_write(struct file *file, const char __user *buffer, if (IS_ERR(f)) { pr_notice("register: failed to install interpreter file %s\n", e->interpreter); - kfree(e); return PTR_ERR(f); } e->interp_file = f; @@ -833,9 +1039,13 @@ static ssize_t bm_register_write(struct file *file, const char __user *buffer, exe_file_allow_write_access(f); filp_close(f, NULL); } - kfree(e); + if (e->bpf_ops) + binfmt_misc_put_ops(e->bpf_ops); return err; } + + /* The entry is owned by its inode now. */ + retain_and_null_ptr(e); return count; } @@ -850,10 +1060,10 @@ static ssize_t bm_status_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) { struct binfmt_misc *misc; - char *s; + const char *s; misc = i_binfmt_misc(file_inode(file)); - s = misc->enabled ? "enabled\n" : "disabled\n"; + s = READ_ONCE(misc->enabled) ? "enabled\n" : "disabled\n"; return simple_read_from_buffer(buf, nbytes, ppos, s, strlen(s)); } @@ -862,37 +1072,17 @@ static ssize_t bm_status_write(struct file *file, const char __user *buffer, { struct binfmt_misc *misc; int res = parse_command(buffer, count); - Node *e, *next; - struct inode *inode; misc = i_binfmt_misc(file_inode(file)); switch (res) { - case 1: - /* Disable all handlers. */ - misc->enabled = false; + case BM_CMD_DISABLE: + WRITE_ONCE(misc->enabled, false); break; - case 2: - /* Enable all handlers. */ - misc->enabled = true; + case BM_CMD_ENABLE: + WRITE_ONCE(misc->enabled, true); break; - case 3: - /* Delete all handlers. */ - inode = d_inode(file_inode(file)->i_sb->s_root); - inode_lock_nested(inode, I_MUTEX_PARENT); - - /* - * In order to add new element or remove elements from the list - * via bm_{entry,register,status}_write() inode_lock() on the - * root inode must be held. - * The lock is exclusive ensuring that the list can't be - * modified. Only load_misc_binary() can access but does so - * read-only. So we only need to take the write lock when we - * actually remove the entry from the list. - */ - list_for_each_entry_safe(e, next, &misc->entries, list) - remove_binfmt_handler(misc, e); - - inode_unlock(inode); + case BM_CMD_REMOVE: + bm_remove_all_entries(misc, file_inode(file)->i_sb); break; default: return res; @@ -917,7 +1107,7 @@ static void bm_put_super(struct super_block *sb) put_user_ns(user_ns); } -static const struct super_operations s_ops = { +static const struct super_operations bm_super_ops = { .statfs = simple_statfs, .evict_inode = bm_evict_inode, .put_super = bm_put_super, @@ -964,10 +1154,10 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc) if (!misc) return -ENOMEM; - INIT_LIST_HEAD(&misc->entries); - rwlock_init(&misc->entries_lock); + INIT_HLIST_HEAD(&misc->entries); + spin_lock_init(&misc->entries_lock); - /* Pairs with smp_load_acquire() in load_binfmt_misc(). */ + /* Pairs with smp_load_acquire() in current_binfmt_misc(). */ smp_store_release(&user_ns->binfmt_misc, misc); } @@ -982,12 +1172,15 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc) * someone mounts binfmt_misc for the first time or again we simply * reset ->enabled to true. */ - misc->enabled = true; + WRITE_ONCE(misc->enabled, true); err = simple_fill_super(sb, BINFMTFS_MAGIC, bm_files); - if (!err) - sb->s_op = &s_ops; - return err; + if (err) + return err; + + sb->s_op = &bm_super_ops; + d_inode(sb->s_root)->i_op = &bm_dir_inode_operations; + return 0; } static void bm_free(struct fs_context *fc) @@ -1038,6 +1231,8 @@ static void __exit exit_misc_binfmt(void) { unregister_binfmt(&misc_format); unregister_filesystem(&bm_fs_type); + /* Flush pending bm_entry_free_rcu() callbacks before the text goes. */ + srcu_barrier(&bm_entries_srcu); } core_initcall(init_misc_binfmt); diff --git a/fs/binfmt_misc_bpf.c b/fs/binfmt_misc_bpf.c new file mode 100644 index 000000000000..65a3b8313fbe --- /dev/null +++ b/fs/binfmt_misc_bpf.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BPF-backed binary type handlers for binfmt_misc. + * + * A handler is a struct binfmt_misc_ops struct_ops map. Loading and + * registering it makes the handler available under its name in the user + * namespace it was registered in. A binfmt_misc 'B' entry activates it: + * + * echo ':entry:B::::<handler-name>:' > <binfmt_misc>/register + */ + +#include <linux/binfmt_misc.h> +#include <linux/binfmts.h> +#include <linux/bpf.h> +#include <linux/bpf_verifier.h> +#include <linux/btf.h> +#include <linux/btf_ids.h> +#include <linux/cred.h> +#include <linux/init.h> +#include <linux/limits.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/string.h> +#include <linux/user_namespace.h> + +struct bm_bpf_ops_reg { + struct list_head list; + const struct binfmt_misc_ops *ops; + struct bpf_link *link; + struct user_namespace *user_ns; +}; + +static DEFINE_SPINLOCK(bm_bpf_ops_lock); +static LIST_HEAD(bm_bpf_ops_list); + +static struct bpf_struct_ops bpf_binfmt_misc_ops; + +static struct bm_bpf_ops_reg *bm_bpf_ops_find(const struct user_namespace *user_ns, + const char *name) +{ + struct bm_bpf_ops_reg *reg; + + lockdep_assert_held(&bm_bpf_ops_lock); + + list_for_each_entry(reg, &bm_bpf_ops_list, list) { + if (reg->user_ns == user_ns && !strcmp(reg->ops->name, name)) + return reg; + } + return NULL; +} + +/** + * binfmt_misc_get_ops - look up a bpf binary type handler by name + * @user_ns: user namespace of the binfmt_misc instance + * @name: name the handler was registered under + * + * Search @user_ns and its ancestors for a handler named @name, mirroring + * the instance lookup in current_binfmt_misc(). The returned handler stays + * callable until binfmt_misc_put_ops() even if the backing struct_ops map + * is detached or deleted in the meantime. + * + * Return: the handler on success, NULL on failure + */ +const struct binfmt_misc_ops *binfmt_misc_get_ops(struct user_namespace *user_ns, + const char *name) +{ + const struct user_namespace *ns; + struct bm_bpf_ops_reg *reg; + + guard(spinlock)(&bm_bpf_ops_lock); + + for (ns = user_ns; ns; ns = ns->parent) { + reg = bm_bpf_ops_find(ns, name); + if (!reg) + continue; + if (!bpf_struct_ops_get(reg->ops)) + return NULL; + return reg->ops; + } + return NULL; +} + +void binfmt_misc_put_ops(const struct binfmt_misc_ops *ops) +{ + bpf_struct_ops_put(ops); +} + +bool bpf_prog_is_binfmt_misc_ops(const struct bpf_prog *prog) +{ + return prog->type == BPF_PROG_TYPE_STRUCT_OPS && + prog->aux->st_ops == &bpf_binfmt_misc_ops; +} + +__bpf_kfunc_start_defs(); + +/** + * bpf_binprm_set_interp - select the interpreter for the current exec + * @bprm: binary that is being executed + * @path: absolute path to the interpreter + * @path__sz: size of the @path buffer, including the terminating NUL + * + * To be called from the load program of a struct binfmt_misc_ops handler + * before returning zero; the verifier rejects the call from any other + * program, including the handler's own match program. The path is opened + * with the credentials of the task doing the exec after the program + * returns. + * + * Return: 0 on success, a negative errno on failure + */ +__bpf_kfunc int bpf_binprm_set_interp(struct linux_binprm *bprm, + const char *path, size_t path__sz) +{ + size_t len; + char *interp; + + if (!path__sz) + return -EINVAL; + len = strnlen(path, path__sz); + if (len == path__sz) + return -EINVAL; + if (path[0] != '/') + return -EINVAL; + if (len >= PATH_MAX) + return -ENAMETOOLONG; + + interp = kmemdup_nul(path, len, GFP_KERNEL); + if (!interp) + return -ENOMEM; + + kfree(bprm->bpf_interp); + bprm->bpf_interp = interp; + return 0; +} + +/** + * bpf_binprm_set_interp_arg - set a single argument for the interpreter + * @bprm: binary that is being executed + * @arg: argument to pass to the interpreter + * @arg__sz: size of the @arg buffer, including the terminating NUL + * + * To be called from the load program of a struct binfmt_misc_ops handler. The + * argument is passed to the interpreter ahead of the binary, mirroring the + * single optional argument of a #! interpreter line. Calling it again + * replaces the argument. + * + * Return: 0 on success, a negative errno on failure + */ +__bpf_kfunc int bpf_binprm_set_interp_arg(struct linux_binprm *bprm, + const char *arg, size_t arg__sz) +{ + size_t len; + char *val; + + if (!arg__sz) + return -EINVAL; + len = strnlen(arg, arg__sz); + if (len == arg__sz) + return -EINVAL; + if (!len) + return -EINVAL; + + val = kmemdup_nul(arg, len, GFP_KERNEL); + if (!val) + return -ENOMEM; + + kfree(bprm->bpf_interp_arg); + bprm->bpf_interp_arg = val; + return 0; +} + +/** + * bpf_binprm_set_flags - choose the interpreter invocation flags for this exec + * @bprm: binary that is being executed + * @flags: an OR of enum bpf_binprm_flags values + * + * To be called from the load program of a struct binfmt_misc_ops handler. It + * decides per exec what a static entry fixes at registration with the P, C and + * O flags: BPF_BINPRM_PRESERVE_ARGV0 keeps the caller's argv[0], + * BPF_BINPRM_CREDENTIALS computes credentials from the binary, and + * BPF_BINPRM_EXECFD hands the binary to the interpreter through AT_EXECFD. + * Calling it again replaces the flags, passing zero clears them again. + * + * Return: 0 on success, -EINVAL if @flags contains an unknown bit + */ +__bpf_kfunc int bpf_binprm_set_flags(struct linux_binprm *bprm, + enum bpf_binprm_flags flags) +{ + if (flags & ~(BPF_BINPRM_PRESERVE_ARGV0 | BPF_BINPRM_CREDENTIALS | + BPF_BINPRM_EXECFD)) + return -EINVAL; + + bprm->bpf_flags = flags; + return 0; +} + +__bpf_kfunc_end_defs(); + +BTF_KFUNCS_START(bm_bpf_kfunc_ids) +BTF_ID_FLAGS(func, bpf_binprm_set_interp, KF_SLEEPABLE) +BTF_ID_FLAGS(func, bpf_binprm_set_interp_arg, KF_SLEEPABLE) +BTF_ID_FLAGS(func, bpf_binprm_set_flags, KF_SLEEPABLE) +BTF_KFUNCS_END(bm_bpf_kfunc_ids) + +static int bm_bpf_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id) +{ + if (!btf_id_set8_contains(&bm_bpf_kfunc_ids, kfunc_id)) + return 0; + if (prog->type != BPF_PROG_TYPE_STRUCT_OPS) + return -EACCES; + /* ->st_ops is unset during the cfg pass; enforced once it is set. */ + if (!prog->aux->st_ops) + return 0; + /* Only the load program decides how a binary is run. */ + if (bpf_prog_is_binfmt_misc_ops(prog) && + prog->aux->attach_st_ops_member_off == offsetof(struct binfmt_misc_ops, load)) + return 0; + return -EACCES; +} + +static const struct btf_kfunc_id_set bm_bpf_kfunc_set = { + .owner = THIS_MODULE, + .set = &bm_bpf_kfunc_ids, + .filter = bm_bpf_kfunc_filter, +}; + +static bool bm_bpf_ops__match(struct linux_binprm *bprm) +{ + return false; +} + +static int bm_bpf_ops__load(struct linux_binprm *bprm) +{ + return 0; +} + +static struct binfmt_misc_ops bm_bpf_ops_stubs = { + .match = bm_bpf_ops__match, + .load = bm_bpf_ops__load, +}; + +static int bm_bpf_init(struct btf *btf) +{ + return register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, + &bm_bpf_kfunc_set); +} + +static int bm_bpf_check_member(const struct btf_type *t, + const struct btf_member *member, + const struct bpf_prog *prog) +{ + u32 moff = __btf_member_bit_offset(t, member) / 8; + + switch (moff) { + case offsetof(struct binfmt_misc_ops, match): + case offsetof(struct binfmt_misc_ops, load): + /* Reliable file reads at exec time require sleeping. */ + if (!prog->sleepable) + return -EINVAL; + break; + } + return 0; +} + +static int bm_bpf_init_member(const struct btf_type *t, + const struct btf_member *member, + void *kdata, const void *udata) +{ + const struct binfmt_misc_ops *uops = udata; + struct binfmt_misc_ops *ops = kdata; + u32 moff = __btf_member_bit_offset(t, member) / 8; + + switch (moff) { + case offsetof(struct binfmt_misc_ops, name): + if (bpf_obj_name_cpy(ops->name, uops->name, + sizeof(ops->name)) <= 0) + return -EINVAL; + return 1; + } + return 0; +} + +static int bm_bpf_validate(void *kdata) +{ + struct binfmt_misc_ops *ops = kdata; + + if (!ops->match || !ops->load) + return -EINVAL; + return 0; +} + +static int bm_bpf_reg(void *kdata, struct bpf_link *link) +{ + struct binfmt_misc_ops *ops = kdata; + struct bm_bpf_ops_reg *reg; + + reg = kzalloc_obj(*reg, GFP_KERNEL_ACCOUNT); + if (!reg) + return -ENOMEM; + + reg->ops = ops; + reg->link = link; + reg->user_ns = get_user_ns(current_user_ns()); + + guard(spinlock)(&bm_bpf_ops_lock); + + if (bm_bpf_ops_find(reg->user_ns, ops->name)) { + put_user_ns(reg->user_ns); + kfree(reg); + return -EEXIST; + } + + list_add(®->list, &bm_bpf_ops_list); + return 0; +} + +static void bm_bpf_unreg(void *kdata, struct bpf_link *link) +{ + struct bm_bpf_ops_reg *reg; + + guard(spinlock)(&bm_bpf_ops_lock); + + list_for_each_entry(reg, &bm_bpf_ops_list, list) { + if (reg->ops == kdata && reg->link == link) { + list_del(®->list); + put_user_ns(reg->user_ns); + kfree(reg); + return; + } + } +} + +static const struct bpf_verifier_ops bm_bpf_verifier_ops = { + .get_func_proto = bpf_base_func_proto, + .is_valid_access = bpf_tracing_btf_ctx_access, +}; + +static struct bpf_struct_ops bpf_binfmt_misc_ops = { + .verifier_ops = &bm_bpf_verifier_ops, + .init = bm_bpf_init, + .check_member = bm_bpf_check_member, + .init_member = bm_bpf_init_member, + .validate = bm_bpf_validate, + .reg = bm_bpf_reg, + .unreg = bm_bpf_unreg, + .cfi_stubs = &bm_bpf_ops_stubs, + .name = "binfmt_misc_ops", + .owner = THIS_MODULE, +}; + +static int __init bm_bpf_struct_ops_init(void) +{ + return register_bpf_struct_ops(&bpf_binfmt_misc_ops, binfmt_misc_ops); +} +late_initcall(bm_bpf_struct_ops_init); diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c index f1863a891db6..6cb877267978 100644 --- a/fs/bpf_fs_kfuncs.c +++ b/fs/bpf_fs_kfuncs.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2024 Google LLC. */ +#include <linux/binfmt_misc.h> #include <linux/bpf.h> #include <linux/bpf_lsm.h> #include <linux/btf.h> @@ -11,6 +12,7 @@ #include <linux/file.h> #include <linux/kernfs.h> #include <linux/mm.h> +#include <linux/net.h> #include <linux/xattr.h> __bpf_kfunc_start_defs(); @@ -359,6 +361,39 @@ __bpf_kfunc int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__s } #endif /* CONFIG_CGROUPS */ +#ifdef CONFIG_NET +/** + * bpf_sock_read_xattr - read xattr of a socket's inode in sockfs + * @sock: socket to get xattr from + * @name__str: name of the xattr + * @value_p: output buffer of the xattr value + * + * Get xattr *name__str* of *sock* and store the output in *value_p*. + * + * For security reasons, only *name__str* with prefix "user." is allowed. + * + * Return: length of the xattr value on success, a negative value on error. + */ +__bpf_kfunc int bpf_sock_read_xattr(struct socket *sock, const char *name__str, + struct bpf_dynptr *value_p) +{ + struct bpf_dynptr_kern *value_ptr = (struct bpf_dynptr_kern *)value_p; + u32 value_len; + void *value; + + /* Only allow reading "user.*" xattrs */ + if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) + return -EPERM; + + value_len = __bpf_dynptr_size(value_ptr); + value = __bpf_dynptr_data_rw(value_ptr, value_len); + if (!value) + return -EINVAL; + + return sock_read_xattr(sock, name__str, value, value_len); +} +#endif /* CONFIG_NET */ + /** * bpf_real_data_inode - get the real inode hosting a file's data * @file: file to resolve @@ -390,12 +425,30 @@ BTF_ID_FLAGS(func, bpf_get_file_xattr, KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_set_dentry_xattr, KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_remove_dentry_xattr, KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_real_data_inode, KF_SLEEPABLE | KF_RET_NULL) +#ifdef CONFIG_NET +BTF_ID_FLAGS(func, bpf_sock_read_xattr, KF_RCU) +#endif BTF_KFUNCS_END(bpf_fs_kfunc_set_ids) +/* Side-effecting kfuncs that stay exclusive to LSM programs. */ +BTF_SET_START(bpf_fs_kfunc_lsm_only_ids) +BTF_ID(func, bpf_set_dentry_xattr) +BTF_ID(func, bpf_remove_dentry_xattr) +BTF_SET_END(bpf_fs_kfunc_lsm_only_ids) + static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id) { - if (!btf_id_set8_contains(&bpf_fs_kfunc_set_ids, kfunc_id) || - prog->type == BPF_PROG_TYPE_LSM) + if (!btf_id_set8_contains(&bpf_fs_kfunc_set_ids, kfunc_id)) + return 0; + if (prog->type == BPF_PROG_TYPE_LSM) + return 0; + if (prog->type != BPF_PROG_TYPE_STRUCT_OPS) + return -EACCES; + /* ->st_ops is unset during the cfg pass; enforced once it is set. */ + if (!prog->aux->st_ops) + return 0; + if (bpf_prog_is_binfmt_misc_ops(prog) && + !btf_id_set_contains(&bpf_fs_kfunc_lsm_only_ids, kfunc_id)) return 0; return -EACCES; } @@ -438,7 +491,13 @@ static const struct btf_kfunc_id_set bpf_fs_kfunc_set = { static int __init bpf_fs_kfuncs_init(void) { - return register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, &bpf_fs_kfunc_set); + int ret; + + ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, &bpf_fs_kfunc_set); + if (ret || !IS_ENABLED(CONFIG_BINFMT_MISC_BPF)) + return ret; + return register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, + &bpf_fs_kfunc_set); } late_initcall(bpf_fs_kfuncs_init); diff --git a/fs/btrfs/dev-replace.c b/fs/btrfs/dev-replace.c index 318ddb790429..dc0834f920c3 100644 --- a/fs/btrfs/dev-replace.c +++ b/fs/btrfs/dev-replace.c @@ -247,8 +247,8 @@ static int btrfs_init_dev_replace_tgtdev(struct btrfs_fs_info *fs_info, return -EINVAL; } - bdev_file = bdev_file_open_by_path(device_path, BLK_OPEN_WRITE, - fs_info->sb, &fs_holder_ops); + /* Unfreezable for the whole replace; see btrfs_dev_replace_start(). */ + bdev_file = btrfs_open_device_deny_freeze(device_path, fs_info->sb); if (IS_ERR(bdev_file)) { btrfs_err(fs_info, "target device %s is invalid!", device_path); return PTR_ERR(bdev_file); @@ -327,7 +327,8 @@ static int btrfs_init_dev_replace_tgtdev(struct btrfs_fs_info *fs_info, return 0; error: - bdev_fput(bdev_file); + /* Undo the open-time freeze deny. */ + btrfs_release_device_allow_freeze(bdev_file); return ret; } @@ -624,6 +625,15 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info, if (ret) return ret; + /* Deny the source before mark, so every 'leave' unwinds both denied. */ + if (src_device->bdev) { + ret = bdev_deny_freeze(src_device->bdev); + if (ret) { + btrfs_destroy_dev_replace_tgtdev(tgt_device, true); + return ret; + } + } + ret = mark_block_group_to_copy(fs_info, src_device); if (ret) return ret; @@ -708,7 +718,9 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info, return ret; leave: - btrfs_destroy_dev_replace_tgtdev(tgt_device); + if (src_device->bdev) + bdev_allow_freeze(src_device->bdev); + btrfs_destroy_dev_replace_tgtdev(tgt_device, true); return ret; } @@ -889,6 +901,7 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info, */ ret = btrfs_start_delalloc_roots(fs_info, LONG_MAX, false); if (ret) { + /* Stays started/resumable; keep both denied. */ mutex_unlock(&dev_replace->lock_finishing_cancel_unmount); return ret; } @@ -902,6 +915,7 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info, while (1) { trans = btrfs_start_transaction(root, 0); if (IS_ERR(trans)) { + /* Stays started/resumable; keep both denied. */ mutex_unlock(&dev_replace->lock_finishing_cancel_unmount); return PTR_ERR(trans); } @@ -954,7 +968,10 @@ error: mutex_unlock(&fs_devices->device_list_mutex); btrfs_rm_dev_replace_blocked(fs_info); if (tgt_device) - btrfs_destroy_dev_replace_tgtdev(tgt_device); + btrfs_destroy_dev_replace_tgtdev(tgt_device, true); + /* The source stays a member; re-allow freezing it. */ + if (src_device->bdev) + bdev_allow_freeze(src_device->bdev); btrfs_rm_dev_replace_unblocked(fs_info); mutex_unlock(&dev_replace->lock_finishing_cancel_unmount); @@ -1027,6 +1044,8 @@ error: mutex_unlock(&dev_replace->lock_finishing_cancel_unmount); + /* The target is now a member; the source is freed (allow + release). */ + bdev_allow_freeze(tgt_device->bdev); btrfs_rm_dev_replace_free_srcdev(src_device); return 0; @@ -1155,8 +1174,9 @@ int btrfs_dev_replace_cancel(struct btrfs_fs_info *fs_info) btrfs_dev_name(src_device), src_device->devid, btrfs_dev_name(tgt_device)); + /* A suspended replace never re-denied freezing; do not allow. */ if (tgt_device) - btrfs_destroy_dev_replace_tgtdev(tgt_device); + btrfs_destroy_dev_replace_tgtdev(tgt_device, false); break; default: up_write(&dev_replace->rwsem); @@ -1186,6 +1206,11 @@ void btrfs_dev_replace_suspend_for_unmount(struct btrfs_fs_info *fs_info) dev_replace->time_stopped = ktime_get_real_seconds(); dev_replace->item_needs_writeback = 1; btrfs_info(fs_info, "suspending dev_replace for unmount"); + /* Reopened freezable next mount; resume re-denies. */ + if (dev_replace->srcdev && dev_replace->srcdev->bdev) + bdev_allow_freeze(dev_replace->srcdev->bdev); + if (dev_replace->tgtdev && dev_replace->tgtdev->bdev) + bdev_allow_freeze(dev_replace->tgtdev->bdev); break; } @@ -1198,6 +1223,7 @@ int btrfs_resume_dev_replace_async(struct btrfs_fs_info *fs_info) { struct task_struct *task; struct btrfs_dev_replace *dev_replace = &fs_info->dev_replace; + int ret = 0; down_write(&dev_replace->rwsem); @@ -1241,8 +1267,33 @@ int btrfs_resume_dev_replace_async(struct btrfs_fs_info *fs_info) return 0; } + /* Re-deny for the resumed replace; stay suspended if frozen now. */ + if (dev_replace->srcdev->bdev && + bdev_deny_freeze(dev_replace->srcdev->bdev)) + goto suspend; + if (bdev_deny_freeze(dev_replace->tgtdev->bdev)) { + if (dev_replace->srcdev->bdev) + bdev_allow_freeze(dev_replace->srcdev->bdev); + goto suspend; + } + task = kthread_run(btrfs_dev_replace_kthread, fs_info, "btrfs-devrepl"); - return PTR_ERR_OR_ZERO(task); + if (IS_ERR(task)) { + bdev_allow_freeze(dev_replace->tgtdev->bdev); + if (dev_replace->srcdev->bdev) + bdev_allow_freeze(dev_replace->srcdev->bdev); + /* Undo the deny and suspend, but still fail the mount. */ + ret = PTR_ERR(task); + goto suspend; + } + return 0; + +suspend: + btrfs_exclop_finish(fs_info); + down_write(&dev_replace->rwsem); + dev_replace->replace_state = BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED; + up_write(&dev_replace->rwsem); + return ret; } static int btrfs_dev_replace_kthread(void *data) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 636196705fa3..0fcbfc393946 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -6843,7 +6843,7 @@ static int btrfs_mknod(struct mnt_idmap *idmap, struct inode *dir, } static int btrfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode; @@ -6947,7 +6947,7 @@ static struct dentry *btrfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, inode = new_inode(dir->i_sb); if (!inode) return ERR_PTR(-ENOMEM); - inode_init_owner(idmap, inode, dir, S_IFDIR | mode); + inode_init_owner(idmap, inode, dir, mode); inode->i_op = &btrfs_dir_inode_operations; inode->i_fop = &btrfs_dir_file_operations; return ERR_PTR(btrfs_create_common(dir, dentry, inode)); diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 5cb927c6e53d..07ce84070efd 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2627,7 +2627,7 @@ static long btrfs_ioctl_rm_dev_v2(struct file *file, void __user *arg) err_drop: mnt_drop_write_file(file); if (bdev_file) - bdev_fput(bdev_file); + btrfs_release_device_allow_freeze(bdev_file); out: btrfs_put_dev_args_from_path(&args); return ret; @@ -2677,7 +2677,7 @@ static long btrfs_ioctl_rm_dev(struct file *file, void __user *arg) mnt_drop_write_file(file); if (bdev_file) - bdev_fput(bdev_file); + btrfs_release_device_allow_freeze(bdev_file); out: btrfs_put_dev_args_from_path(&args); return ret; diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index a8e27db8e4bc..9b66eb584ece 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -12,6 +12,7 @@ #include <linux/uuid.h> #include <linux/list_sort.h> #include <linux/namei.h> +#include <linux/fs_struct.h> #include "misc.h" #include "disk-io.h" #include "extent-tree.h" @@ -480,7 +481,12 @@ btrfs_get_bdev_and_sb(const char *device_path, blk_mode_t flags, void *holder, struct block_device *bdev; int ret; - *bdev_file = bdev_file_open_by_path(device_path, flags, holder, &fs_holder_ops); + if (holder) + *bdev_file = fs_bdev_file_open_by_path(device_path, flags, + holder, holder); + else + *bdev_file = bdev_file_open_by_path(device_path, flags, NULL, + NULL); if (IS_ERR(*bdev_file)) { ret = PTR_ERR(*bdev_file); @@ -495,7 +501,7 @@ btrfs_get_bdev_and_sb(const char *device_path, blk_mode_t flags, void *holder, if (holder) { ret = set_blocksize(*bdev_file, BTRFS_BDEV_BLOCKSIZE); if (ret) { - bdev_fput(*bdev_file); + fs_bdev_file_release(*bdev_file, holder); goto error; } } @@ -503,7 +509,10 @@ btrfs_get_bdev_and_sb(const char *device_path, blk_mode_t flags, void *holder, *disk_super = btrfs_read_disk_super(bdev, 0, false); if (IS_ERR(*disk_super)) { ret = PTR_ERR(*disk_super); - bdev_fput(*bdev_file); + if (holder) + fs_bdev_file_release(*bdev_file, holder); + else + bdev_fput(*bdev_file); goto error; } @@ -727,7 +736,7 @@ static int btrfs_open_one_device(struct btrfs_fs_devices *fs_devices, error_free_page: btrfs_release_disk_super(disk_super); - bdev_fput(bdev_file); + fs_bdev_file_release(bdev_file, holder); return -EINVAL; } @@ -1052,7 +1061,7 @@ static void __btrfs_free_extra_devids(struct btrfs_fs_devices *fs_devices, continue; if (device->bdev_file) { - bdev_fput(device->bdev_file); + fs_bdev_file_release(device->bdev_file, device->bdev_file->private_data); device->bdev = NULL; device->bdev_file = NULL; fs_devices->open_devices--; @@ -1089,7 +1098,18 @@ void btrfs_free_extra_devids(struct btrfs_fs_devices *fs_devices) mutex_unlock(&uuid_mutex); } -static void btrfs_close_bdev(struct btrfs_device *device) +/* Release a device that was made unfreezable for a membership change. */ +void btrfs_release_device_allow_freeze(struct file *bdev_file) +{ + struct super_block *sb = bdev_file->private_data; + + /* Unregister before re-allowing (strand-safe); file still open (UAF-safe). */ + fs_bdev_unregister(bdev_file, sb); + bdev_allow_freeze(file_bdev(bdev_file)); + bdev_fput(bdev_file); +} + +static void btrfs_close_bdev(struct btrfs_device *device, bool allow_freeze) { if (!device->bdev) return; @@ -1099,7 +1119,12 @@ static void btrfs_close_bdev(struct btrfs_device *device) invalidate_bdev(device->bdev); } - bdev_fput(device->bdev_file); + /* @allow_freeze undoes a replace-time deny; unmount-close was never denied. */ + if (allow_freeze) + btrfs_release_device_allow_freeze(device->bdev_file); + else + fs_bdev_file_release(device->bdev_file, + device->bdev_file->private_data); } static void btrfs_close_one_device(struct btrfs_device *device) @@ -1120,7 +1145,7 @@ static void btrfs_close_one_device(struct btrfs_device *device) fs_devices->missing_devices--; } - btrfs_close_bdev(device); + btrfs_close_bdev(device, false); if (device->bdev) { fs_devices->open_devices--; device->bdev = NULL; @@ -2090,8 +2115,16 @@ static int btrfs_add_dev_item(struct btrfs_trans_handle *trans, static void update_dev_time(const char *device_path) { struct path path; + int err; + + if (tsk_is_kthread(current)) { + scoped_with_init_fs() + err = kern_path(device_path, LOOKUP_FOLLOW, &path); + } else { + err = kern_path(device_path, LOOKUP_FOLLOW, &path); + } - if (!kern_path(device_path, LOOKUP_FOLLOW, &path)) { + if (!err) { vfs_utimes(&path, NULL); path_put(&path); } @@ -2338,6 +2371,13 @@ int btrfs_rm_device(struct btrfs_fs_info *fs_info, fs_info->fs_devices->rw_devices == 1) return BTRFS_ERROR_DEV_ONLY_WRITABLE; + /* Removal and freezing are mutually exclusive; refuse if frozen now. */ + if (device->bdev) { + ret = bdev_deny_freeze(device->bdev); + if (ret) + return ret; + } + if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) { mutex_lock(&fs_info->chunk_mutex); list_del_init(&device->dev_alloc_list); @@ -2364,6 +2404,8 @@ int btrfs_rm_device(struct btrfs_fs_info *fs_info, device->devid, ret); btrfs_abort_transaction(trans, ret); btrfs_end_transaction(trans); + if (device->bdev) + bdev_allow_freeze(device->bdev); return ret; } @@ -2455,6 +2497,8 @@ int btrfs_rm_device(struct btrfs_fs_info *fs_info, return btrfs_commit_transaction(trans); error_undo: + if (device->bdev) + bdev_allow_freeze(device->bdev); if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) { mutex_lock(&fs_info->chunk_mutex); list_add(&device->dev_alloc_list, @@ -2499,7 +2543,8 @@ void btrfs_rm_dev_replace_free_srcdev(struct btrfs_device *srcdev) mutex_lock(&uuid_mutex); - btrfs_close_bdev(srcdev); + /* The source was made unfreezable for the replace; undo it. */ + btrfs_close_bdev(srcdev, true); synchronize_rcu(); btrfs_free_device(srcdev); @@ -2520,7 +2565,8 @@ void btrfs_rm_dev_replace_free_srcdev(struct btrfs_device *srcdev) mutex_unlock(&uuid_mutex); } -void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev) +void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev, + bool allow_freeze) { struct btrfs_fs_devices *fs_devices = tgtdev->fs_info->fs_devices; @@ -2541,7 +2587,7 @@ void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev) btrfs_scratch_superblocks(tgtdev->fs_info, tgtdev); - btrfs_close_bdev(tgtdev); + btrfs_close_bdev(tgtdev, allow_freeze); synchronize_rcu(); btrfs_free_device(tgtdev); } @@ -2810,6 +2856,37 @@ next_slot: return 0; } +/* + * Open @path for @sb with freezing denied before the holder claim is published, + * so a racing bdev_freeze() can never reach a claim a device add or replace may + * still abort. The deny is taken on a throwaway non-holder probe open, then the + * holder is opened by the probe's dev_t. Balanced by the caller. + */ +struct file *btrfs_open_device_deny_freeze(const char *path, + struct super_block *sb) +{ + struct file *probe_file, *bdev_file; + int ret; + + /* WRITE so bdev_file_open_by_path() rejects a read-only device. */ + probe_file = bdev_file_open_by_path(path, BLK_OPEN_WRITE, NULL, NULL); + if (IS_ERR(probe_file)) + return probe_file; + + ret = bdev_deny_freeze(file_bdev(probe_file)); + if (ret) { + bdev_fput(probe_file); + return ERR_PTR(ret); + } + + bdev_file = fs_bdev_file_open_by_dev(file_bdev(probe_file)->bd_dev, + BLK_OPEN_WRITE, sb, sb); + if (IS_ERR(bdev_file)) + bdev_allow_freeze(file_bdev(probe_file)); + bdev_fput(probe_file); + return bdev_file; +} + int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path) { struct btrfs_root *root = fs_info->dev_root; @@ -2828,8 +2905,8 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path if (sb_rdonly(sb) && !fs_devices->seeding) return -EROFS; - bdev_file = bdev_file_open_by_path(device_path, BLK_OPEN_WRITE, - fs_info->sb, &fs_holder_ops); + /* Forbid freezing until the device is a committed member (or unwound). */ + bdev_file = btrfs_open_device_deny_freeze(device_path, fs_info->sb); if (IS_ERR(bdev_file)) return PTR_ERR(bdev_file); @@ -3000,8 +3077,10 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path up_write(&sb->s_umount); locked = false; - if (ret) /* transaction commit */ + if (ret) { /* transaction commit */ + bdev_allow_freeze(file_bdev(bdev_file)); return ret; + } ret = btrfs_relocate_sys_chunks(fs_info); if (ret < 0) @@ -3009,8 +3088,10 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path "Failed to relocate sys chunks after device initialization. This can be fixed using the \"btrfs balance\" command."); trans = btrfs_attach_transaction(root); if (IS_ERR(trans)) { - if (PTR_ERR(trans) == -ENOENT) + if (PTR_ERR(trans) == -ENOENT) { + bdev_allow_freeze(file_bdev(bdev_file)); return 0; + } ret = PTR_ERR(trans); trans = NULL; goto error_sysfs; @@ -3030,6 +3111,7 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path /* Update ctime/mtime for blkid or udev */ update_dev_time(device_path); + bdev_allow_freeze(file_bdev(bdev_file)); return ret; error_sysfs: @@ -3059,7 +3141,7 @@ error_free_zone: error_free_device: btrfs_free_device(device); error: - bdev_fput(bdev_file); + btrfs_release_device_allow_freeze(bdev_file); if (locked) { mutex_unlock(&uuid_mutex); up_write(&sb->s_umount); diff --git a/fs/btrfs/volumes.h b/fs/btrfs/volumes.h index eaf23c0dcbf6..0415d74cad9b 100644 --- a/fs/btrfs/volumes.h +++ b/fs/btrfs/volumes.h @@ -750,6 +750,7 @@ int btrfs_open_devices(struct btrfs_fs_devices *fs_devices, struct btrfs_device *btrfs_scan_one_device(const char *path, bool mount_arg_dev); int btrfs_forget_devices(dev_t devt); void btrfs_close_devices(struct btrfs_fs_devices *fs_devices); +void btrfs_release_device_allow_freeze(struct file *bdev_file); void btrfs_free_extra_devids(struct btrfs_fs_devices *fs_devices); void btrfs_assign_next_active_device(struct btrfs_device *device, struct btrfs_device *this_dev); @@ -774,6 +775,8 @@ struct btrfs_device *btrfs_find_device(const struct btrfs_fs_devices *fs_devices const struct btrfs_dev_lookup_args *args); int btrfs_shrink_device(struct btrfs_device *device, u64 new_size); int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *path); +struct file *btrfs_open_device_deny_freeze(const char *path, + struct super_block *sb); int btrfs_balance(struct btrfs_fs_info *fs_info, struct btrfs_balance_control *bctl, struct btrfs_ioctl_balance_args *bargs); @@ -794,7 +797,8 @@ int btrfs_init_writeback_bio_size(struct btrfs_fs_info *fs_info); int btrfs_run_dev_stats(struct btrfs_trans_handle *trans); void btrfs_rm_dev_replace_remove_srcdev(struct btrfs_device *srcdev); void btrfs_rm_dev_replace_free_srcdev(struct btrfs_device *srcdev); -void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev); +void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev, + bool allow_freeze); unsigned long btrfs_full_stripe_len(struct btrfs_fs_info *fs_info, u64 logical); u64 btrfs_calc_stripe_length(const struct btrfs_chunk_map *map); diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index ef9e92e362d3..dfa590a220ea 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c @@ -983,7 +983,7 @@ out: } static int ceph_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return ceph_mknod(idmap, dir, dentry, mode, 0); } @@ -1147,7 +1147,6 @@ static struct dentry *ceph_mkdir(struct mnt_idmap *idmap, struct inode *dir, goto out; } - mode |= S_IFDIR; req->r_new_inode = ceph_new_inode(dir, dentry, &mode, &as_ctx); if (IS_ERR(req->r_new_inode)) { ret = ERR_CAST(req->r_new_inode); diff --git a/fs/coda/dir.c b/fs/coda/dir.c index 835eb7fdfdad..67148edfadee 100644 --- a/fs/coda/dir.c +++ b/fs/coda/dir.c @@ -134,7 +134,7 @@ static inline void coda_dir_drop_nlink(struct inode *dir) /* creation routines: create, mknod, mkdir, link, symlink */ static int coda_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *de, umode_t mode, bool excl) + struct dentry *de, umode_t mode) { int error; const char *name=de->d_name.name; @@ -179,7 +179,12 @@ static struct dentry *coda_mkdir(struct mnt_idmap *idmap, struct inode *dir, if (is_root_inode(dir) && coda_iscontrol(name, len)) return ERR_PTR(-EPERM); - attrs.va_mode = mode; + /* + * vfs_mkdir() now passes S_IFDIR in @mode, but @mode is forwarded + * verbatim to userspace, which has only ever been given the permission + * bits. Strip the type bit until venus is known to cope with it. + */ + attrs.va_mode = mode & ~S_IFDIR; error = venus_mkdir(dir->i_sb, coda_i2f(dir), name, len, &newfid, &attrs); if (error) diff --git a/fs/coredump.c b/fs/coredump.c index e68a76ff92a3..ac3cd74808c6 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -921,15 +921,10 @@ static bool coredump_file(struct core_name *cn, struct coredump_params *cprm, * with a fully qualified path" rule is to control where * coredumps may be placed using root privileges, * current->fs->root must not be used. Instead, use the - * root directory of init_task. + * root directory of PID 1. */ - struct path root; - - task_lock(&init_task); - get_fs_root(init_task.fs, &root); - task_unlock(&init_task); - file = file_open_root(&root, cn->corename, open_flags, 0600); - path_put(&root); + scoped_with_init_fs() + file = filp_open(cn->corename, open_flags, 0600); } else { file = filp_open(cn->corename, open_flags, 0600); } diff --git a/fs/cramfs/inode.c b/fs/cramfs/inode.c index 4edbfccd0bbe..d4cd03f4f60d 100644 --- a/fs/cramfs/inode.c +++ b/fs/cramfs/inode.c @@ -504,7 +504,7 @@ static void cramfs_kill_sb(struct super_block *sb) sb->s_mtd = NULL; } else if (IS_ENABLED(CONFIG_CRAMFS_BLOCKDEV) && sb->s_bdev) { sync_blockdev(sb->s_bdev); - bdev_fput(sb->s_bdev_file); + fs_bdev_file_release(sb->s_bdev_file, sb); } kfree(sbi); } diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 7aaf1913f9c6..525297c7ebd8 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -268,7 +268,7 @@ out: static int ecryptfs_create(struct mnt_idmap *idmap, struct inode *directory_inode, struct dentry *ecryptfs_dentry, - umode_t mode, bool excl) + umode_t mode) { struct inode *ecryptfs_inode; int rc; diff --git a/fs/efivarfs/inode.c b/fs/efivarfs/inode.c index 95dcad83da11..f0d009555fc6 100644 --- a/fs/efivarfs/inode.c +++ b/fs/efivarfs/inode.c @@ -75,7 +75,7 @@ static bool efivarfs_valid_name(const char *str, int len) } static int efivarfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode = NULL; struct efivar_entry *var; diff --git a/fs/efs/Kconfig b/fs/efs/Kconfig deleted file mode 100644 index 0833e533df9d..000000000000 --- a/fs/efs/Kconfig +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -config EFS_FS - tristate "EFS file system support (read only)" - depends on BLOCK - select BUFFER_HEAD - help - EFS is an older file system used for non-ISO9660 CD-ROMs and hard - disk partitions by SGI's IRIX operating system (IRIX 6.0 and newer - uses the XFS file system for hard disk partitions however). - - This implementation only offers read-only access. If you don't know - what all this is about, it's safe to say N. For more information - about EFS see its home page at <http://aeschi.ch.eu.org/efs/>. - - To compile the EFS file system support as a module, choose M here: the - module will be called efs. diff --git a/fs/efs/Makefile b/fs/efs/Makefile deleted file mode 100644 index 85e5b88f9471..000000000000 --- a/fs/efs/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# -# Makefile for the linux efs-filesystem routines. -# - -obj-$(CONFIG_EFS_FS) += efs.o - -efs-objs := super.o inode.o namei.o dir.o file.o symlink.o diff --git a/fs/efs/dir.c b/fs/efs/dir.c deleted file mode 100644 index 35ad0092c115..000000000000 --- a/fs/efs/dir.c +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * dir.c - * - * Copyright (c) 1999 Al Smith - */ - -#include <linux/buffer_head.h> -#include <linux/filelock.h> -#include "efs.h" - -static int efs_readdir(struct file *, struct dir_context *); - -const struct file_operations efs_dir_operations = { - .llseek = generic_file_llseek, - .read = generic_read_dir, - .iterate_shared = efs_readdir, - .setlease = generic_setlease, -}; - -const struct inode_operations efs_dir_inode_operations = { - .lookup = efs_lookup, -}; - -static int efs_readdir(struct file *file, struct dir_context *ctx) -{ - struct inode *inode = file_inode(file); - efs_block_t block; - int slot; - - if (inode->i_size & (EFS_DIRBSIZE-1)) - pr_warn("%s(): directory size not a multiple of EFS_DIRBSIZE\n", - __func__); - - /* work out where this entry can be found */ - block = ctx->pos >> EFS_DIRBSIZE_BITS; - - /* each block contains at most 256 slots */ - slot = ctx->pos & 0xff; - - /* look at all blocks */ - while (block < inode->i_blocks) { - struct efs_dir *dirblock; - struct buffer_head *bh; - - /* read the dir block */ - bh = sb_bread(inode->i_sb, efs_bmap(inode, block)); - - if (!bh) { - pr_err("%s(): failed to read dir block %d\n", - __func__, block); - break; - } - - dirblock = (struct efs_dir *) bh->b_data; - - if (be16_to_cpu(dirblock->magic) != EFS_DIRBLK_MAGIC) { - pr_err("%s(): invalid directory block\n", __func__); - brelse(bh); - break; - } - - for (; slot < dirblock->slots; slot++) { - struct efs_dentry *dirslot; - efs_ino_t inodenum; - const char *nameptr; - int namelen; - - if (dirblock->space[slot] == 0) - continue; - - dirslot = (struct efs_dentry *) (((char *) bh->b_data) + EFS_SLOTAT(dirblock, slot)); - - inodenum = be32_to_cpu(dirslot->inode); - namelen = dirslot->namelen; - nameptr = dirslot->name; - pr_debug("%s(): block %d slot %d/%d: inode %u, name \"%s\", namelen %u\n", - __func__, block, slot, dirblock->slots-1, - inodenum, nameptr, namelen); - if (!namelen) - continue; - /* found the next entry */ - ctx->pos = (block << EFS_DIRBSIZE_BITS) | slot; - - /* sanity check */ - if (nameptr - (char *) dirblock + namelen > EFS_DIRBSIZE) { - pr_warn("directory entry %d exceeds directory block\n", - slot); - continue; - } - - /* copy filename and data in dirslot */ - if (!dir_emit(ctx, nameptr, namelen, inodenum, DT_UNKNOWN)) { - brelse(bh); - return 0; - } - } - brelse(bh); - - slot = 0; - block++; - } - ctx->pos = (block << EFS_DIRBSIZE_BITS) | slot; - return 0; -} diff --git a/fs/efs/efs.h b/fs/efs/efs.h deleted file mode 100644 index 918d2b9abb76..000000000000 --- a/fs/efs/efs.h +++ /dev/null @@ -1,144 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Copyright (c) 1999 Al Smith, <Al.Smith@aeschi.ch.eu.org> - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang. - * Portions derived from IRIX header files (c) 1988 Silicon Graphics - */ -#ifndef _EFS_EFS_H_ -#define _EFS_EFS_H_ - -#ifdef pr_fmt -#undef pr_fmt -#endif - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include <linux/fs.h> -#include <linux/uaccess.h> - -#define EFS_VERSION "1.0a" - -/* 1 block is 512 bytes */ -#define EFS_BLOCKSIZE_BITS 9 -#define EFS_BLOCKSIZE (1 << EFS_BLOCKSIZE_BITS) - -typedef int32_t efs_block_t; -typedef uint32_t efs_ino_t; - -#define EFS_DIRECTEXTENTS 12 - -/* - * layout of an extent, in memory and on disk. 8 bytes exactly. - */ -typedef union extent_u { - unsigned char raw[8]; - struct extent_s { - unsigned int ex_magic:8; /* magic # (zero) */ - unsigned int ex_bn:24; /* basic block */ - unsigned int ex_length:8; /* numblocks in this extent */ - unsigned int ex_offset:24; /* logical offset into file */ - } cooked; -} efs_extent; - -typedef struct edevs { - __be16 odev; - __be32 ndev; -} efs_devs; - -/* - * extent based filesystem inode as it appears on disk. The efs inode - * is exactly 128 bytes long. - */ -struct efs_dinode { - __be16 di_mode; /* mode and type of file */ - __be16 di_nlink; /* number of links to file */ - __be16 di_uid; /* owner's user id */ - __be16 di_gid; /* owner's group id */ - __be32 di_size; /* number of bytes in file */ - __be32 di_atime; /* time last accessed */ - __be32 di_mtime; /* time last modified */ - __be32 di_ctime; /* time created */ - __be32 di_gen; /* generation number */ - __be16 di_numextents; /* # of extents */ - u_char di_version; /* version of inode */ - u_char di_spare; /* spare - used by AFS */ - union di_addr { - efs_extent di_extents[EFS_DIRECTEXTENTS]; - efs_devs di_dev; /* device for IFCHR/IFBLK */ - } di_u; -}; - -/* efs inode storage in memory */ -struct efs_inode_info { - int numextents; - int lastextent; - - efs_extent extents[EFS_DIRECTEXTENTS]; - struct inode vfs_inode; -}; - -#include <linux/efs_fs_sb.h> - -#define EFS_DIRBSIZE_BITS EFS_BLOCKSIZE_BITS -#define EFS_DIRBSIZE (1 << EFS_DIRBSIZE_BITS) - -struct efs_dentry { - __be32 inode; - unsigned char namelen; - char name[3]; -}; - -#define EFS_DENTSIZE (sizeof(struct efs_dentry) - 3 + 1) -#define EFS_MAXNAMELEN ((1 << (sizeof(char) * 8)) - 1) - -#define EFS_DIRBLK_HEADERSIZE 4 -#define EFS_DIRBLK_MAGIC 0xbeef /* moo */ - -struct efs_dir { - __be16 magic; - unsigned char firstused; - unsigned char slots; - - unsigned char space[EFS_DIRBSIZE - EFS_DIRBLK_HEADERSIZE]; -}; - -#define EFS_MAXENTS \ - ((EFS_DIRBSIZE - EFS_DIRBLK_HEADERSIZE) / \ - (EFS_DENTSIZE + sizeof(char))) - -#define EFS_SLOTAT(dir, slot) EFS_REALOFF((dir)->space[slot]) - -#define EFS_REALOFF(offset) ((offset << 1)) - - -static inline struct efs_inode_info *INODE_INFO(struct inode *inode) -{ - return container_of(inode, struct efs_inode_info, vfs_inode); -} - -static inline struct efs_sb_info *SUPER_INFO(struct super_block *sb) -{ - return sb->s_fs_info; -} - -struct statfs; -struct fid; - -extern const struct inode_operations efs_dir_inode_operations; -extern const struct file_operations efs_dir_operations; -extern const struct address_space_operations efs_symlink_aops; - -extern struct inode *efs_iget(struct super_block *, unsigned long); -extern efs_block_t efs_map_block(struct inode *, efs_block_t); -extern int efs_get_block(struct inode *, sector_t, struct buffer_head *, int); - -extern struct dentry *efs_lookup(struct inode *, struct dentry *, unsigned int); -extern struct dentry *efs_fh_to_dentry(struct super_block *sb, struct fid *fid, - int fh_len, int fh_type); -extern struct dentry *efs_fh_to_parent(struct super_block *sb, struct fid *fid, - int fh_len, int fh_type); -extern struct dentry *efs_get_parent(struct dentry *); -extern int efs_bmap(struct inode *, int); - -#endif /* _EFS_EFS_H_ */ diff --git a/fs/efs/file.c b/fs/efs/file.c deleted file mode 100644 index 9153dfe79bbc..000000000000 --- a/fs/efs/file.c +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * file.c - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang. - */ - -#include <linux/buffer_head.h> -#include "efs.h" - -int efs_get_block(struct inode *inode, sector_t iblock, - struct buffer_head *bh_result, int create) -{ - int error = -EROFS; - long phys; - - if (create) - return error; - if (iblock >= inode->i_blocks) - return 0; - - phys = efs_map_block(inode, iblock); - if (phys) - map_bh(bh_result, inode->i_sb, phys); - return 0; -} - -int efs_bmap(struct inode *inode, efs_block_t block) { - - if (block < 0) { - pr_warn("%s(): block < 0\n", __func__); - return 0; - } - - /* are we about to read past the end of a file ? */ - if (!(block < inode->i_blocks)) - return 0; - - return efs_map_block(inode, block); -} diff --git a/fs/efs/inode.c b/fs/efs/inode.c deleted file mode 100644 index 4b132729e638..000000000000 --- a/fs/efs/inode.c +++ /dev/null @@ -1,315 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * inode.c - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang, - * and from work (c) 1998 Mike Shaver. - */ - -#include <linux/buffer_head.h> -#include <linux/module.h> -#include <linux/fs.h> -#include "efs.h" -#include <linux/efs_fs_sb.h> - -static int efs_read_folio(struct file *file, struct folio *folio) -{ - return block_read_full_folio(folio, efs_get_block); -} - -static sector_t _efs_bmap(struct address_space *mapping, sector_t block) -{ - return generic_block_bmap(mapping,block,efs_get_block); -} - -static const struct address_space_operations efs_aops = { - .read_folio = efs_read_folio, - .bmap = _efs_bmap -}; - -static inline void extent_copy(efs_extent *src, efs_extent *dst) { - /* - * this is slightly evil. it doesn't just copy - * efs_extent from src to dst, it also mangles - * the bits so that dst ends up in cpu byte-order. - */ - - dst->cooked.ex_magic = (unsigned int) src->raw[0]; - dst->cooked.ex_bn = ((unsigned int) src->raw[1] << 16) | - ((unsigned int) src->raw[2] << 8) | - ((unsigned int) src->raw[3] << 0); - dst->cooked.ex_length = (unsigned int) src->raw[4]; - dst->cooked.ex_offset = ((unsigned int) src->raw[5] << 16) | - ((unsigned int) src->raw[6] << 8) | - ((unsigned int) src->raw[7] << 0); - return; -} - -struct inode *efs_iget(struct super_block *super, unsigned long ino) -{ - int i, inode_index; - dev_t device; - u32 rdev; - struct buffer_head *bh; - struct efs_sb_info *sb = SUPER_INFO(super); - struct efs_inode_info *in; - efs_block_t block, offset; - struct efs_dinode *efs_inode; - struct inode *inode; - - inode = iget_locked(super, ino); - if (!inode) - return ERR_PTR(-ENOMEM); - if (!(inode_state_read_once(inode) & I_NEW)) - return inode; - - in = INODE_INFO(inode); - - /* - ** EFS layout: - ** - ** | cylinder group | cylinder group | cylinder group ..etc - ** |inodes|data |inodes|data |inodes|data ..etc - ** - ** work out the inode block index, (considering initially that the - ** inodes are stored as consecutive blocks). then work out the block - ** number of that inode given the above layout, and finally the - ** offset of the inode within that block. - */ - - inode_index = inode->i_ino / - (EFS_BLOCKSIZE / sizeof(struct efs_dinode)); - - block = sb->fs_start + sb->first_block + - (sb->group_size * (inode_index / sb->inode_blocks)) + - (inode_index % sb->inode_blocks); - - offset = (inode->i_ino % - (EFS_BLOCKSIZE / sizeof(struct efs_dinode))) * - sizeof(struct efs_dinode); - - bh = sb_bread(inode->i_sb, block); - if (!bh) { - pr_warn("%s() failed at block %d\n", __func__, block); - goto read_inode_error; - } - - efs_inode = (struct efs_dinode *) (bh->b_data + offset); - - inode->i_mode = be16_to_cpu(efs_inode->di_mode); - set_nlink(inode, be16_to_cpu(efs_inode->di_nlink)); - i_uid_write(inode, (uid_t)be16_to_cpu(efs_inode->di_uid)); - i_gid_write(inode, (gid_t)be16_to_cpu(efs_inode->di_gid)); - inode->i_size = be32_to_cpu(efs_inode->di_size); - inode_set_atime(inode, be32_to_cpu(efs_inode->di_atime), 0); - inode_set_mtime(inode, be32_to_cpu(efs_inode->di_mtime), 0); - inode_set_ctime(inode, be32_to_cpu(efs_inode->di_ctime), 0); - - /* this is the number of blocks in the file */ - if (inode->i_size == 0) { - inode->i_blocks = 0; - } else { - inode->i_blocks = ((inode->i_size - 1) >> EFS_BLOCKSIZE_BITS) + 1; - } - - rdev = be16_to_cpu(efs_inode->di_u.di_dev.odev); - if (rdev == 0xffff) { - rdev = be32_to_cpu(efs_inode->di_u.di_dev.ndev); - if (sysv_major(rdev) > 0xfff) - device = 0; - else - device = MKDEV(sysv_major(rdev), sysv_minor(rdev)); - } else - device = old_decode_dev(rdev); - - /* get the number of extents for this object */ - in->numextents = be16_to_cpu(efs_inode->di_numextents); - in->lastextent = 0; - - /* copy the extents contained within the inode to memory */ - for(i = 0; i < EFS_DIRECTEXTENTS; i++) { - extent_copy(&(efs_inode->di_u.di_extents[i]), &(in->extents[i])); - if (i < in->numextents && in->extents[i].cooked.ex_magic != 0) { - pr_warn("extent %d has bad magic number in inode %llu\n", - i, inode->i_ino); - brelse(bh); - goto read_inode_error; - } - } - - brelse(bh); - pr_debug("efs_iget(): inode %llu, extents %d, mode %o\n", - inode->i_ino, in->numextents, inode->i_mode); - switch (inode->i_mode & S_IFMT) { - case S_IFDIR: - inode->i_op = &efs_dir_inode_operations; - inode->i_fop = &efs_dir_operations; - break; - case S_IFREG: - inode->i_fop = &generic_ro_fops; - inode->i_data.a_ops = &efs_aops; - break; - case S_IFLNK: - inode->i_op = &page_symlink_inode_operations; - inode_nohighmem(inode); - inode->i_data.a_ops = &efs_symlink_aops; - break; - case S_IFCHR: - case S_IFBLK: - case S_IFIFO: - init_special_inode(inode, inode->i_mode, device); - break; - default: - pr_warn("unsupported inode mode %o\n", inode->i_mode); - goto read_inode_error; - break; - } - - unlock_new_inode(inode); - return inode; - -read_inode_error: - pr_warn("failed to read inode %llu\n", inode->i_ino); - iget_failed(inode); - return ERR_PTR(-EIO); -} - -static inline efs_block_t -efs_extent_check(efs_extent *ptr, efs_block_t block, struct efs_sb_info *sb) { - efs_block_t start; - efs_block_t length; - efs_block_t offset; - - /* - * given an extent and a logical block within a file, - * can this block be found within this extent ? - */ - start = ptr->cooked.ex_bn; - length = ptr->cooked.ex_length; - offset = ptr->cooked.ex_offset; - - if ((block >= offset) && (block < offset+length)) { - return(sb->fs_start + start + block - offset); - } else { - return 0; - } -} - -efs_block_t efs_map_block(struct inode *inode, efs_block_t block) { - struct efs_sb_info *sb = SUPER_INFO(inode->i_sb); - struct efs_inode_info *in = INODE_INFO(inode); - struct buffer_head *bh = NULL; - - int cur, last, first = 1; - int ibase, ioffset, dirext, direxts, indext, indexts; - efs_block_t iblock, result = 0, lastblock = 0; - efs_extent ext, *exts; - - last = in->lastextent; - - if (in->numextents <= EFS_DIRECTEXTENTS) { - /* first check the last extent we returned */ - if ((result = efs_extent_check(&in->extents[last], block, sb))) - return result; - - /* if we only have one extent then nothing can be found */ - if (in->numextents == 1) { - pr_err("%s() failed to map (1 extent)\n", __func__); - return 0; - } - - direxts = in->numextents; - - /* - * check the stored extents in the inode - * start with next extent and check forwards - */ - for(dirext = 1; dirext < direxts; dirext++) { - cur = (last + dirext) % in->numextents; - if ((result = efs_extent_check(&in->extents[cur], block, sb))) { - in->lastextent = cur; - return result; - } - } - - pr_err("%s() failed to map block %u (dir)\n", __func__, block); - return 0; - } - - pr_debug("%s(): indirect search for logical block %u\n", - __func__, block); - direxts = in->extents[0].cooked.ex_offset; - indexts = in->numextents; - - for(indext = 0; indext < indexts; indext++) { - cur = (last + indext) % indexts; - - /* - * work out which direct extent contains `cur'. - * - * also compute ibase: i.e. the number of the first - * indirect extent contained within direct extent `cur'. - * - */ - ibase = 0; - for(dirext = 0; cur < ibase && dirext < direxts; dirext++) { - ibase += in->extents[dirext].cooked.ex_length * - (EFS_BLOCKSIZE / sizeof(efs_extent)); - } - - if (dirext == direxts) { - /* should never happen */ - pr_err("couldn't find direct extent for indirect extent %d (block %u)\n", - cur, block); - if (bh) brelse(bh); - return 0; - } - - /* work out block number and offset of this indirect extent */ - iblock = sb->fs_start + in->extents[dirext].cooked.ex_bn + - (cur - ibase) / - (EFS_BLOCKSIZE / sizeof(efs_extent)); - ioffset = (cur - ibase) % - (EFS_BLOCKSIZE / sizeof(efs_extent)); - - if (first || lastblock != iblock) { - if (bh) brelse(bh); - - bh = sb_bread(inode->i_sb, iblock); - if (!bh) { - pr_err("%s() failed at block %d\n", - __func__, iblock); - return 0; - } - pr_debug("%s(): read indirect extent block %d\n", - __func__, iblock); - first = 0; - lastblock = iblock; - } - - exts = (efs_extent *) bh->b_data; - - extent_copy(&(exts[ioffset]), &ext); - - if (ext.cooked.ex_magic != 0) { - pr_err("extent %d has bad magic number in block %d\n", - cur, iblock); - if (bh) brelse(bh); - return 0; - } - - if ((result = efs_extent_check(&ext, block, sb))) { - if (bh) brelse(bh); - in->lastextent = cur; - return result; - } - } - if (bh) brelse(bh); - pr_err("%s() failed to map block %u (indir)\n", __func__, block); - return 0; -} - -MODULE_DESCRIPTION("Extent File System (efs)"); -MODULE_LICENSE("GPL"); diff --git a/fs/efs/namei.c b/fs/efs/namei.c deleted file mode 100644 index 38961ee1d1af..000000000000 --- a/fs/efs/namei.c +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * namei.c - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang. - */ - -#include <linux/buffer_head.h> -#include <linux/string.h> -#include <linux/exportfs.h> -#include "efs.h" - - -static efs_ino_t efs_find_entry(struct inode *inode, const char *name, int len) -{ - struct buffer_head *bh; - - int slot, namelen; - char *nameptr; - struct efs_dir *dirblock; - struct efs_dentry *dirslot; - efs_ino_t inodenum; - efs_block_t block; - - if (inode->i_size & (EFS_DIRBSIZE-1)) - pr_warn("%s(): directory size not a multiple of EFS_DIRBSIZE\n", - __func__); - - for(block = 0; block < inode->i_blocks; block++) { - - bh = sb_bread(inode->i_sb, efs_bmap(inode, block)); - if (!bh) { - pr_err("%s(): failed to read dir block %d\n", - __func__, block); - return 0; - } - - dirblock = (struct efs_dir *) bh->b_data; - - if (be16_to_cpu(dirblock->magic) != EFS_DIRBLK_MAGIC) { - pr_err("%s(): invalid directory block\n", __func__); - brelse(bh); - return 0; - } - - for (slot = 0; slot < dirblock->slots; slot++) { - dirslot = (struct efs_dentry *) (((char *) bh->b_data) + EFS_SLOTAT(dirblock, slot)); - - namelen = dirslot->namelen; - nameptr = dirslot->name; - - if ((namelen == len) && (!memcmp(name, nameptr, len))) { - inodenum = be32_to_cpu(dirslot->inode); - brelse(bh); - return inodenum; - } - } - brelse(bh); - } - return 0; -} - -struct dentry *efs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) -{ - efs_ino_t inodenum; - struct inode *inode = NULL; - - inodenum = efs_find_entry(dir, dentry->d_name.name, dentry->d_name.len); - if (inodenum) - inode = efs_iget(dir->i_sb, inodenum); - - return d_splice_alias(inode, dentry); -} - -static struct inode *efs_nfs_get_inode(struct super_block *sb, u64 ino, - u32 generation) -{ - struct inode *inode; - - if (ino == 0) - return ERR_PTR(-ESTALE); - inode = efs_iget(sb, ino); - if (IS_ERR(inode)) - return ERR_CAST(inode); - - if (generation && inode->i_generation != generation) { - iput(inode); - return ERR_PTR(-ESTALE); - } - - return inode; -} - -struct dentry *efs_fh_to_dentry(struct super_block *sb, struct fid *fid, - int fh_len, int fh_type) -{ - return generic_fh_to_dentry(sb, fid, fh_len, fh_type, - efs_nfs_get_inode); -} - -struct dentry *efs_fh_to_parent(struct super_block *sb, struct fid *fid, - int fh_len, int fh_type) -{ - return generic_fh_to_parent(sb, fid, fh_len, fh_type, - efs_nfs_get_inode); -} - -struct dentry *efs_get_parent(struct dentry *child) -{ - struct dentry *parent = ERR_PTR(-ENOENT); - efs_ino_t ino; - - ino = efs_find_entry(d_inode(child), "..", 2); - if (ino) - parent = d_obtain_alias(efs_iget(child->d_sb, ino)); - - return parent; -} diff --git a/fs/efs/super.c b/fs/efs/super.c deleted file mode 100644 index 11fea3bbce7c..000000000000 --- a/fs/efs/super.c +++ /dev/null @@ -1,368 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * super.c - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang. - */ - -#include <linux/init.h> -#include <linux/module.h> -#include <linux/exportfs.h> -#include <linux/slab.h> -#include <linux/buffer_head.h> -#include <linux/vfs.h> -#include <linux/blkdev.h> -#include <linux/fs_context.h> -#include "efs.h" -#include <linux/efs_vh.h> -#include <linux/efs_fs_sb.h> - -static int efs_statfs(struct dentry *dentry, struct kstatfs *buf); -static int efs_init_fs_context(struct fs_context *fc); - -static void efs_kill_sb(struct super_block *s) -{ - struct efs_sb_info *sbi = SUPER_INFO(s); - kill_block_super(s); - kfree(sbi); -} - -static struct pt_types sgi_pt_types[] = { - {0x00, "SGI vh"}, - {0x01, "SGI trkrepl"}, - {0x02, "SGI secrepl"}, - {0x03, "SGI raw"}, - {0x04, "SGI bsd"}, - {SGI_SYSV, "SGI sysv"}, - {0x06, "SGI vol"}, - {SGI_EFS, "SGI efs"}, - {0x08, "SGI lv"}, - {0x09, "SGI rlv"}, - {0x0A, "SGI xfs"}, - {0x0B, "SGI xfslog"}, - {0x0C, "SGI xlv"}, - {0x82, "Linux swap"}, - {0x83, "Linux native"}, - {0, NULL} -}; - -/* - * File system definition and registration. - */ -static struct file_system_type efs_fs_type = { - .owner = THIS_MODULE, - .name = "efs", - .kill_sb = efs_kill_sb, - .fs_flags = FS_REQUIRES_DEV, - .init_fs_context = efs_init_fs_context, -}; -MODULE_ALIAS_FS("efs"); - -static struct kmem_cache * efs_inode_cachep; - -static struct inode *efs_alloc_inode(struct super_block *sb) -{ - struct efs_inode_info *ei; - ei = alloc_inode_sb(sb, efs_inode_cachep, GFP_KERNEL); - if (!ei) - return NULL; - return &ei->vfs_inode; -} - -static void efs_free_inode(struct inode *inode) -{ - kmem_cache_free(efs_inode_cachep, INODE_INFO(inode)); -} - -static void init_once(void *foo) -{ - struct efs_inode_info *ei = (struct efs_inode_info *) foo; - - inode_init_once(&ei->vfs_inode); -} - -static int __init init_inodecache(void) -{ - efs_inode_cachep = kmem_cache_create("efs_inode_cache", - sizeof(struct efs_inode_info), 0, - SLAB_RECLAIM_ACCOUNT|SLAB_ACCOUNT, - init_once); - if (efs_inode_cachep == NULL) - return -ENOMEM; - return 0; -} - -static void destroy_inodecache(void) -{ - /* - * Make sure all delayed rcu free inodes are flushed before we - * destroy cache. - */ - rcu_barrier(); - kmem_cache_destroy(efs_inode_cachep); -} - -static const struct super_operations efs_superblock_operations = { - .alloc_inode = efs_alloc_inode, - .free_inode = efs_free_inode, - .statfs = efs_statfs, -}; - -static const struct export_operations efs_export_ops = { - .encode_fh = generic_encode_ino32_fh, - .fh_to_dentry = efs_fh_to_dentry, - .fh_to_parent = efs_fh_to_parent, - .get_parent = efs_get_parent, -}; - -static int __init init_efs_fs(void) { - int err; - pr_info(EFS_VERSION" - http://aeschi.ch.eu.org/efs/\n"); - err = init_inodecache(); - if (err) - goto out1; - err = register_filesystem(&efs_fs_type); - if (err) - goto out; - return 0; -out: - destroy_inodecache(); -out1: - return err; -} - -static void __exit exit_efs_fs(void) { - unregister_filesystem(&efs_fs_type); - destroy_inodecache(); -} - -module_init(init_efs_fs) -module_exit(exit_efs_fs) - -static efs_block_t efs_validate_vh(struct volume_header *vh) { - int i; - __be32 cs, *ui; - int csum; - efs_block_t sblock = 0; /* shuts up gcc */ - struct pt_types *pt_entry; - int pt_type, slice = -1; - - if (be32_to_cpu(vh->vh_magic) != VHMAGIC) { - /* - * assume that we're dealing with a partition and allow - * read_super() to try and detect a valid superblock - * on the next block. - */ - return 0; - } - - ui = ((__be32 *) (vh + 1)) - 1; - for(csum = 0; ui >= ((__be32 *) vh);) { - cs = *ui--; - csum += be32_to_cpu(cs); - } - if (csum) { - pr_warn("SGI disklabel: checksum bad, label corrupted\n"); - return 0; - } - -#ifdef DEBUG - pr_debug("bf: \"%16s\"\n", vh->vh_bootfile); - - for(i = 0; i < NVDIR; i++) { - int j; - char name[VDNAMESIZE+1]; - - for(j = 0; j < VDNAMESIZE; j++) { - name[j] = vh->vh_vd[i].vd_name[j]; - } - name[j] = (char) 0; - - if (name[0]) { - pr_debug("vh: %8s block: 0x%08x size: 0x%08x\n", - name, (int) be32_to_cpu(vh->vh_vd[i].vd_lbn), - (int) be32_to_cpu(vh->vh_vd[i].vd_nbytes)); - } - } -#endif - - for(i = 0; i < NPARTAB; i++) { - pt_type = (int) be32_to_cpu(vh->vh_pt[i].pt_type); - for(pt_entry = sgi_pt_types; pt_entry->pt_name; pt_entry++) { - if (pt_type == pt_entry->pt_type) break; - } -#ifdef DEBUG - if (be32_to_cpu(vh->vh_pt[i].pt_nblks)) { - pr_debug("pt %2d: start: %08d size: %08d type: 0x%02x (%s)\n", - i, (int)be32_to_cpu(vh->vh_pt[i].pt_firstlbn), - (int)be32_to_cpu(vh->vh_pt[i].pt_nblks), - pt_type, (pt_entry->pt_name) ? - pt_entry->pt_name : "unknown"); - } -#endif - if (IS_EFS(pt_type)) { - sblock = be32_to_cpu(vh->vh_pt[i].pt_firstlbn); - slice = i; - } - } - - if (slice == -1) { - pr_notice("partition table contained no EFS partitions\n"); -#ifdef DEBUG - } else { - pr_info("using slice %d (type %s, offset 0x%x)\n", slice, - (pt_entry->pt_name) ? pt_entry->pt_name : "unknown", - sblock); -#endif - } - return sblock; -} - -static int efs_validate_super(struct efs_sb_info *sb, struct efs_super *super) { - - if (!IS_EFS_MAGIC(be32_to_cpu(super->fs_magic))) - return -1; - - sb->fs_magic = be32_to_cpu(super->fs_magic); - sb->total_blocks = be32_to_cpu(super->fs_size); - sb->first_block = be32_to_cpu(super->fs_firstcg); - sb->group_size = be32_to_cpu(super->fs_cgfsize); - sb->data_free = be32_to_cpu(super->fs_tfree); - sb->inode_free = be32_to_cpu(super->fs_tinode); - sb->inode_blocks = be16_to_cpu(super->fs_cgisize); - sb->total_groups = be16_to_cpu(super->fs_ncg); - - return 0; -} - -static int efs_fill_super(struct super_block *s, struct fs_context *fc) -{ - struct efs_sb_info *sb; - struct buffer_head *bh; - struct inode *root; - - sb = kzalloc_obj(struct efs_sb_info); - if (!sb) - return -ENOMEM; - s->s_fs_info = sb; - s->s_time_min = 0; - s->s_time_max = U32_MAX; - - s->s_magic = EFS_SUPER_MAGIC; - if (!sb_set_blocksize(s, EFS_BLOCKSIZE)) { - pr_err("device does not support %d byte blocks\n", - EFS_BLOCKSIZE); - return invalf(fc, "device does not support %d byte blocks\n", - EFS_BLOCKSIZE); - } - - /* read the vh (volume header) block */ - bh = sb_bread(s, 0); - - if (!bh) { - pr_err("cannot read volume header\n"); - return -EIO; - } - - /* - * if this returns zero then we didn't find any partition table. - * this isn't (yet) an error - just assume for the moment that - * the device is valid and go on to search for a superblock. - */ - sb->fs_start = efs_validate_vh((struct volume_header *) bh->b_data); - brelse(bh); - - if (sb->fs_start == -1) { - return -EINVAL; - } - - bh = sb_bread(s, sb->fs_start + EFS_SUPER); - if (!bh) { - pr_err("cannot read superblock\n"); - return -EIO; - } - - if (efs_validate_super(sb, (struct efs_super *) bh->b_data)) { -#ifdef DEBUG - pr_warn("invalid superblock at block %u\n", - sb->fs_start + EFS_SUPER); -#endif - brelse(bh); - return -EINVAL; - } - brelse(bh); - - if (!sb_rdonly(s)) { -#ifdef DEBUG - pr_info("forcing read-only mode\n"); -#endif - s->s_flags |= SB_RDONLY; - } - s->s_op = &efs_superblock_operations; - s->s_export_op = &efs_export_ops; - root = efs_iget(s, EFS_ROOTINODE); - if (IS_ERR(root)) { - pr_err("get root inode failed\n"); - return PTR_ERR(root); - } - - s->s_root = d_make_root(root); - if (!(s->s_root)) { - pr_err("get root dentry failed\n"); - return -ENOMEM; - } - - return 0; -} - -static int efs_get_tree(struct fs_context *fc) -{ - return get_tree_bdev(fc, efs_fill_super); -} - -static int efs_reconfigure(struct fs_context *fc) -{ - sync_filesystem(fc->root->d_sb); - fc->sb_flags |= SB_RDONLY; - - return 0; -} - -static const struct fs_context_operations efs_context_opts = { - .get_tree = efs_get_tree, - .reconfigure = efs_reconfigure, -}; - -/* - * Set up the filesystem mount context. - */ -static int efs_init_fs_context(struct fs_context *fc) -{ - fc->ops = &efs_context_opts; - - return 0; -} - -static int efs_statfs(struct dentry *dentry, struct kstatfs *buf) { - struct super_block *sb = dentry->d_sb; - struct efs_sb_info *sbi = SUPER_INFO(sb); - u64 id = huge_encode_dev(sb->s_bdev->bd_dev); - - buf->f_type = EFS_SUPER_MAGIC; /* efs magic number */ - buf->f_bsize = EFS_BLOCKSIZE; /* blocksize */ - buf->f_blocks = sbi->total_groups * /* total data blocks */ - (sbi->group_size - sbi->inode_blocks); - buf->f_bfree = sbi->data_free; /* free data blocks */ - buf->f_bavail = sbi->data_free; /* free blocks for non-root */ - buf->f_files = sbi->total_groups * /* total inodes */ - sbi->inode_blocks * - (EFS_BLOCKSIZE / sizeof(struct efs_dinode)); - buf->f_ffree = sbi->inode_free; /* free inodes */ - buf->f_fsid = u64_to_fsid(id); - buf->f_namelen = EFS_MAXNAMELEN; /* max filename length */ - - return 0; -} - diff --git a/fs/efs/symlink.c b/fs/efs/symlink.c deleted file mode 100644 index 7749feded722..000000000000 --- a/fs/efs/symlink.c +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * symlink.c - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang. - */ - -#include <linux/string.h> -#include <linux/pagemap.h> -#include <linux/buffer_head.h> -#include "efs.h" - -static int efs_symlink_read_folio(struct file *file, struct folio *folio) -{ - char *link = folio_address(folio); - struct buffer_head *bh; - struct inode *inode = folio->mapping->host; - efs_block_t size = inode->i_size; - int err; - - err = -ENAMETOOLONG; - if (size > 2 * EFS_BLOCKSIZE) - goto fail; - - /* read first 512 bytes of link target */ - err = -EIO; - bh = sb_bread(inode->i_sb, efs_bmap(inode, 0)); - if (!bh) - goto fail; - memcpy(link, bh->b_data, (size > EFS_BLOCKSIZE) ? EFS_BLOCKSIZE : size); - brelse(bh); - if (size > EFS_BLOCKSIZE) { - bh = sb_bread(inode->i_sb, efs_bmap(inode, 1)); - if (!bh) - goto fail; - memcpy(link + EFS_BLOCKSIZE, bh->b_data, size - EFS_BLOCKSIZE); - brelse(bh); - } - link[size] = '\0'; - err = 0; -fail: - folio_end_read(folio, err == 0); - return err; -} - -const struct address_space_operations efs_symlink_aops = { - .read_folio = efs_symlink_read_folio -}; diff --git a/fs/erofs/super.c b/fs/erofs/super.c index bc55be84d945..8ead1646f329 100644 --- a/fs/erofs/super.c +++ b/fs/erofs/super.c @@ -147,8 +147,8 @@ static int erofs_init_device(struct erofs_buf *buf, struct super_block *sb, if (!sbi->devs->flatdev) { file = erofs_is_fileio_mode(sbi) ? filp_open(dif->path, O_RDONLY | O_LARGEFILE, 0) : - bdev_file_open_by_path(dif->path, - BLK_OPEN_READ, sb->s_type, NULL); + fs_bdev_file_open_by_path(dif->path, + BLK_OPEN_READ, sb->s_type, sb); if (IS_ERR(file)) { if (file == ERR_PTR(-ENOTBLK)) return -EINVAL; @@ -841,28 +841,34 @@ static int erofs_fc_reconfigure(struct fs_context *fc) static int erofs_release_device_info(int id, void *ptr, void *data) { + struct super_block *sb = data; struct erofs_device_info *dif = ptr; fs_put_dax(dif->dax_dev, NULL); - if (dif->file) - fput(dif->file); + if (dif->file) { + if (S_ISBLK(file_inode(dif->file)->i_mode)) + fs_bdev_file_release(dif->file, sb); + else + fput(dif->file); + } kfree(dif->path); kfree(dif); return 0; } -static void erofs_free_dev_context(struct erofs_dev_context *devs) +static void erofs_free_dev_context(struct erofs_dev_context *devs, + struct super_block *sb) { if (!devs) return; - idr_for_each(&devs->tree, &erofs_release_device_info, NULL); + idr_for_each(&devs->tree, &erofs_release_device_info, sb); idr_destroy(&devs->tree); kfree(devs); } -static void erofs_sb_free(struct erofs_sb_info *sbi) +static void erofs_sb_free(struct erofs_sb_info *sbi, struct super_block *sb) { - erofs_free_dev_context(sbi->devs); + erofs_free_dev_context(sbi->devs, sb); kfree_sensitive(sbi->domain_id); if (sbi->dif0.file) fput(sbi->dif0.file); @@ -874,8 +880,13 @@ static void erofs_fc_free(struct fs_context *fc) { struct erofs_sb_info *sbi = fc->s_fs_info; - if (sbi) /* free here if an error occurs before transferring to sb */ - erofs_sb_free(sbi); + /* + * Freed here only if an error occurs before the sb is set up; at that + * point no block-backed device has been claimed (that happens in + * fill_super), so the NULL sb never reaches fs_bdev_file_release(). + */ + if (sbi) + erofs_sb_free(sbi, NULL); } static const struct fs_context_operations erofs_context_ops = { @@ -929,7 +940,7 @@ static void erofs_kill_sb(struct super_block *sb) kill_block_super(sb); erofs_drop_internal_inodes(sbi); fs_put_dax(sbi->dif0.dax_dev, NULL); - erofs_sb_free(sbi); + erofs_sb_free(sbi, sb); sb->s_fs_info = NULL; } @@ -941,7 +952,7 @@ static void erofs_put_super(struct super_block *sb) erofs_shrinker_unregister(sb); erofs_xattr_prefixes_cleanup(sb); erofs_drop_internal_inodes(sbi); - erofs_free_dev_context(sbi->devs); + erofs_free_dev_context(sbi->devs, sb); sbi->devs = NULL; } diff --git a/fs/exec.c b/fs/exec.c index c7b8f2d6366c..41e1684d999c 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1418,6 +1418,8 @@ static void free_bprm(struct linux_binprm *bprm) /* If a binfmt changed the interp, free it. */ if (bprm->interp != bprm->filename) kfree(bprm->interp); + kfree(bprm->bpf_interp); + kfree(bprm->bpf_interp_arg); kfree(bprm->fdpath); kfree(bprm); } diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index b7d5e44ad38e..cd9c9eca58f8 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -538,7 +538,7 @@ out: } static int exfat_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; struct inode *inode; diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c index 0d09d22fe708..8666233ec63b 100644 --- a/fs/ext2/namei.c +++ b/fs/ext2/namei.c @@ -99,7 +99,7 @@ struct dentry *ext2_get_parent(struct dentry *child) */ static int ext2_create (struct mnt_idmap * idmap, struct inode * dir, struct dentry * dentry, - umode_t mode, bool excl) + umode_t mode) { struct inode *inode; int err; @@ -236,7 +236,7 @@ static struct dentry *ext2_mkdir(struct mnt_idmap * idmap, inode_inc_link_count(dir); - inode = ext2_new_inode(dir, S_IFDIR | mode, &dentry->d_name); + inode = ext2_new_inode(dir, mode, &dentry->d_name); err = PTR_ERR(inode); if (IS_ERR(inode)) goto out_dir; diff --git a/fs/ext4/extents-test.c b/fs/ext4/extents-test.c index bd7795a82607..c3836ecb89f9 100644 --- a/fs/ext4/extents-test.c +++ b/fs/ext4/extents-test.c @@ -126,11 +126,6 @@ struct kunit_ext_test_param { struct kunit_ext_data_state exp_data_state[3]; }; -static void ext_kill_sb(struct super_block *sb) -{ - generic_shutdown_super(sb); -} - static int ext_init_fs_context(struct fs_context *fc) { return 0; @@ -138,13 +133,13 @@ static int ext_init_fs_context(struct fs_context *fc) static int ext_set(struct super_block *sb, struct fs_context *fc) { - return 0; + return set_anon_super_fc(sb, fc); } static struct file_system_type ext_fs_type = { .name = "extents test", .init_fs_context = ext_init_fs_context, - .kill_sb = ext_kill_sb, + .kill_sb = kill_anon_super, }; static void extents_kunit_exit(struct kunit *test) diff --git a/fs/ext4/mballoc-test.c b/fs/ext4/mballoc-test.c index 0424b8b0b4c3..d31780075c21 100644 --- a/fs/ext4/mballoc-test.c +++ b/fs/ext4/mballoc-test.c @@ -59,11 +59,6 @@ static const struct super_operations mbt_sops = { .free_inode = mbt_free_inode, }; -static void mbt_kill_sb(struct super_block *sb) -{ - generic_shutdown_super(sb); -} - static int mbt_init_fs_context(struct fs_context *fc) { return 0; @@ -72,7 +67,7 @@ static int mbt_init_fs_context(struct fs_context *fc) static struct file_system_type mbt_fs_type = { .name = "mballoc test", .init_fs_context = mbt_init_fs_context, - .kill_sb = mbt_kill_sb, + .kill_sb = kill_anon_super, }; static int mbt_mb_init(struct super_block *sb) @@ -136,7 +131,7 @@ static void mbt_mb_release(struct super_block *sb) static int mbt_set(struct super_block *sb, struct fs_context *fc) { - return 0; + return set_anon_super_fc(sb, fc); } static struct super_block *mbt_ext4_alloc_super_block(void) diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 3b9740c1c16d..a6386c1d237f 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -2813,7 +2813,7 @@ static int ext4_add_nondir(handle_t *handle, * with d_instantiate(). */ static int ext4_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { handle_t *handle; struct inode *inode; @@ -3011,7 +3011,7 @@ static struct dentry *ext4_mkdir(struct mnt_idmap *idmap, struct inode *dir, credits = (EXT4_DATA_TRANS_BLOCKS(dir->i_sb) + EXT4_INDEX_EXTRA_TRANS_BLOCKS + 3); retry: - inode = ext4_new_inode_start_handle(idmap, dir, S_IFDIR | mode, + inode = ext4_new_inode_start_handle(idmap, dir, mode, &dentry->d_name, 0, NULL, EXT4_HT_DIR, credits); handle = ext4_journal_current_handle(); diff --git a/fs/ext4/super.c b/fs/ext4/super.c index 32c8e33b5036..8e7c93a5e458 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -5816,7 +5816,7 @@ failed_mount: brelse(sbi->s_sbh); if (sbi->s_journal_bdev_file) { invalidate_bdev(file_bdev(sbi->s_journal_bdev_file)); - bdev_fput(sbi->s_journal_bdev_file); + fs_bdev_file_release(sbi->s_journal_bdev_file, sb); } out_fail: invalidate_bdev(sb->s_bdev); @@ -6000,9 +6000,9 @@ static struct file *ext4_get_journal_blkdev(struct super_block *sb, struct ext4_super_block *es; int errno; - bdev_file = bdev_file_open_by_dev(j_dev, + bdev_file = fs_bdev_file_open_by_dev(j_dev, BLK_OPEN_READ | BLK_OPEN_WRITE | BLK_OPEN_RESTRICT_WRITES, - sb, &fs_holder_ops); + sb, sb); if (IS_ERR(bdev_file)) { ext4_msg(sb, KERN_ERR, "failed to open journal device unknown-block(%u,%u) %pe", @@ -6062,7 +6062,7 @@ static struct file *ext4_get_journal_blkdev(struct super_block *sb, out_bh: brelse(bh); out_bdev: - bdev_fput(bdev_file); + fs_bdev_file_release(bdev_file, sb); return ERR_PTR(errno); } @@ -6101,7 +6101,7 @@ static journal_t *ext4_open_dev_journal(struct super_block *sb, out_journal: ext4_journal_destroy(EXT4_SB(sb), journal); out_bdev: - bdev_fput(bdev_file); + fs_bdev_file_release(bdev_file, sb); return ERR_PTR(errno); } @@ -7519,7 +7519,7 @@ static void ext4_kill_sb(struct super_block *sb) kill_block_super(sb); if (bdev_file) - bdev_fput(bdev_file); + fs_bdev_file_release(bdev_file, sb); } static struct file_system_type ext4_fs_type = { diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index cac03b8e91a1..5ae647a352aa 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -366,7 +366,7 @@ fail_drop: } static int f2fs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct f2fs_sb_info *sbi = F2FS_I_SB(dir); struct f2fs_lock_context lc; @@ -742,7 +742,7 @@ static struct dentry *f2fs_mkdir(struct mnt_idmap *idmap, struct inode *dir, if (err) return ERR_PTR(err); - inode = f2fs_new_inode(idmap, dir, S_IFDIR | mode, NULL); + inode = f2fs_new_inode(idmap, dir, mode, NULL); if (IS_ERR(inode)) return ERR_CAST(inode); diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 769d16d54997..de99eead6f8d 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -1971,7 +1971,7 @@ static void destroy_device_list(struct f2fs_sb_info *sbi) for (i = 0; i < sbi->s_ndevs; i++) { if (i > 0) - bdev_fput(FDEV(i).bdev_file); + fs_bdev_file_release(FDEV(i).bdev_file, sbi->sb); #ifdef CONFIG_BLK_DEV_ZONED kvfree(FDEV(i).blkz_seq); #endif @@ -4901,8 +4901,8 @@ static int f2fs_scan_devices(struct f2fs_sb_info *sbi) FDEV(i).end_blk = FDEV(i).start_blk + SEGS_TO_BLKS(sbi, FDEV(i).total_segments) - 1; - FDEV(i).bdev_file = bdev_file_open_by_path( - FDEV(i).path, mode, sbi->sb, NULL); + FDEV(i).bdev_file = fs_bdev_file_open_by_path( + FDEV(i).path, mode, sbi->sb, sbi->sb); } } if (IS_ERR(FDEV(i).bdev_file)) diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index 0fd2971ad4b1..ee6824c5d136 100644 --- a/fs/fat/namei_msdos.c +++ b/fs/fat/namei_msdos.c @@ -29,6 +29,9 @@ static int msdos_format_name(const unsigned char *name, int len, unsigned char c; int space; + if (len > NAME_MAX) + return -ENAMETOOLONG; + if (name[0] == '.') { /* dotfile because . and .. already done */ if (opts->dotsOK) { /* Get rid of dot - test for it elsewhere */ @@ -262,7 +265,7 @@ static int msdos_add_entry(struct inode *dir, const unsigned char *name, /***** Create a file */ static int msdos_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; struct inode *inode = NULL; diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index e909447873e3..139d3ef4bfae 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -755,7 +755,7 @@ error: } static int vfat_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; struct inode *inode; diff --git a/fs/fs_struct.c b/fs/fs_struct.c index 394875d06fd6..34699f3b6f88 100644 --- a/fs/fs_struct.c +++ b/fs/fs_struct.c @@ -8,6 +8,7 @@ #include <linux/fs_struct.h> #include <linux/init_task.h> #include "internal.h" +#include "mount.h" /* * Replace the fs->{rootmnt,root} with {mnt,dentry}. Put the old values. @@ -60,8 +61,11 @@ void chroot_fs_refs(const struct path *old_root, const struct path *new_root) read_lock(&tasklist_lock); for_each_process_thread(g, p) { + if (p->flags & (PF_KTHREAD | PF_EXITING | PF_DUMPCORE)) + continue; + task_lock(p); - fs = p->fs; + fs = p->real_fs; if (fs) { int hits = 0; write_seqlock(&fs->seq); @@ -89,12 +93,13 @@ void free_fs_struct(struct fs_struct *fs) void exit_fs(struct task_struct *tsk) { - struct fs_struct *fs = tsk->fs; + struct fs_struct *fs = tsk->real_fs; if (fs) { int kill; task_lock(tsk); read_seqlock_excl(&fs->seq); + tsk->real_fs = NULL; tsk->fs = NULL; kill = !--fs->users; read_sequnlock_excl(&fs->seq); @@ -126,7 +131,7 @@ struct fs_struct *copy_fs_struct(struct fs_struct *old) int unshare_fs_struct(void) { - struct fs_struct *fs = current->fs; + struct fs_struct *fs = current->real_fs; struct fs_struct *new_fs = copy_fs_struct(fs); int kill; @@ -135,8 +140,10 @@ int unshare_fs_struct(void) task_lock(current); read_seqlock_excl(&fs->seq); + VFS_WARN_ON_ONCE(fs != current->fs); kill = !--fs->users; current->fs = new_fs; + current->real_fs = new_fs; read_sequnlock_excl(&fs->seq); task_unlock(current); @@ -147,9 +154,99 @@ int unshare_fs_struct(void) } EXPORT_SYMBOL_GPL(unshare_fs_struct); +/* + * PID 1 may choose to stop sharing fs_struct state with us. + * Either via unshare(CLONE_FS) or unshare(CLONE_NEWNS). Of + * course, PID 1 could have chosen to create arbitrary process + * trees that all share fs_struct state via CLONE_FS. This is a + * strong statement: We only care about PID 1 aka the thread-group + * leader so subthread's fs_struct state doesn't matter. + * + * PID 1 unsharing fs_struct state is a bug. PID 1 relies on + * various kthreads to be able to perform work based on its + * fs_struct state. Breaking that contract sucks for both sides. + * So just don't bother with extra work for this. No sane init + * system should ever do this. + * + * On older kernels if PID 1 unshared its filesystem state with us the + * kernel simply used the stale fs_struct state implicitly pinning + * anything that PID 1 had last used. Even if PID 1 might've moved on to + * some completely different fs_struct state and might've even unmounted + * the old root. + * + * This has hilarious consequences: Think continuing to dump coredump + * state into an implicitly pinned directory somewhere. Calling random + * binaries in the old rootfs via usermodehelpers. + * + * Be aggressive about this: We simply reject operating on stale + * fs_struct state by reverting to nullfs. Every kworker that does + * lookups after this point will fail. Every usermodehelper call will + * fail. Tough luck but let's be kind and emit a warning to userspace. + */ +static inline void validate_fs_switch(struct fs_struct *old_fs) +{ + might_sleep(); + + if (likely(current->pid != 1)) + return; + /* @old_fs may be dangling but for comparison it's fine */ + if (old_fs != userspace_init_fs) + return; + pr_warn("VFS: Pid 1 stopped sharing filesystem state\n"); + set_fs_root(userspace_init_fs, &init_fs.root); + set_fs_pwd(userspace_init_fs, &init_fs.root); +} + +struct fs_struct *switch_fs_struct(struct fs_struct *new_fs) +{ + struct fs_struct *fs; + + scoped_guard(task_lock, current) { + fs = current->fs; + VFS_WARN_ON_ONCE(fs != current->real_fs); + read_seqlock_excl(&fs->seq); + current->fs = new_fs; + current->real_fs = new_fs; + if (--fs->users) + new_fs = NULL; + else + new_fs = fs; + read_sequnlock_excl(&fs->seq); + } + + validate_fs_switch(fs); + return new_fs; +} + /* to be mentioned only in INIT_TASK */ struct fs_struct init_fs = { .users = 1, .seq = __SEQLOCK_UNLOCKED(init_fs.seq), .umask = 0022, }; + +struct fs_struct *userspace_init_fs __ro_after_init; +EXPORT_SYMBOL_GPL(userspace_init_fs); + +void __init init_userspace_fs(void) +{ + struct mount *m; + struct path root; + + /* Move PID 1 from nullfs into the initramfs. */ + m = topmost_overmount(current->nsproxy->mnt_ns->root); + root.mnt = &m->mnt; + root.dentry = root.mnt->mnt_root; + + VFS_WARN_ON_ONCE(current->pid != 1); + + set_fs_root(current->fs, &root); + set_fs_pwd(current->fs, &root); + + /* Hold a reference for the global pointer. */ + read_seqlock_excl(¤t->fs->seq); + current->fs->users++; + read_sequnlock_excl(¤t->fs->seq); + + userspace_init_fs = current->fs; +} diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 0e2a1039fa43..d4e0029810c0 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1084,7 +1084,7 @@ static int fuse_mknod(struct mnt_idmap *idmap, struct inode *dir, } static int fuse_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *entry, umode_t mode, bool excl) + struct dentry *entry, umode_t mode) { return fuse_mknod(idmap, dir, entry, mode, 0); } @@ -1117,6 +1117,14 @@ static struct dentry *fuse_mkdir(struct mnt_idmap *idmap, struct inode *dir, if (!fm->fc->dont_mask) mode &= ~current_umask(); + /* + * vfs_mkdir() now passes S_IFDIR in @mode, but @mode is forwarded + * verbatim to the userspace server which has only ever been given the + * permission bits. Strip the type bit until the protocol is known to + * cope with it. + */ + mode &= ~S_IFDIR; + memset(&inarg, 0, sizeof(inarg)); inarg.mode = mode; inarg.umask = current_umask(); diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 8a77794bbd4a..f361876c5583 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -963,15 +963,14 @@ fail: * @dir: The directory in which to create the file * @dentry: The dentry of the new file * @mode: The mode of the new file - * @excl: Force fail if inode exists * * Returns: errno */ static int gfs2_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { - return gfs2_create_inode(dir, dentry, NULL, S_IFREG | mode, 0, NULL, 0, excl); + return gfs2_create_inode(dir, dentry, NULL, S_IFREG | mode, 0, NULL, 0, 1); } /** @@ -1351,7 +1350,7 @@ static struct dentry *gfs2_mkdir(struct mnt_idmap *idmap, struct inode *dir, { unsigned dsize = gfs2_max_stuffed_size(GFS2_I(dir)); - return ERR_PTR(gfs2_create_inode(dir, dentry, NULL, S_IFDIR | mode, 0, NULL, dsize, 0)); + return ERR_PTR(gfs2_create_inode(dir, dentry, NULL, mode, 0, NULL, dsize, 0)); } /** diff --git a/fs/hfs/dir.c b/fs/hfs/dir.c index e13450bb933e..e1f1fb351464 100644 --- a/fs/hfs/dir.c +++ b/fs/hfs/dir.c @@ -184,7 +184,7 @@ static int hfs_dir_release(struct inode *inode, struct file *file) * the directory and the name (and its length) of the new file. */ static int hfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode; int res; @@ -219,7 +219,7 @@ static struct dentry *hfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct inode *inode; int res; - inode = hfs_new_inode(dir, &dentry->d_name, S_IFDIR | mode); + inode = hfs_new_inode(dir, &dentry->d_name, mode); if (IS_ERR(inode)) return ERR_CAST(inode); diff --git a/fs/hfs/super.c b/fs/hfs/super.c index a466c401f6bb..ecdafc658928 100644 --- a/fs/hfs/super.c +++ b/fs/hfs/super.c @@ -82,7 +82,7 @@ void hfs_mark_mdb_dirty(struct super_block *sb) spin_lock(&sbi->work_lock); if (!sbi->work_queued) { delay = msecs_to_jiffies(dirty_writeback_interval * 10); - queue_delayed_work(system_long_wq, &sbi->mdb_work, delay); + queue_delayed_work(system_dfl_long_wq, &sbi->mdb_work, delay); sbi->work_queued = 1; } spin_unlock(&sbi->work_lock); diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c index 8bf6c7cdd9a8..51fcba2e6d40 100644 --- a/fs/hfsplus/dir.c +++ b/fs/hfsplus/dir.c @@ -562,7 +562,7 @@ out: } static int hfsplus_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return hfsplus_mknod(&nop_mnt_idmap, dir, dentry, mode, 0); } @@ -570,7 +570,7 @@ static int hfsplus_create(struct mnt_idmap *idmap, struct inode *dir, static struct dentry *hfsplus_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { - return ERR_PTR(hfsplus_mknod(&nop_mnt_idmap, dir, dentry, mode | S_IFDIR, 0)); + return ERR_PTR(hfsplus_mknod(&nop_mnt_idmap, dir, dentry, mode, 0)); } static int hfsplus_rename(struct mnt_idmap *idmap, diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c index 5777e31de45a..ff7d6b3336a6 100644 --- a/fs/hfsplus/super.c +++ b/fs/hfsplus/super.c @@ -312,7 +312,7 @@ void hfsplus_mark_mdb_dirty(struct super_block *sb) spin_lock(&sbi->work_lock); if (!sbi->work_queued) { delay = msecs_to_jiffies(dirty_writeback_interval * 10); - queue_delayed_work(system_long_wq, &sbi->sync_work, delay); + queue_delayed_work(system_dfl_long_wq, &sbi->sync_work, delay); sbi->work_queued = 1; } spin_unlock(&sbi->work_lock); diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index abe86d72d9ef..7add056d47d8 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -593,7 +593,7 @@ static struct inode *hostfs_iget(struct super_block *sb, char *name) } static int hostfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode; char *name; diff --git a/fs/hpfs/namei.c b/fs/hpfs/namei.c index 353e13a615f5..9446f4038874 100644 --- a/fs/hpfs/namei.c +++ b/fs/hpfs/namei.c @@ -105,10 +105,10 @@ static struct dentry *hpfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, if (!uid_eq(result->i_uid, current_fsuid()) || !gid_eq(result->i_gid, current_fsgid()) || - result->i_mode != (mode | S_IFDIR)) { + result->i_mode != mode) { result->i_uid = current_fsuid(); result->i_gid = current_fsgid(); - result->i_mode = mode | S_IFDIR; + result->i_mode = mode; hpfs_write_inode_nolock(result); } hpfs_update_directory_times(dir); @@ -129,7 +129,7 @@ bail: } static int hpfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { const unsigned char *name = dentry->d_name.name; unsigned len = dentry->d_name.len; diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c index 216e1a0dd0b2..38e9e59f64d8 100644 --- a/fs/hugetlbfs/inode.c +++ b/fs/hugetlbfs/inode.c @@ -971,7 +971,7 @@ static struct dentry *hugetlbfs_mkdir(struct mnt_idmap *idmap, struct inode *dir struct dentry *dentry, umode_t mode) { int retval = hugetlbfs_mknod(idmap, dir, dentry, - mode | S_IFDIR, 0); + mode, 0); if (!retval) inc_nlink(dir); return ERR_PTR(retval); @@ -979,7 +979,7 @@ static struct dentry *hugetlbfs_mkdir(struct mnt_idmap *idmap, struct inode *dir static int hugetlbfs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, bool excl) + umode_t mode) { return hugetlbfs_mknod(idmap, dir, dentry, mode | S_IFREG, 0); } diff --git a/fs/inode.c b/fs/inode.c index 31c5b9ee3a81..a31aa7cb47f6 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -763,21 +763,18 @@ void clear_inode(struct inode *inode) fsverity_cleanup_inode(inode); /* - * We have to cycle the i_pages lock here because reclaim can be in the - * process of removing the last page (in __filemap_remove_folio()) - * and we must not free the mapping under it. + * We have to cycle the i_pages lock here because reclaim + * can be in the process of removing the last page (in + * __filemap_remove_folio()) and we must not free the mapping + * under it. We also remove nodes which are empty; these + * can occur in two different ways. The first is that radix + * tree expansion can fail partway and the second is that THP + * collapse_file() can allocate some temporary nodes and not + * clean them up. */ - xa_lock_irq(&inode->i_data.i_pages); + xa_destroy(&inode->i_data.i_pages); + BUG_ON(inode->i_data.nrpages); - /* - * Almost always, mapping_empty(&inode->i_data) here; but there are - * two known and long-standing ways in which nodes may get left behind - * (when deep radix-tree node allocation failed partway; or when THP - * collapse_file() failed). Until those two known cases are cleaned up, - * or a cleanup function is called here, do not BUG_ON(!mapping_empty), - * nor even WARN_ON(!mapping_empty). - */ - xa_unlock_irq(&inode->i_data.i_pages); BUG_ON(!(inode_state_read_once(inode) & I_FREEING)); BUG_ON(inode_state_read_once(inode) & I_CLEAR); BUG_ON(!list_empty(&inode->i_wb_list)); diff --git a/fs/internal.h b/fs/internal.h index 355d93f92208..174f06357555 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -137,6 +137,7 @@ extern int reconfigure_super(struct fs_context *); extern bool super_trylock_shared(struct super_block *sb); struct super_block *user_get_super(dev_t, bool excl); void put_super(struct super_block *sb); +void __init super_dev_init(void); extern bool mount_capable(struct fs_context *); /* diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index 6d9a2efd4bee..f669fcdca2f0 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -792,7 +792,7 @@ EXPORT_SYMBOL_GPL(iomap_is_partially_uptodate); */ struct folio *iomap_get_folio(struct iomap_iter *iter, loff_t pos, size_t len) { - fgf_t fgp = FGP_WRITEBEGIN | FGP_NOFS; + fgf_t fgp = FGP_WRITEBEGIN; if (iter->flags & IOMAP_NOWAIT) fgp |= FGP_NOWAIT; @@ -1182,7 +1182,6 @@ static bool iomap_write_end(struct iomap_iter *iter, size_t len, size_t copied, static int iomap_write_iter(struct iomap_iter *iter, struct iov_iter *i, const struct iomap_write_ops *write_ops) { - ssize_t total_written = 0; int status = 0; struct address_space *mapping = iter->inode->i_mapping; size_t chunk = mapping_max_folio_size(mapping); @@ -1278,12 +1277,11 @@ retry: goto retry; } } else { - total_written += written; iomap_iter_advance(iter, written); } } while (iov_iter_count(i) && iomap_length(iter)); - return total_written ? 0 : status; + return status; } ssize_t diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c index e2cd5f92babe..64c6b7286c20 100644 --- a/fs/iomap/direct-io.c +++ b/fs/iomap/direct-io.c @@ -10,6 +10,7 @@ #include <linux/iomap.h> #include <linux/task_io_accounting_ops.h> #include <linux/fserror.h> +#include <linux/init.h> #include "internal.h" #include "trace.h" @@ -88,9 +89,9 @@ static inline enum fserror_type iomap_dio_err_type(const struct iomap_dio *dio) return FSERR_DIRECTIO_READ; } -static inline bool should_report_dio_fserror(const struct iomap_dio *dio) +static inline bool should_report_dio_fserror(int error) { - switch (dio->error) { + switch (error) { case 0: case -EAGAIN: case -ENOTBLK: @@ -110,7 +111,7 @@ ssize_t iomap_dio_complete(struct iomap_dio *dio) if (dops && dops->end_io) ret = dops->end_io(iocb, dio->size, ret, dio->flags); - if (should_report_dio_fserror(dio)) + if (should_report_dio_fserror(dio->error)) fserror_report_io(file_inode(iocb->ki_filp), iomap_dio_err_type(dio), offset, dio->size, dio->error, GFP_NOFS); @@ -403,6 +404,14 @@ out_put_bio: return ret; } +static inline unsigned int iomap_dio_alignment(struct inode *inode, + struct block_device *bdev, unsigned int dio_flags) +{ + if (dio_flags & IOMAP_DIO_FSBLOCK_ALIGNED) + return i_blocksize(inode); + return bdev_logical_block_size(bdev); +} + static int iomap_dio_bio_iter(struct iomap_iter *iter, struct iomap_dio *dio) { const struct iomap *iomap = &iter->iomap; @@ -421,10 +430,7 @@ static int iomap_dio_bio_iter(struct iomap_iter *iter, struct iomap_dio *dio) * File systems that write out of place and always allocate new blocks * need each bio to be block aligned as that's the unit of allocation. */ - if (dio->flags & IOMAP_DIO_FSBLOCK_ALIGNED) - alignment = fs_block_size; - else - alignment = bdev_logical_block_size(iomap->bdev); + alignment = iomap_dio_alignment(inode, iomap->bdev, dio->flags); if ((pos | length) & (alignment - 1)) return -EINVAL; @@ -893,12 +899,277 @@ out_free_dio: } EXPORT_SYMBOL_GPL(__iomap_dio_rw); +struct iomap_dio_simple { + struct kiocb *iocb; + size_t size; + unsigned int dio_flags; + struct work_struct work; + /* + * Align @bio to a cacheline boundary so that, combined with the + * front_pad passed to bioset_init(), the bio sits at the start of + * a cacheline in memory returned by the (HWCACHE-aligned) bio + * slab. This keeps the hot fields block layer touches on submit + * and completion (bi_iter, bi_status, ...) within a single line. + */ + struct bio bio ____cacheline_aligned_in_smp; +}; + +static struct bio_set iomap_dio_simple_pool; + +static ssize_t iomap_dio_simple_complete(struct iomap_dio_simple *sr) +{ + struct bio *bio = &sr->bio; + struct kiocb *iocb = sr->iocb; + struct inode *inode = file_inode(iocb->ki_filp); + ssize_t ret; + + if (unlikely(bio->bi_status)) { + ret = blk_status_to_errno(bio->bi_status); + if (should_report_dio_fserror(ret)) + fserror_report_io(inode, FSERR_DIRECTIO_READ, + iocb->ki_pos, sr->size, ret, + GFP_NOFS); + } else { + ret = sr->size; + iocb->ki_pos += ret; + } + + if (sr->dio_flags & IOMAP_DIO_USER_BACKED) { + bio_check_pages_dirty(bio); + } else { + bio_release_pages(bio, false); + bio_put(bio); + } + inode_dio_end(inode); + trace_iomap_dio_complete(iocb, ret < 0 ? ret : 0, ret); + return ret; +} + +static void iomap_dio_simple_complete_work(struct work_struct *work) +{ + struct iomap_dio_simple *sr = + container_of(work, struct iomap_dio_simple, work); + struct kiocb *iocb = sr->iocb; + + WRITE_ONCE(iocb->private, NULL); + iocb->ki_complete(iocb, iomap_dio_simple_complete(sr)); +} + +static void iomap_dio_simple_end_io(struct bio *bio) +{ + struct iomap_dio_simple *sr = + container_of(bio, struct iomap_dio_simple, bio); + struct kiocb *iocb = sr->iocb; + + if (unlikely(sr->bio.bi_status)) { + struct inode *inode = file_inode(iocb->ki_filp); + + INIT_WORK(&sr->work, iomap_dio_simple_complete_work); + queue_work(inode->i_sb->s_dio_done_wq, &sr->work); + return; + } + + WRITE_ONCE(iocb->private, NULL); + iocb->ki_complete(iocb, iomap_dio_simple_complete(sr)); +} + +static inline bool +iomap_dio_simple_supported(struct kiocb *iocb, struct iov_iter *iter, + const struct iomap_dio_ops *dops, + unsigned int dio_flags, size_t done_before) +{ + struct inode *inode = file_inode(iocb->ki_filp); + size_t count = iov_iter_count(iter); + + if (dops || done_before) + return false; + if (iov_iter_rw(iter) != READ) + return false; + if (!count) + return false; + /* + * Simple dio is an optimization for small IO. Filter out large IO + * early as it's the most common case to fail for typical direct IO + * workloads. + */ + if (count > inode->i_sb->s_blocksize) + return false; + if (dio_flags & (IOMAP_DIO_FORCE_WAIT | IOMAP_DIO_PARTIAL | + IOMAP_DIO_BOUNCE)) + return false; + if (iocb->ki_pos + count > i_size_read(inode)) + return false; + if (IS_ENCRYPTED(inode)) + return false; + + return true; +} + +/* + * Fast path for small, block-aligned direct I/Os that map to a single + * contiguous on-disk extent. + * + * iomap_dio_simple_supported() enforces the cheap up-front constraints before + * entering this path. + * + * @dops must be NULL: a non-NULL @dops means the caller wants its + * ->end_io / ->submit_io hooks invoked, and in particular wants its bios to be + * allocated from the filesystem-private @dops->bio_set (whose front_pad sizes a + * filesystem-private wrapper around the bio). The fast path instead allocates + * from the shared iomap_dio_simple_pool, whose front_pad matches struct + * iomap_dio_simple; the two wrappers are not interchangeable, so we must fall + * back to __iomap_dio_rw() in that case. + * + * @done_before must be zero: a non-zero caller-accumulated residual cannot be + * carried through a single-bio inline completion. + * + * @iter must describe a non-empty READ no larger than the inode block size: + * writes, zero-length I/O, and larger requests need the generic iomap direct + * I/O path. + * + * @dio_flags must not request IOMAP_DIO_FORCE_WAIT, IOMAP_DIO_PARTIAL, or + * IOMAP_DIO_BOUNCE: this path does not support forced waiting, partial direct + * I/O, or bouncing. The range must also stay within i_size and encrypted + * inodes must use the generic iomap direct I/O path. + * + * -ENOTBLK is the private sentinel returned by iomap_dio_simple() when it + * decides the request does not fit the fast path. In that case we proceed to + * the generic __iomap_dio_rw() slow path. Any other errno is a real result and + * is propagated as-is, in particular -EAGAIN for IOCB_NOWAIT must reach the + * caller. + */ +static ssize_t +iomap_dio_simple(struct kiocb *iocb, struct iov_iter *iter, + const struct iomap_ops *ops, void *private, + unsigned int dio_flags) +{ + struct inode *inode = file_inode(iocb->ki_filp); + size_t count = iov_iter_count(iter); + bool wait_for_completion = is_sync_kiocb(iocb); + struct iomap_iter iomi = { + .inode = inode, + .pos = iocb->ki_pos, + .len = count, + .flags = IOMAP_DIRECT, + .private = private, + }; + struct iomap_dio_simple *sr; + unsigned int alignment; + struct bio *bio; + ssize_t ret; + + if (iocb->ki_flags & IOCB_NOWAIT) + iomi.flags |= IOMAP_NOWAIT; + + ret = kiocb_write_and_wait(iocb, count); + if (ret) + return ret; + + inode_dio_begin(inode); + + ret = ops->iomap_begin(inode, iomi.pos, count, iomi.flags, + &iomi.iomap, &iomi.srcmap); + if (ret) { + inode_dio_end(inode); + return ret; + } + + if (iomi.iomap.type != IOMAP_MAPPED || + iomi.iomap.offset + iomi.iomap.length < iomi.pos + count || + (iomi.iomap.flags & IOMAP_F_INTEGRITY)) { + ret = -ENOTBLK; + goto out_iomap_end; + } + + alignment = iomap_dio_alignment(inode, iomi.iomap.bdev, dio_flags); + if ((iomi.pos | count) & (alignment - 1)) { + ret = -EINVAL; + goto out_iomap_end; + } + + if (!wait_for_completion && unlikely(!inode->i_sb->s_dio_done_wq)) { + ret = sb_init_dio_done_wq(inode->i_sb); + if (ret < 0) + goto out_iomap_end; + } + + trace_iomap_dio_rw_begin(iocb, iter, dio_flags, 0); + + if (user_backed_iter(iter)) + dio_flags |= IOMAP_DIO_USER_BACKED; + + bio = bio_alloc_bioset(iomi.iomap.bdev, + bio_iov_vecs_to_alloc(iter, BIO_MAX_VECS), + REQ_OP_READ, GFP_KERNEL, &iomap_dio_simple_pool); + sr = container_of(bio, struct iomap_dio_simple, bio); + sr->iocb = iocb; + sr->dio_flags = dio_flags; + + bio->bi_iter.bi_sector = iomap_sector(&iomi.iomap, iomi.pos); + bio->bi_ioprio = iocb->ki_ioprio; + + ret = bio_iov_iter_get_pages(bio, iter, alignment - 1); + if (unlikely(ret)) + goto out_bio_put; + + if (bio->bi_iter.bi_size != count) { + iov_iter_revert(iter, bio->bi_iter.bi_size); + ret = -ENOTBLK; + goto out_bio_release_pages; + } + + sr->size = bio->bi_iter.bi_size; + + if (dio_flags & IOMAP_DIO_USER_BACKED) + bio_set_pages_dirty(bio); + + if (iocb->ki_flags & IOCB_NOWAIT) + bio->bi_opf |= REQ_NOWAIT; + if ((iocb->ki_flags & IOCB_HIPRI) && !wait_for_completion) { + bio->bi_opf |= REQ_POLLED; + WRITE_ONCE(iocb->private, bio); + } + + if (ops->iomap_end) + ops->iomap_end(inode, iomi.pos, count, count, iomi.flags, + &iomi.iomap); + + if (!wait_for_completion) { + bio->bi_end_io = iomap_dio_simple_end_io; + submit_bio(bio); + trace_iomap_dio_rw_queued(inode, iomi.pos, count); + return -EIOCBQUEUED; + } + + submit_bio_wait(bio); + return iomap_dio_simple_complete(sr); + +out_bio_release_pages: + bio_release_pages(bio, false); +out_bio_put: + bio_put(bio); +out_iomap_end: + if (ops->iomap_end) + ops->iomap_end(inode, iomi.pos, count, 0, iomi.flags, + &iomi.iomap); + inode_dio_end(inode); + return ret; +} + ssize_t iomap_dio_rw(struct kiocb *iocb, struct iov_iter *iter, const struct iomap_ops *ops, const struct iomap_dio_ops *dops, unsigned int dio_flags, void *private, size_t done_before) { struct iomap_dio *dio; + ssize_t ret; + + if (iomap_dio_simple_supported(iocb, iter, dops, dio_flags, + done_before)) { + ret = iomap_dio_simple(iocb, iter, ops, private, dio_flags); + if (ret != -ENOTBLK) + return ret; + } dio = __iomap_dio_rw(iocb, iter, ops, dops, dio_flags, private, done_before); @@ -907,3 +1178,11 @@ iomap_dio_rw(struct kiocb *iocb, struct iov_iter *iter, return iomap_dio_complete(dio); } EXPORT_SYMBOL_GPL(iomap_dio_rw); + +static int __init iomap_dio_init(void) +{ + return bioset_init(&iomap_dio_simple_pool, 4, + offsetof(struct iomap_dio_simple, bio), + BIOSET_NEED_BVECS | BIOSET_PERCPU_CACHE); +} +fs_initcall(iomap_dio_init); diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c index 30468d51b5ad..fb636dce43af 100644 --- a/fs/iomap/ioend.c +++ b/fs/iomap/ioend.c @@ -13,6 +13,7 @@ struct bio_set iomap_ioend_bioset; EXPORT_SYMBOL_GPL(iomap_ioend_bioset); +static struct bio_set iomap_ioend_split_bioset; struct iomap_ioend *iomap_init_ioend(struct inode *inode, struct bio *bio, loff_t file_offset, u16 ioend_flags) @@ -488,7 +489,8 @@ struct iomap_ioend *iomap_split_ioend(struct iomap_ioend *ioend, sector_offset = ALIGN_DOWN(sector_offset << SECTOR_SHIFT, i_blocksize(ioend->io_inode)) >> SECTOR_SHIFT; - split = bio_split(bio, sector_offset, GFP_NOFS, &iomap_ioend_bioset); + split = bio_split(bio, sector_offset, GFP_NOFS, + &iomap_ioend_split_bioset); if (IS_ERR(split)) return ERR_CAST(split); split->bi_private = bio->bi_private; @@ -511,8 +513,23 @@ EXPORT_SYMBOL_GPL(iomap_split_ioend); static int __init iomap_ioend_init(void) { - return bioset_init(&iomap_ioend_bioset, 4 * (PAGE_SIZE / SECTOR_SIZE), + const unsigned int nr_mempool_entries = 4 * (PAGE_SIZE / SECTOR_SIZE); + int error; + + error = bioset_init(&iomap_ioend_bioset, nr_mempool_entries, offsetof(struct iomap_ioend, io_bio), BIOSET_NEED_BVECS); + if (error) + return error; + error = bioset_init(&iomap_ioend_split_bioset, nr_mempool_entries, + offsetof(struct iomap_ioend, io_bio), + BIOSET_NEED_BVECS); + if (error) + goto out_exit_ioend_bioset; + return 0; + +out_exit_ioend_bioset: + bioset_exit(&iomap_ioend_bioset); + return error; } fs_initcall(iomap_ioend_init); diff --git a/fs/jffs2/dir.c b/fs/jffs2/dir.c index c4088c3b4ac0..656c920864c5 100644 --- a/fs/jffs2/dir.c +++ b/fs/jffs2/dir.c @@ -26,7 +26,7 @@ static int jffs2_readdir (struct file *, struct dir_context *); static int jffs2_create (struct mnt_idmap *, struct inode *, - struct dentry *, umode_t, bool); + struct dentry *, umode_t); static struct dentry *jffs2_lookup (struct inode *,struct dentry *, unsigned int); static int jffs2_link (struct dentry *,struct inode *,struct dentry *); @@ -163,7 +163,7 @@ static int jffs2_readdir(struct file *file, struct dir_context *ctx) static int jffs2_create(struct mnt_idmap *idmap, struct inode *dir_i, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct jffs2_raw_inode *ri; struct jffs2_inode_info *f, *dir_f; @@ -462,8 +462,6 @@ static struct dentry *jffs2_mkdir (struct mnt_idmap *idmap, struct inode *dir_i, uint32_t alloclen; int ret; - mode |= S_IFDIR; - ri = jffs2_alloc_raw_inode(); if (!ri) return ERR_PTR(-ENOMEM); diff --git a/fs/jffs2/wbuf.c b/fs/jffs2/wbuf.c index 8ff7a0b6add2..3b7803c75d58 100644 --- a/fs/jffs2/wbuf.c +++ b/fs/jffs2/wbuf.c @@ -1177,7 +1177,7 @@ void jffs2_dirty_trigger(struct jffs2_sb_info *c) return; delay = msecs_to_jiffies(dirty_writeback_interval * 10); - if (queue_delayed_work(system_long_wq, &c->wbuf_dwork, delay)) + if (queue_delayed_work(system_dfl_long_wq, &c->wbuf_dwork, delay)) jffs2_dbg(1, "%s()\n", __func__); } diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c index 442d62679262..8a36c218f0f7 100644 --- a/fs/jfs/namei.c +++ b/fs/jfs/namei.c @@ -61,7 +61,7 @@ static inline void free_ea_wmap(struct inode *inode) * */ static int jfs_create(struct mnt_idmap *idmap, struct inode *dip, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { int rc = 0; tid_t tid; /* transaction id */ @@ -223,7 +223,7 @@ static struct dentry *jfs_mkdir(struct mnt_idmap *idmap, struct inode *dip, * block there while holding dtree page, so we allocate the inode & * begin the transaction before we search the directory. */ - ip = ialloc(dip, S_IFDIR | mode); + ip = ialloc(dip, mode); if (IS_ERR(ip)) { rc = PTR_ERR(ip); goto out2; diff --git a/fs/kernel_read_file.c b/fs/kernel_read_file.c index de32c95d823d..9c2ba9240083 100644 --- a/fs/kernel_read_file.c +++ b/fs/kernel_read_file.c @@ -150,18 +150,13 @@ ssize_t kernel_read_file_from_path_initns(const char *path, loff_t offset, enum kernel_read_file_id id) { struct file *file; - struct path root; ssize_t ret; if (!path || !*path) return -EINVAL; - task_lock(&init_task); - get_fs_root(init_task.fs, &root); - task_unlock(&init_task); - - file = file_open_root(&root, path, O_RDONLY, 0); - path_put(&root); + scoped_with_init_fs() + file = filp_open(path, O_RDONLY, 0); if (IS_ERR(file)) return PTR_ERR(file); diff --git a/fs/minix/namei.c b/fs/minix/namei.c index 263e4ba8b1c8..5525ba367ed7 100644 --- a/fs/minix/namei.c +++ b/fs/minix/namei.c @@ -64,7 +64,7 @@ static int minix_tmpfile(struct mnt_idmap *idmap, struct inode *dir, } static int minix_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return minix_mknod(&nop_mnt_idmap, dir, dentry, mode, 0); } @@ -110,7 +110,7 @@ static struct dentry *minix_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct inode * inode; int err; - inode = minix_new_inode(dir, S_IFDIR | mode); + inode = minix_new_inode(dir, mode); if (IS_ERR(inode)) return ERR_CAST(inode); diff --git a/fs/namei.c b/fs/namei.c index 19ce43c9a6e6..e4ef48583a3b 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4140,11 +4140,6 @@ EXPORT_SYMBOL(end_renaming); * after setgid stripping allows the same ordering for both non-POSIX ACL and * POSIX ACL supporting filesystems. * - * Note that it's currently valid for @type to be 0 if a directory is created. - * Filesystems raise that flag individually and we need to check whether each - * filesystem can deal with receiving S_IFDIR from the vfs before we enforce a - * non-zero type. - * * Returns: mode to be passed to the filesystem */ static inline umode_t vfs_prepare_mode(struct mnt_idmap *idmap, @@ -4190,7 +4185,7 @@ int vfs_create(struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode, return error; if (!dir->i_op->create) - return -EACCES; /* shouldn't it be ENOSYS? */ + return -EOPNOTSUPP; mode = vfs_prepare_mode(idmap, dir, mode, S_IALLUGO, S_IFREG); error = security_inode_create(dir, dentry, mode); @@ -4199,7 +4194,7 @@ int vfs_create(struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode, error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di); if (error) return error; - error = dir->i_op->create(idmap, dir, dentry, mode, true); + error = dir->i_op->create(idmap, dir, dentry, mode); if (!error) fsnotify_create(dir, dentry); return error; @@ -4501,12 +4496,11 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file, file->f_mode |= FMODE_CREATED; audit_inode_child(dir_inode, dentry, AUDIT_TYPE_CHILD_CREATE); if (!dir_inode->i_op->create) { - error = -EACCES; + error = -EOPNOTSUPP; goto out_dput; } - error = dir_inode->i_op->create(idmap, dir_inode, dentry, - mode, open_flag & O_EXCL); + error = dir_inode->i_op->create(idmap, dir_inode, dentry, mode); if (error) goto out_dput; } @@ -5117,7 +5111,7 @@ int vfs_mknod(struct mnt_idmap *idmap, struct inode *dir, return -EPERM; if (!dir->i_op->mknod) - return -EPERM; + return -EOPNOTSUPP; mode = vfs_prepare_mode(idmap, dir, mode, mode, mode); error = devcgroup_inode_mknod(mode, dev); @@ -5256,11 +5250,11 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, if (error) goto err; - error = -EPERM; + error = -EOPNOTSUPP; if (!dir->i_op->mkdir) goto err; - mode = vfs_prepare_mode(idmap, dir, mode, S_IRWXUGO | S_ISVTX, 0); + mode = vfs_prepare_mode(idmap, dir, mode, S_IRWXUGO | S_ISVTX, S_IFDIR); error = security_inode_mkdir(dir, dentry, mode); if (error) goto err; @@ -5360,7 +5354,7 @@ int vfs_rmdir(struct mnt_idmap *idmap, struct inode *dir, return error; if (!dir->i_op->rmdir) - return -EPERM; + return -EOPNOTSUPP; dget(dentry); inode_lock(dentry->d_inode); @@ -5496,7 +5490,7 @@ int vfs_unlink(struct mnt_idmap *idmap, struct inode *dir, return error; if (!dir->i_op->unlink) - return -EPERM; + return -EOPNOTSUPP; inode_lock(target); if (IS_SWAPFILE(target)) @@ -5647,7 +5641,7 @@ int vfs_symlink(struct mnt_idmap *idmap, struct inode *dir, return error; if (!dir->i_op->symlink) - return -EPERM; + return -EOPNOTSUPP; error = security_inode_symlink(dir, dentry, oldname); if (error) @@ -5769,7 +5763,7 @@ int vfs_link(struct dentry *old_dentry, struct mnt_idmap *idmap, if (HAS_UNMAPPED_ID(idmap, inode)) return -EPERM; if (!dir->i_op->link) - return -EPERM; + return -EOPNOTSUPP; if (S_ISDIR(inode->i_mode)) return -EPERM; @@ -5978,7 +5972,7 @@ int vfs_rename(struct renamedata *rd) return error; if (!old_dir->i_op->rename) - return -EPERM; + return -EOPNOTSUPP; /* * If we are going to change the parent - check write permissions, diff --git a/fs/namespace.c b/fs/namespace.c index 3d5cd5bf3b05..6b81b77f9303 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -2908,6 +2908,9 @@ static int do_change_type(const struct path *path, int ms_flags) for (m = mnt; m; m = (recurse ? next_mnt(m, mnt) : NULL)) change_mnt_propagation(m, type); + guard(mount_locked_reader)(); + touch_mnt_namespace(mnt->mnt_ns); + return 0; } @@ -3481,6 +3484,10 @@ static int do_set_group(const struct path *from_path, const struct path *to_path list_add(&to->mnt_share, &from->mnt_share); set_mnt_shared(to); } + + guard(mount_locked_reader)(); + touch_mnt_namespace(to->mnt_ns); + return 0; } @@ -6184,12 +6191,14 @@ static void __init init_mount_tree(void) struct path root; /* - * We create two mounts: + * We create three mounts: * * (1) nullfs with mount id 1 * (2) mutable rootfs with mount id 2 + * (3) private nullfs for kthreads (SB_KERNMOUNT) * - * with (2) mounted on top of (1). + * with (2) mounted on top of (1). The init_task's root and pwd + * are pointed at (3) so all kthreads start isolated in nullfs. */ nullfs_mnt = vfs_kern_mount(&nullfs_fs_type, 0, "nullfs", NULL); if (IS_ERR(nullfs_mnt)) @@ -6229,12 +6238,14 @@ static void __init init_mount_tree(void) init_mnt_ns.nr_mounts++; } + nullfs_mnt = kern_mount(&nullfs_fs_type); + if (IS_ERR(nullfs_mnt)) + panic("VFS: Failed to create private nullfs instance"); + root.mnt = nullfs_mnt; + root.dentry = nullfs_mnt->mnt_root; + init_task.nsproxy->mnt_ns = &init_mnt_ns; get_mnt_ns(&init_mnt_ns); - - /* The root and pwd always point to the mutable rootfs. */ - root.mnt = mnt; - root.dentry = mnt->mnt_root; set_fs_pwd(current->fs, &root); set_fs_root(current->fs, &root); @@ -6262,6 +6273,8 @@ void __init mnt_init(void) if (!mount_hashtable || !mountpoint_hashtable) panic("Failed to allocate mount hash table\n"); + super_dev_init(); + kernfs_init(); err = sysfs_init(); diff --git a/fs/nfs/blocklayout/dev.c b/fs/nfs/blocklayout/dev.c index bb35f88501ce..368d20daf67b 100644 --- a/fs/nfs/blocklayout/dev.c +++ b/fs/nfs/blocklayout/dev.c @@ -4,6 +4,7 @@ */ #include <linux/sunrpc/svc.h> #include <linux/blkdev.h> +#include <linux/fs_struct.h> #include <linux/nfs4.h> #include <linux/nfs_fs.h> #include <linux/nfs_xdr.h> @@ -363,15 +364,22 @@ static struct file * bl_open_path(struct pnfs_block_volume *v, const char *prefix) { struct file *bdev_file; - const char *devname; + const char *devname __free(kfree) = NULL; devname = kasprintf(GFP_KERNEL, "/dev/disk/by-id/%s%*phN", prefix, v->scsi.designator_len, v->scsi.designator); if (!devname) return ERR_PTR(-ENOMEM); - bdev_file = bdev_file_open_by_path(devname, - BLK_OPEN_READ | BLK_OPEN_WRITE, NULL, NULL); + if (tsk_is_kthread(current)) { + scoped_with_init_fs() + bdev_file = bdev_file_open_by_path(devname, + BLK_OPEN_READ | BLK_OPEN_WRITE, + NULL, NULL); + } else { + bdev_file = bdev_file_open_by_path(devname, + BLK_OPEN_READ | BLK_OPEN_WRITE, NULL, NULL); + } if (IS_ERR(bdev_file)) { dprintk("failed to open device %s (%ld)\n", devname, PTR_ERR(bdev_file)); @@ -380,7 +388,6 @@ bl_open_path(struct pnfs_block_volume *v, const char *prefix) file_bdev(bdev_file)->bd_disk->disk_name); } - kfree(devname); return bdev_file; } diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index c7caffb31935..36f2e8588922 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2427,9 +2427,9 @@ out_err: } int nfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { - return nfs_do_create(dir, dentry, mode, excl ? O_EXCL : 0); + return nfs_do_create(dir, dentry, mode, O_EXCL); } EXPORT_SYMBOL_GPL(nfs_create); @@ -2474,7 +2474,7 @@ struct dentry *nfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, dir->i_sb->s_id, dir->i_ino, dentry); attr.ia_valid = ATTR_MODE; - attr.ia_mode = mode | S_IFDIR; + attr.ia_mode = mode; trace_nfs_mkdir_enter(dir, dentry); ret = NFS_PROTO(dir)->mkdir(dir, dentry, &attr); diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index e4533f583632..7f96a258af76 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -395,7 +395,7 @@ extern unsigned long nfs_access_cache_scan(struct shrinker *shrink, struct dentry *nfs_lookup(struct inode *, struct dentry *, unsigned int); void nfs_d_prune_case_insensitive_aliases(struct inode *inode); int nfs_create(struct mnt_idmap *, struct inode *, struct dentry *, - umode_t, bool); + umode_t); struct dentry *nfs_mkdir(struct mnt_idmap *, struct inode *, struct dentry *, umode_t); int nfs_rmdir(struct inode *, struct dentry *); diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c index e2fe95de3d71..77c5f7f74fbf 100644 --- a/fs/nilfs2/namei.c +++ b/fs/nilfs2/namei.c @@ -86,7 +86,7 @@ nilfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) * with d_instantiate(). */ static int nilfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode; struct nilfs_transaction_info ti; @@ -231,7 +231,7 @@ static struct dentry *nilfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, inc_nlink(dir); - inode = nilfs_new_inode(dir, S_IFDIR | mode); + inode = nilfs_new_inode(dir, mode); err = PTR_ERR(inode); if (IS_ERR(inode)) goto out_dir; diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index aee7aabaf059..0de5b63ef407 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -737,7 +737,7 @@ err_out: } static int ntfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct ntfs_volume *vol = NTFS_SB(dir->i_sb); struct ntfs_inode *ni; @@ -1083,7 +1083,7 @@ static struct dentry *ntfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, if (!(vol->vol_flags & VOLUME_IS_DIRTY)) ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); - ni = __ntfs_create(idmap, dir, uname, uname_len, S_IFDIR | mode, 0, NULL, 0); + ni = __ntfs_create(idmap, dir, uname, uname_len, mode, 0, NULL, 0); kmem_cache_free(ntfs_name_cache, uname); if (IS_ERR(ni)) { err = PTR_ERR(ni); diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c index 937d349e841d..7bf889a4ca35 100644 --- a/fs/ntfs3/namei.c +++ b/fs/ntfs3/namei.c @@ -110,7 +110,7 @@ static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry, * ntfs_create - inode_operations::create */ static int ntfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return ntfs_create_inode(idmap, dir, dentry, NULL, S_IFREG | mode, 0, NULL, 0, NULL); @@ -218,7 +218,7 @@ static struct dentry *ntfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return ERR_PTR(ntfs_create_inode(idmap, dir, dentry, NULL, - S_IFDIR | mode, 0, NULL, 0, NULL)); + mode, 0, NULL, 0, NULL)); } /* diff --git a/fs/nullfs.c b/fs/nullfs.c index fdbd3e5d3d71..e06352c7b2cc 100644 --- a/fs/nullfs.c +++ b/fs/nullfs.c @@ -4,6 +4,8 @@ #include <linux/fs_context.h> #include <linux/magic.h> +#include "mount.h" + static const struct super_operations nullfs_super_operations = { .statfs = simple_statfs, }; @@ -40,14 +42,9 @@ static int nullfs_fs_fill_super(struct super_block *s, struct fs_context *fc) return 0; } -/* - * For now this is a single global instance. If needed we can make it - * mountable by userspace at which point we will need to make it - * multi-instance. - */ static int nullfs_fs_get_tree(struct fs_context *fc) { - return get_tree_single(fc, nullfs_fs_fill_super); + return get_tree_nodev(fc, nullfs_fs_fill_super); } static const struct fs_context_operations nullfs_fs_context_ops = { @@ -57,9 +54,8 @@ static const struct fs_context_operations nullfs_fs_context_ops = { static int nullfs_init_fs_context(struct fs_context *fc) { fc->ops = &nullfs_fs_context_ops; - fc->global = true; - fc->sb_flags = SB_NOUSER; - fc->s_iflags = SB_I_NOEXEC | SB_I_NODEV; + fc->sb_flags |= SB_NOUSER; + fc->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; return 0; } diff --git a/fs/ocfs2/dlmfs/dlmfs.c b/fs/ocfs2/dlmfs/dlmfs.c index 5821e33df78f..53df5dd10ad0 100644 --- a/fs/ocfs2/dlmfs/dlmfs.c +++ b/fs/ocfs2/dlmfs/dlmfs.c @@ -422,7 +422,7 @@ static struct dentry *dlmfs_mkdir(struct mnt_idmap * idmap, goto bail; } - inode = dlmfs_get_inode(dir, dentry, mode | S_IFDIR); + inode = dlmfs_get_inode(dir, dentry, mode); if (!inode) { status = -ENOMEM; mlog_errno(status); @@ -453,8 +453,7 @@ bail: static int dlmfs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, - bool excl) + umode_t mode) { int status = 0; struct inode *inode; diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index 1277666c77cd..b23dd678a7e0 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -657,7 +657,7 @@ static struct dentry *ocfs2_mkdir(struct mnt_idmap *idmap, trace_ocfs2_mkdir(dir, dentry, dentry->d_name.len, dentry->d_name.name, OCFS2_I(dir)->ip_blkno, mode); - ret = ocfs2_mknod(&nop_mnt_idmap, dir, dentry, mode | S_IFDIR, 0); + ret = ocfs2_mknod(&nop_mnt_idmap, dir, dentry, mode, 0); if (ret) mlog_errno(ret); @@ -667,8 +667,7 @@ static struct dentry *ocfs2_mkdir(struct mnt_idmap *idmap, static int ocfs2_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, - bool excl) + umode_t mode) { int ret; diff --git a/fs/ocfs2/super.c b/fs/ocfs2/super.c index bcac614ff02a..c62e389d4dd6 100644 --- a/fs/ocfs2/super.c +++ b/fs/ocfs2/super.c @@ -1882,7 +1882,6 @@ static void ocfs2_dismount_volume(struct super_block *sb, int mnt_err) ocfs2_delete_osb(osb); kfree(osb); - sb->s_dev = 0; sb->s_fs_info = NULL; } diff --git a/fs/omfs/dir.c b/fs/omfs/dir.c index 2ed541fccf33..692297cf84e7 100644 --- a/fs/omfs/dir.c +++ b/fs/omfs/dir.c @@ -282,11 +282,11 @@ out_free_inode: static struct dentry *omfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { - return ERR_PTR(omfs_add_node(dir, dentry, mode | S_IFDIR)); + return ERR_PTR(omfs_add_node(dir, dentry, mode)); } static int omfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return omfs_add_node(dir, dentry, mode | S_IFREG); } diff --git a/fs/orangefs/namei.c b/fs/orangefs/namei.c index 75e65e72c2d6..8ebc34e112d5 100644 --- a/fs/orangefs/namei.c +++ b/fs/orangefs/namei.c @@ -18,8 +18,7 @@ static int orangefs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, - bool exclusive) + umode_t mode) { struct orangefs_inode_s *parent = ORANGEFS_I(dir); struct orangefs_kernel_op_s *new_op; @@ -333,7 +332,7 @@ static struct dentry *orangefs_mkdir(struct mnt_idmap *idmap, struct inode *dir, ref = new_op->downcall.resp.mkdir.refn; - inode = orangefs_new_inode(dir->i_sb, dir, S_IFDIR | mode, 0, &ref); + inode = orangefs_new_inode(dir->i_sb, dir, mode, 0, &ref); if (IS_ERR(inode)) { gossip_err("*** Failed to allocate orangefs dir inode\n"); ret = PTR_ERR(inode); diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index a033743dbf51..610c0116252c 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -689,8 +689,8 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, return err; } -static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, - const char *link) +static int ovl_create_object(struct mnt_idmap *idmap, struct dentry *dentry, + int mode, dev_t rdev, const char *link) { int err; struct inode *inode; @@ -717,7 +717,7 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, inode_state_set(inode, I_CREATING); spin_unlock(&inode->i_lock); - inode_init_owner(&nop_mnt_idmap, inode, dentry->d_parent->d_inode, mode); + inode_init_owner(idmap, inode, dentry->d_parent->d_inode, mode); attr.mode = inode->i_mode; err = ovl_create_or_link(dentry, inode, &attr, false); @@ -732,15 +732,15 @@ out: } static int ovl_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { - return ovl_create_object(dentry, (mode & 07777) | S_IFREG, 0, NULL); + return ovl_create_object(idmap, dentry, (mode & 07777) | S_IFREG, 0, NULL); } static struct dentry *ovl_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { - return ERR_PTR(ovl_create_object(dentry, (mode & 07777) | S_IFDIR, 0, NULL)); + return ERR_PTR(ovl_create_object(idmap, dentry, (mode & 07777) | S_IFDIR, 0, NULL)); } static int ovl_mknod(struct mnt_idmap *idmap, struct inode *dir, @@ -750,13 +750,13 @@ static int ovl_mknod(struct mnt_idmap *idmap, struct inode *dir, if (S_ISCHR(mode) && rdev == WHITEOUT_DEV) return -EPERM; - return ovl_create_object(dentry, mode, rdev, NULL); + return ovl_create_object(idmap, dentry, mode, rdev, NULL); } static int ovl_symlink(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *link) { - return ovl_create_object(dentry, S_IFLNK, 0, link); + return ovl_create_object(idmap, dentry, S_IFLNK, 0, link); } static int ovl_set_link_redirect(struct dentry *dentry) @@ -1444,7 +1444,7 @@ static int ovl_tmpfile(struct mnt_idmap *idmap, struct inode *dir, if (!inode) goto drop_write; - inode_init_owner(&nop_mnt_idmap, inode, dir, mode); + inode_init_owner(idmap, inode, dir, mode); err = ovl_create_tmpfile(file, dentry, inode, inode->i_mode); if (err) goto put_inode; diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index bc71231cad53..401cb8c75520 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -26,10 +26,18 @@ int ovl_setattr(struct mnt_idmap *idmap, struct dentry *dentry, bool full_copy_up = false; struct dentry *upperdentry; - err = setattr_prepare(&nop_mnt_idmap, dentry, attr); + err = setattr_prepare(idmap, dentry, attr); if (err) return err; + /* Rebase ownership from the mount idmap into overlay id space. */ + if (attr->ia_valid & ATTR_UID) + attr->ia_vfsuid = VFSUIDT_INIT(from_vfsuid(idmap, + i_user_ns(d_inode(dentry)), attr->ia_vfsuid)); + if (attr->ia_valid & ATTR_GID) + attr->ia_vfsgid = VFSGIDT_INIT(from_vfsgid(idmap, + i_user_ns(d_inode(dentry)), attr->ia_vfsgid)); + if (attr->ia_valid & ATTR_SIZE) { /* Truncate should trigger data copy up as well */ full_copy_up = true; @@ -172,6 +180,8 @@ int ovl_getattr(struct mnt_idmap *idmap, const struct path *path, int fsid = 0; int err; bool metacopy_blocks = false; + vfsuid_t vfsuid; + vfsgid_t vfsgid; metacopy_blocks = ovl_is_metacopy_dentry(dentry); @@ -284,6 +294,12 @@ int ovl_getattr(struct mnt_idmap *idmap, const struct path *path, if (!is_dir && ovl_test_flag(OVL_INDEX, d_inode(dentry))) stat->nlink = dentry->d_inode->i_nlink; + /* Map ownership of the real inode through the overlay mount idmap. */ + vfsuid = make_vfsuid(idmap, i_user_ns(inode), stat->uid); + vfsgid = make_vfsgid(idmap, i_user_ns(inode), stat->gid); + stat->uid = vfsuid_into_kuid(vfsuid); + stat->gid = vfsgid_into_kgid(vfsgid); + return err; } @@ -306,7 +322,7 @@ int ovl_permission(struct mnt_idmap *idmap, * Check overlay inode with the creds of task and underlying inode * with creds of mounter */ - err = generic_permission(&nop_mnt_idmap, inode, mask); + err = generic_permission(idmap, inode, mask); if (err) return err; @@ -534,7 +550,7 @@ int ovl_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, return -EOPNOTSUPP; if (type == ACL_TYPE_DEFAULT && !S_ISDIR(inode->i_mode)) return acl ? -EACCES : 0; - if (!inode_owner_or_capable(&nop_mnt_idmap, inode)) + if (!inode_owner_or_capable(idmap, inode)) return -EPERM; /* @@ -542,8 +558,8 @@ int ovl_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, * be done with mounter's capabilities and so that won't do it for us). */ if (unlikely(inode->i_mode & S_ISGID) && type == ACL_TYPE_ACCESS && - !in_group_p(inode->i_gid) && - !capable_wrt_inode_uidgid(&nop_mnt_idmap, inode, CAP_FSETID)) { + !in_group_or_capable(idmap, inode, + i_gid_into_vfsgid(idmap, inode))) { struct iattr iattr = { .ia_valid = ATTR_KILL_SGID }; err = ovl_setattr(&nop_mnt_idmap, dentry, &iattr); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index b75df37f70ac..e0d8c6152e9f 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -320,6 +320,7 @@ static inline int ovl_do_setxattr(struct ovl_fs *ofs, struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { + /* Use vfs_setxattr(), not __vfs_setxattr(): it idmaps the security.capability rootid. */ int err = vfs_setxattr(ovl_upper_mnt_idmap(ofs), dentry, name, value, size, flags); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 60f0b7ceef0a..f2889cf9bc07 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1573,7 +1573,7 @@ struct file_system_type ovl_fs_type = { .name = "overlay", .init_fs_context = ovl_init_fs_context, .parameters = ovl_parameter_spec, - .fs_flags = FS_USERNS_MOUNT, + .fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP, .kill_sb = kill_anon_super, }; MODULE_ALIAS_FS("overlay"); diff --git a/fs/overlayfs/xattrs.c b/fs/overlayfs/xattrs.c index 859e80ae6f40..5ae44b9c8790 100644 --- a/fs/overlayfs/xattrs.c +++ b/fs/overlayfs/xattrs.c @@ -84,6 +84,7 @@ static int ovl_xattr_get(struct dentry *dentry, struct inode *inode, const char struct path realpath; ovl_i_path_real(inode, &realpath); + /* Use vfs_getxattr(), not __vfs_getxattr(): it idmaps the security.capability rootid. */ with_ovl_creds(dentry->d_sb) return vfs_getxattr(mnt_idmap(realpath.mnt), realpath.dentry, name, value, size); } diff --git a/fs/posix_acl.c b/fs/posix_acl.c index 3dc62c1c2708..18b302f94174 100644 --- a/fs/posix_acl.c +++ b/fs/posix_acl.c @@ -747,8 +747,6 @@ static int posix_acl_fix_xattr_common(const void *value, size_t size) count = posix_acl_xattr_count(size); if (count < 0) return -EINVAL; - if (count == 0) - return 0; return count; } diff --git a/fs/proc/array.c b/fs/proc/array.c index 479ea8cb4ef4..f6f75d206762 100644 --- a/fs/proc/array.c +++ b/fs/proc/array.c @@ -168,8 +168,8 @@ static inline void task_state(struct seq_file *m, struct pid_namespace *ns, cred = get_task_cred(p); task_lock(p); - if (p->fs) - umask = p->fs->umask; + if (p->real_fs) + umask = p->real_fs->umask; if (p->files) max_fds = files_fdtable(p->files)->max_fds; task_unlock(p); diff --git a/fs/proc/base.c b/fs/proc/base.c index 780f81259052..6a39de424f62 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -211,8 +211,8 @@ static int get_task_root(struct task_struct *task, struct path *root) int result = -ENOENT; task_lock(task); - if (task->fs) { - get_fs_root(task->fs, root); + if (task->real_fs) { + get_fs_root(task->real_fs, root); result = 0; } task_unlock(task); @@ -225,8 +225,8 @@ static int proc_cwd_link(struct dentry *dentry, struct path *path, int result = -ENOENT; task_lock(task); - if (task->fs) { - get_fs_pwd(task->fs, path); + if (task->real_fs) { + get_fs_pwd(task->real_fs, path); result = 0; } task_unlock(task); diff --git a/fs/proc_namespace.c b/fs/proc_namespace.c index 5c555db68aa2..036356c0a55b 100644 --- a/fs/proc_namespace.c +++ b/fs/proc_namespace.c @@ -254,13 +254,13 @@ static int mounts_open_common(struct inode *inode, struct file *file, } ns = nsp->mnt_ns; get_mnt_ns(ns); - if (!task->fs) { + if (!task->real_fs) { task_unlock(task); put_task_struct(task); ret = -ENOENT; goto err_put_ns; } - get_fs_root(task->fs, &root); + get_fs_root(task->real_fs, &root); task_unlock(task); put_task_struct(task); diff --git a/fs/ramfs/inode.c b/fs/ramfs/inode.c index 3987639ed132..0a88ede48e0a 100644 --- a/fs/ramfs/inode.c +++ b/fs/ramfs/inode.c @@ -121,14 +121,14 @@ out: static struct dentry *ramfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { - int retval = ramfs_mknod(&nop_mnt_idmap, dir, dentry, mode | S_IFDIR, 0); + int retval = ramfs_mknod(&nop_mnt_idmap, dir, dentry, mode, 0); if (!retval) inc_nlink(dir); return ERR_PTR(retval); } static int ramfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return ramfs_mknod(&nop_mnt_idmap, dir, dentry, mode | S_IFREG, 0); } diff --git a/fs/romfs/super.c b/fs/romfs/super.c index ac55193bf398..7f783d6173e8 100644 --- a/fs/romfs/super.c +++ b/fs/romfs/super.c @@ -240,6 +240,8 @@ static struct dentry *romfs_lookup(struct inode *dir, struct dentry *dentry, if ((be32_to_cpu(ri.next) & ROMFH_TYPE) == ROMFH_HRD) offset = be32_to_cpu(ri.spec) & ROMFH_MASK; inode = romfs_iget(dir->i_sb, offset); + if (IS_ERR(inode)) + return ERR_CAST(inode); break; } @@ -262,6 +264,8 @@ static const struct inode_operations romfs_dir_inode_operations = { .lookup = romfs_lookup, }; +#define ROMFS_MAX_HARDLINK_DEPTH 64 + /* * get a romfs inode based on its position in the image (which doubles as the * inode number) @@ -273,6 +277,7 @@ static struct inode *romfs_iget(struct super_block *sb, unsigned long pos) struct inode *i; unsigned long nlen; unsigned nextfh; + unsigned int depth = 0; int ret; umode_t mode; @@ -289,6 +294,9 @@ static struct inode *romfs_iget(struct super_block *sb, unsigned long pos) if ((nextfh & ROMFH_TYPE) != ROMFH_HRD) break; + if (++depth > ROMFS_MAX_HARDLINK_DEPTH) + return ERR_PTR(-ELOOP); + pos = be32_to_cpu(ri.spec) & ROMFH_MASK; } @@ -587,7 +595,7 @@ static void romfs_kill_sb(struct super_block *sb) #ifdef CONFIG_ROMFS_ON_BLOCK if (sb->s_bdev) { sync_blockdev(sb->s_bdev); - bdev_fput(sb->s_bdev_file); + fs_bdev_file_release(sb->s_bdev_file, sb); } #endif } diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index 854e672a4e37..287c632392c4 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -54,7 +54,7 @@ void cifs_sb_deactive(struct super_block *sb); extern const struct inode_operations cifs_dir_inode_ops; struct inode *cifs_root_iget(struct super_block *sb); int cifs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *direntry, umode_t mode, bool excl); + struct dentry *direntry, umode_t mode); int cifs_atomic_open(struct inode *dir, struct dentry *direntry, struct file *file, unsigned int oflags, umode_t mode); int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 88a4a1787ff0..7803bd5bd01f 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -645,7 +645,7 @@ out_free_xid: * hashed-positive by calling d_instantiate(). */ int cifs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *direntry, umode_t mode, bool excl) + struct dentry *direntry, umode_t mode) { struct cifs_sb_info *cifs_sb = CIFS_SB(dir); int rc; diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index b2806371bfde..dc64474d16f6 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -2287,6 +2287,13 @@ struct dentry *cifs_mkdir(struct mnt_idmap *idmap, struct inode *inode, const char *full_path; void *page; + /* + * vfs_mkdir() now passes S_IFDIR in @mode, but @mode is forwarded + * verbatim to the server and in the past only contained permission + * bits. Strip the type bit until SMB is verified to deal with it. + */ + mode &= ~S_IFDIR; + cifs_dbg(FYI, "In cifs_mkdir, mode = %04ho inode = 0x%p\n", mode, inode); diff --git a/fs/smb/server/mgmt/share_config.c b/fs/smb/server/mgmt/share_config.c index 6f97f8d39657..e00aee155935 100644 --- a/fs/smb/server/mgmt/share_config.c +++ b/fs/smb/server/mgmt/share_config.c @@ -9,6 +9,7 @@ #include <linux/rwsem.h> #include <linux/parser.h> #include <linux/namei.h> +#include <linux/fs_struct.h> #include <linux/sched.h> #include <linux/mm.h> @@ -193,7 +194,8 @@ static struct ksmbd_share_config *share_config_request(struct ksmbd_work *work, goto out; } - ret = kern_path(share->path, 0, &share->vfs_path); + scoped_with_init_fs() + ret = kern_path(share->path, 0, &share->vfs_path); ksmbd_revert_fsids(work); if (ret) { ksmbd_debug(SMB, "failed to access '%s'\n", diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index add6194cd565..0caf1598aae7 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -9,6 +9,7 @@ #include <net/addrconf.h> #include <linux/syscalls.h> #include <linux/namei.h> +#include <linux/fs_struct.h> #include <linux/statfs.h> #include <linux/ethtool.h> #include <linux/falloc.h> @@ -6057,7 +6058,8 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work, if (!share->path) return -EIO; - rc = kern_path(share->path, LOOKUP_NO_SYMLINKS, &path); + scoped_with_init_fs() + rc = kern_path(share->path, LOOKUP_NO_SYMLINKS, &path); if (rc) { pr_err("cannot create vfs path\n"); return -EIO; diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c index 6600c2f5a404..2f111b1923d7 100644 --- a/fs/smb/server/vfs.c +++ b/fs/smb/server/vfs.c @@ -7,6 +7,7 @@ #include <crypto/sha2.h> #include <linux/kernel.h> #include <linux/fs.h> +#include <linux/fs_struct.h> #include <linux/filelock.h> #include <linux/uaccess.h> #include <linux/backing-dev.h> @@ -67,8 +68,9 @@ static int ksmbd_vfs_path_lookup(struct ksmbd_share_config *share_conf, } CLASS(filename_kernel, filename)(pathname); - err = vfs_path_parent_lookup(filename, flags, path, &last, - root_share_path); + scoped_with_init_fs() + err = vfs_path_parent_lookup(filename, flags, path, &last, + root_share_path); if (err) return err; @@ -621,7 +623,8 @@ int ksmbd_vfs_link(struct ksmbd_work *work, const char *oldname, if (ksmbd_override_fsids(work)) return -ENOMEM; - err = kern_path(oldname, LOOKUP_NO_SYMLINKS, &oldpath); + scoped_with_init_fs() + err = kern_path(oldname, LOOKUP_NO_SYMLINKS, &oldpath); if (err) { pr_err("cannot get linux path for %s, err = %d\n", oldname, err); diff --git a/fs/super.c b/fs/super.c index ffdcc6a2e0de..5feecf5d9038 100644 --- a/fs/super.c +++ b/fs/super.c @@ -24,6 +24,8 @@ #include <linux/export.h> #include <linux/slab.h> #include <linux/blkdev.h> +#include <linux/memcontrol.h> +#include <linux/rhashtable.h> #include <linux/mount.h> #include <linux/security.h> #include <linux/writeback.h> /* for the emergency remount stuff */ @@ -102,7 +104,7 @@ static bool super_flags(const struct super_block *sb, unsigned int flags) * creation will succeed and SB_BORN is set by vfs_get_tree() or we're * woken and we'll see SB_DYING. * - * The caller must have acquired a temporary reference on @sb->s_count. + * The caller must have acquired a temporary reference on @sb->s_passive. * * Return: The function returns true if SB_BORN was set and with * s_umount held. The function returns false if SB_DYING was @@ -170,6 +172,19 @@ static void super_wake(struct super_block *sb, unsigned int flag) } /* + * The s_op->nr_cached_objects hooks (used for example by btrfs and xfs) + * operate on filesystem-global state and ignore sc->memcg. Driving them + * from per-memcg shrink_slab_memcg() invocations only burns CPU walking + * per-cpu counters and queueing duplicate work: the actual reclaim happens on + * the global path (kswapd or root direct reclaim) regardless. Restrict them + * to that path. + */ +static inline bool super_fs_objects_eligible(struct shrink_control *sc) +{ + return !sc->memcg || mem_cgroup_is_root(sc->memcg); +} + +/* * One thing we have to be careful of with a per-sb shrinker is that we don't * drop the last active reference to the superblock from within the shrinker. * If that happens we could trigger unregistering the shrinker from within the @@ -198,7 +213,7 @@ static unsigned long super_cache_scan(struct shrinker *shrink, if (!super_trylock_shared(sb)) return SHRINK_STOP; - if (sb->s_op->nr_cached_objects) + if (sb->s_op->nr_cached_objects && super_fs_objects_eligible(sc)) fs_objects = sb->s_op->nr_cached_objects(sb, sc); inodes = list_lru_shrink_count(&sb->s_inode_lru, sc); @@ -259,7 +274,8 @@ static unsigned long super_cache_count(struct shrinker *shrink, return 0; smp_rmb(); - if (sb->s_op && sb->s_op->nr_cached_objects) + if (sb->s_op && sb->s_op->nr_cached_objects && + super_fs_objects_eligible(sc)) total_objects = sb->s_op->nr_cached_objects(sb, sc); total_objects += list_lru_shrink_count(&sb->s_dentry_lru, sc); @@ -272,6 +288,8 @@ static unsigned long super_cache_count(struct shrinker *shrink, return total_objects; } +static struct super_dev *super_dev_alloc(dev_t dev, struct super_block *sb); + static void destroy_super_work(struct work_struct *work) { struct super_block *s = container_of(work, struct super_block, @@ -279,6 +297,8 @@ static void destroy_super_work(struct work_struct *work) fsnotify_sb_free(s); security_sb_free(s); put_user_ns(s->s_user_ns); + /* Only an unregistered entry is still owned by the superblock. */ + kfree(s->s_super_dev); kfree(s->s_subtype); for (int i = 0; i < SB_FREEZE_LEVELS; i++) percpu_free_rwsem(&s->s_writers.rw_sem[i]); @@ -367,7 +387,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags, spin_lock_init(&s->s_inode_wblist_lock); fserror_mount(s); - s->s_count = 1; + refcount_set(&s->s_passive, 1); atomic_set(&s->s_active, 1); mutex_init(&s->s_vfs_rename_mutex); lockdep_set_class(&s->s_vfs_rename_mutex, &type->s_vfs_rename_key); @@ -392,6 +412,10 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags, goto fail; if (list_lru_init_memcg(&s->s_inode_lru, s->s_shrink)) goto fail; + s->s_super_dev = super_dev_alloc(0, s); + if (!s->s_super_dev) + goto fail; + s->s_min_writeback_pages = MIN_WRITEBACK_PAGES; return s; @@ -403,12 +427,17 @@ fail: /* Superblock refcounting */ /* - * Drop a superblock's refcount. The caller must hold sb_lock. + * Drop a superblock's passive reference. Must be called WITHOUT sb_lock held; + * put_super() acquires sb_lock itself when the final reference is dropped. */ -static void __put_super(struct super_block *s) +void put_super(struct super_block *s) { - if (!--s->s_count) { + if (refcount_dec_and_test(&s->s_passive)) { + + spin_lock(&sb_lock); list_del_init(&s->s_list); + spin_unlock(&sb_lock); + WARN_ON(s->s_dentry_lru.node); WARN_ON(s->s_inode_lru.node); WARN_ON(s->s_mounts); @@ -416,18 +445,109 @@ static void __put_super(struct super_block *s) } } -/** - * put_super - drop a temporary reference to superblock - * @sb: superblock in question - * - * Drops a temporary reference, frees superblock if there's no - * references left. - */ -void put_super(struct super_block *sb) +struct super_dev { + dev_t sd_dev; + struct super_block *sd_sb; + refcount_t sd_ref; + struct rhlist_head sd_node; + struct rcu_head sd_rcu; +}; + +static struct rhltable super_dev_table; +static const struct rhashtable_params super_dev_params = { + .key_len = sizeof(dev_t), + .key_offset = offsetof(struct super_dev, sd_dev), + .head_offset = offsetof(struct super_dev, sd_node), +}; + +static struct super_dev *super_dev_alloc(dev_t dev, struct super_block *sb) { - spin_lock(&sb_lock); - __put_super(sb); - spin_unlock(&sb_lock); + struct super_dev *fsd; + + fsd = kzalloc_obj(*fsd); + if (!fsd) + return NULL; + fsd->sd_dev = dev; + fsd->sd_sb = sb; + refcount_set(&fsd->sd_ref, 1); + return fsd; +} + +static void super_dev_put(struct super_dev *fsd) +{ + /* Unlink only once unpinned, so a cursor never resumes from a removed node. */ + if (fsd && refcount_dec_and_test(&fsd->sd_ref)) { + rhltable_remove(&super_dev_table, &fsd->sd_node, super_dev_params); + put_super(fsd->sd_sb); + kfree_rcu(fsd, sd_rcu); + } +} + +void __init super_dev_init(void) +{ + if (rhltable_init(&super_dev_table, &super_dev_params)) + panic("VFS: Cannot initialise super_dev_table\n"); +} + +static int super_dev_insert(struct super_dev *fsd) +{ + int err; + + err = rhltable_insert(&super_dev_table, &fsd->sd_node, super_dev_params); + if (!err) + refcount_inc(&fsd->sd_sb->s_passive); + return err; +} + +/* Register @sb under @sb->s_dev as the final fallible act of a set callback. */ +static int super_dev_register(struct super_block *sb) +{ + struct super_dev *fsd = sb->s_super_dev; + int err; + + lockdep_assert_held(&sb_lock); + VFS_WARN_ON_ONCE(!sb->s_dev); + VFS_WARN_ON_ONCE(!fsd || fsd->sd_dev); + + fsd->sd_dev = sb->s_dev; + err = super_dev_insert(fsd); + if (err) + fsd->sd_dev = 0; + return err; +} + +static struct super_dev *super_dev_get(struct rhlist_head *pos) +{ + struct super_dev *sb_dev; + + for (; pos; pos = rcu_dereference_all(pos->next)) { + sb_dev = container_of(pos, struct super_dev, sd_node); + if (refcount_inc_not_zero(&sb_dev->sd_ref)) + return sb_dev; + } + return NULL; +} + +static struct super_dev *super_dev_first(dev_t dev) +{ + struct super_dev *sb_dev; + + rcu_read_lock(); + sb_dev = super_dev_get(rhltable_lookup(&super_dev_table, &dev, super_dev_params)); + rcu_read_unlock(); + return sb_dev; +} + +static struct super_dev *super_dev_next(struct super_dev *prev) +{ + struct super_dev *sb_dev; + + rcu_read_lock(); + sb_dev = super_dev_get(rcu_dereference_all(prev->sd_node.next)); + rcu_read_unlock(); + + super_dev_put(prev); + return sb_dev; } static void kill_super_notify(struct super_block *sb) @@ -449,6 +569,12 @@ static void kill_super_notify(struct super_block *sb) hlist_del_init(&sb->s_instances); spin_unlock(&sb_lock); + /* Drop sget_fc()'s claim; a never-registered entry stays with the sb. */ + if (sb->s_super_dev->sd_dev) { + super_dev_put(sb->s_super_dev); + sb->s_super_dev = NULL; + } + /* * Let concurrent mounts know that this thing is really dead. * We don't need @sb->s_umount here as every concurrent caller @@ -478,11 +604,7 @@ void deactivate_locked_super(struct super_block *s) kill_super_notify(s); - /* - * Since list_lru_destroy() may sleep, we cannot call it from - * put_super(), where we hold the sb_lock. Therefore we destroy - * the lru lists right now. - */ + /* list_lru_destroy() may sleep; put_super() callers may not. */ list_lru_destroy(&s->s_dentry_lru); list_lru_destroy(&s->s_inode_lru); @@ -529,7 +651,7 @@ static bool grab_super(struct super_block *sb) { bool locked; - sb->s_count++; + refcount_inc(&sb->s_passive); spin_unlock(&sb_lock); locked = super_lock_excl(sb); if (locked) { @@ -556,7 +678,7 @@ static bool grab_super(struct super_block *sb) * lock held in read mode in case of success. On successful return, * the caller must drop the s_umount lock when done. * - * Note that unlike get_super() et.al. this one does *not* bump ->s_count. + * Note that unlike get_super() et.al. this one does *not* bump ->s_passive. * The reason why it's safe is that we are OK with doing trylock instead * of down_read(). There's a couple of places that are OK with that, but * it's very much not a general-purpose interface. @@ -763,6 +885,7 @@ retry: } if (!s) { spin_unlock(&sb_lock); + s = alloc_super(fc->fs_type, fc->sb_flags, user_ns); if (!s) return ERR_PTR(-ENOMEM); @@ -772,11 +895,13 @@ retry: s->s_fs_info = fc->s_fs_info; err = set(s, fc); if (err) { + VFS_WARN_ON_ONCE(s->s_super_dev->sd_dev); s->s_fs_info = NULL; spin_unlock(&sb_lock); destroy_unused_super(s); return ERR_PTR(err); } + VFS_WARN_ON_ONCE(!s->s_super_dev->sd_dev); fc->s_fs_info = NULL; s->s_type = fc->fs_type; s->s_iflags |= fc->s_iflags; @@ -851,14 +976,17 @@ static void __iterate_supers(void (*f)(struct super_block *, void *), void *arg, struct super_block *sb, *p = NULL; bool excl = flags & SUPER_ITER_EXCL; - guard(spinlock)(&sb_lock); + spin_lock(&sb_lock); for (sb = first_super(flags); !list_entry_is_head(sb, &super_blocks, s_list); sb = next_super(sb, flags)) { if (super_flags(sb, SB_DYING)) continue; - sb->s_count++; + + if (!refcount_inc_not_zero(&sb->s_passive)) + continue; + spin_unlock(&sb_lock); if (flags & SUPER_ITER_UNLOCKED) { @@ -868,13 +996,14 @@ static void __iterate_supers(void (*f)(struct super_block *, void *), void *arg, super_unlock(sb, excl); } - spin_lock(&sb_lock); if (p) - __put_super(p); + put_super(p); p = sb; + spin_lock(&sb_lock); } + spin_unlock(&sb_lock); if (p) - __put_super(p); + put_super(p); } void iterate_supers(void (*f)(struct super_block *, void *), void *arg) @@ -903,7 +1032,9 @@ void iterate_supers_type(struct file_system_type *type, if (super_flags(sb, SB_DYING)) continue; - sb->s_count++; + if (!refcount_inc_not_zero(&sb->s_passive)) + continue; + spin_unlock(&sb_lock); locked = super_lock_shared(sb); @@ -912,41 +1043,33 @@ void iterate_supers_type(struct file_system_type *type, super_unlock_shared(sb); } - spin_lock(&sb_lock); if (p) - __put_super(p); + put_super(p); p = sb; + spin_lock(&sb_lock); } - if (p) - __put_super(p); spin_unlock(&sb_lock); + if (p) + put_super(p); } EXPORT_SYMBOL(iterate_supers_type); struct super_block *user_get_super(dev_t dev, bool excl) { - struct super_block *sb; + struct super_dev *sb_dev; - spin_lock(&sb_lock); - list_for_each_entry(sb, &super_blocks, s_list) { - bool locked; + for (sb_dev = super_dev_first(dev); sb_dev; sb_dev = super_dev_next(sb_dev)) { + struct super_block *sb = sb_dev->sd_sb; - if (sb->s_dev != dev) + if (!super_lock(sb, excl)) continue; - sb->s_count++; - spin_unlock(&sb_lock); - - locked = super_lock(sb, excl); - if (locked) - return sb; - - spin_lock(&sb_lock); - __put_super(sb); - break; + /* The pinned entry holds a passive reference, take our own. */ + refcount_inc(&sb->s_passive); + super_dev_put(sb_dev); + return sb; } - spin_unlock(&sb_lock); return NULL; } @@ -1228,7 +1351,16 @@ EXPORT_SYMBOL(free_anon_bdev); int set_anon_super(struct super_block *s, void *data) { - return get_anon_bdev(&s->s_dev); + int error; + + error = get_anon_bdev(&s->s_dev); + if (error) + return error; + + error = super_dev_register(s); + if (error) + free_anon_bdev(s->s_dev); + return error; } EXPORT_SYMBOL(set_anon_super); @@ -1314,7 +1446,7 @@ EXPORT_SYMBOL(get_tree_keyed); static int set_bdev_super(struct super_block *s, void *data) { s->s_dev = *(dev_t *)data; - return 0; + return super_dev_register(s); } static int super_s_dev_set(struct super_block *s, struct fs_context *fc) @@ -1356,197 +1488,313 @@ struct super_block *sget_dev(struct fs_context *fc, dev_t dev) EXPORT_SYMBOL(sget_dev); #ifdef CONFIG_BLOCK -/* - * Lock the superblock that is holder of the bdev. Returns the superblock - * pointer if we successfully locked the superblock and it is alive. Otherwise - * we return NULL and just unlock bdev->bd_holder_lock. - * - * The function must be called with bdev->bd_holder_lock and releases it. - */ -static struct super_block *bdev_super_lock(struct block_device *bdev, bool excl) - __releases(&bdev->bd_holder_lock) +static int fs_super_freeze(struct super_block *sb) { - struct super_block *sb = bdev->bd_holder; - bool locked; - - lockdep_assert_held(&bdev->bd_holder_lock); - lockdep_assert_not_held(&sb->s_umount); - lockdep_assert_not_held(&bdev->bd_disk->open_mutex); - - /* Make sure sb doesn't go away from under us */ - spin_lock(&sb_lock); - sb->s_count++; - spin_unlock(&sb_lock); - - mutex_unlock(&bdev->bd_holder_lock); - - locked = super_lock(sb, excl); - - /* - * If the superblock wasn't already SB_DYING then we hold - * s_umount and can safely drop our temporary reference. - */ - put_super(sb); - - if (!locked) - return NULL; - - if (!sb->s_root || !(sb->s_flags & SB_ACTIVE)) { - super_unlock(sb, excl); - return NULL; - } + if (sb->s_op->freeze_super) + return sb->s_op->freeze_super(sb, + FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL); + return freeze_super(sb, FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL); +} - return sb; +static int fs_super_thaw(struct super_block *sb) +{ + if (sb->s_op->thaw_super) + return sb->s_op->thaw_super(sb, + FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL); + return thaw_super(sb, FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL); } static void fs_bdev_mark_dead(struct block_device *bdev, bool surprise) { - struct super_block *sb; + struct super_dev *sb_dev; + dev_t dev = bdev->bd_dev; - sb = bdev_super_lock(bdev, false); - if (!sb) - return; + mutex_unlock(&bdev->bd_holder_lock); - if (sb->s_op->remove_bdev) { - int ret; + for (sb_dev = super_dev_first(dev); sb_dev; sb_dev = super_dev_next(sb_dev)) { + struct super_block *sb = sb_dev->sd_sb; - ret = sb->s_op->remove_bdev(sb, bdev); - if (!ret) { - super_unlock_shared(sb); - return; + if (!super_lock_shared(sb)) + continue; + if (sb->s_root && (sb->s_flags & SB_ACTIVE)) { + if (!sb->s_op->remove_bdev || + sb->s_op->remove_bdev(sb, bdev)) { + if (!surprise) + sync_filesystem(sb); + shrink_dcache_sb(sb); + evict_inodes(sb); + if (sb->s_op->shutdown) + sb->s_op->shutdown(sb); + } } - /* Fallback to shutdown. */ + super_unlock_shared(sb); } - - if (!surprise) - sync_filesystem(sb); - shrink_dcache_sb(sb); - evict_inodes(sb); - if (sb->s_op->shutdown) - sb->s_op->shutdown(sb); - - super_unlock_shared(sb); } static void fs_bdev_sync(struct block_device *bdev) { - struct super_block *sb; - - sb = bdev_super_lock(bdev, false); - if (!sb) - return; + struct super_dev *sb_dev; + dev_t dev = bdev->bd_dev; - sync_filesystem(sb); - super_unlock_shared(sb); -} + mutex_unlock(&bdev->bd_holder_lock); -static struct super_block *get_bdev_super(struct block_device *bdev) -{ - bool active = false; - struct super_block *sb; + for (sb_dev = super_dev_first(dev); sb_dev; sb_dev = super_dev_next(sb_dev)) { + struct super_block *sb = sb_dev->sd_sb; - sb = bdev_super_lock(bdev, true); - if (sb) { - active = atomic_inc_not_zero(&sb->s_active); - super_unlock_excl(sb); + if (!super_lock_shared(sb)) + continue; + if (sb->s_root && (sb->s_flags & SB_ACTIVE)) + sync_filesystem(sb); + super_unlock_shared(sb); } - if (!active) - return NULL; - return sb; } /** - * fs_bdev_freeze - freeze owning filesystem of block device + * fs_bdev_freeze - freeze every superblock using a block device * @bdev: block device * - * Freeze the filesystem that owns this block device if it is still - * active. - * - * A filesystem that owns multiple block devices may be frozen from each - * block device and won't be unfrozen until all block devices are - * unfrozen. Each block device can only freeze the filesystem once as we - * nest freezes for block devices in the block layer. + * Freeze each live superblock using @bdev. A superblock owning several block + * devices is frozen once per device and stays frozen until all are thawed; the + * block layer nests these freezes so the count stays balanced. * - * Return: If the freeze was successful zero is returned. If the freeze - * failed a negative error code is returned. + * Return: 0, or the error from the one superblock on a single-fs device. When + * several superblocks share @bdev a per-superblock failure is swallowed + * (see below), but a sync_blockdev() failure is always reported. */ static int fs_bdev_freeze(struct block_device *bdev) { - struct super_block *sb; - int error = 0; + dev_t dev = bdev->bd_dev; + struct super_dev *sb_dev; + unsigned int count = 0; + int error = 0, err; lockdep_assert_held(&bdev->bd_fsfreeze_mutex); - sb = get_bdev_super(bdev); - if (!sb) - return -EINVAL; + mutex_unlock(&bdev->bd_holder_lock); - if (sb->s_op->freeze_super) - error = sb->s_op->freeze_super(sb, - FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL); - else - error = freeze_super(sb, - FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL); + for (sb_dev = super_dev_first(dev); sb_dev; sb_dev = super_dev_next(sb_dev)) { + if (!get_active_super(sb_dev->sd_sb)) + continue; + err = fs_super_freeze(sb_dev->sd_sb); + if (err && !error) + error = err; + deactivate_super(sb_dev->sd_sb); + count++; + } + + /* + * When several superblocks share the device, keep it frozen even if some + * of them failed to freeze and swallow the error: rolling the rest back + * via thaw_super() can fail too, so neither is a clear win. A single + * filesystem (count == 1) still reports its error. + */ + if (error && count > 1) + error = 0; if (!error) error = sync_blockdev(bdev); - deactivate_super(sb); return error; } /** - * fs_bdev_thaw - thaw owning filesystem of block device + * fs_bdev_thaw - thaw every superblock using a block device * @bdev: block device * - * Thaw the filesystem that owns this block device. - * - * A filesystem that owns multiple block devices may be frozen from each - * block device and won't be unfrozen until all block devices are - * unfrozen. Each block device can only freeze the filesystem once as we - * nest freezes for block devices in the block layer. + * The counterpart to fs_bdev_freeze(): thaw each live superblock using @bdev. + * A zero return does not imply a superblock is fully unfrozen; it may have been + * frozen more than once (by the kernel or via another device). * - * Return: If the thaw was successful zero is returned. If the thaw - * failed a negative error code is returned. If this function - * returns zero it doesn't mean that the filesystem is unfrozen - * as it may have been frozen multiple times (kernel may hold a - * freeze or might be frozen from other block devices). + * Return: 0, or the first error on a single-fs device; a shared device swallows + * per-superblock errors, as fs_bdev_freeze() does. */ static int fs_bdev_thaw(struct block_device *bdev) { - struct super_block *sb; - int error; + dev_t dev = bdev->bd_dev; + struct super_dev *sb_dev; + unsigned int count = 0; + int error = 0, err; lockdep_assert_held(&bdev->bd_fsfreeze_mutex); - /* - * The block device may have been frozen before it was claimed by a - * filesystem. Concurrently another process might try to mount that - * frozen block device and has temporarily claimed the block device for - * that purpose causing a concurrent fs_bdev_thaw() to end up here. The - * mounter is already about to abort mounting because they still saw an - * elevanted bdev->bd_fsfreeze_count so get_bdev_super() will return - * NULL in that case. - */ - sb = get_bdev_super(bdev); - if (!sb) - return -EINVAL; + mutex_unlock(&bdev->bd_holder_lock); - if (sb->s_op->thaw_super) - error = sb->s_op->thaw_super(sb, - FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL); - else - error = thaw_super(sb, - FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL); - deactivate_super(sb); + for (sb_dev = super_dev_first(dev); sb_dev; sb_dev = super_dev_next(sb_dev)) { + if (!get_active_super(sb_dev->sd_sb)) + continue; + err = fs_super_thaw(sb_dev->sd_sb); + if (err && !error) + error = err; + deactivate_super(sb_dev->sd_sb); + count++; + } + + /* Shared device: swallow per-superblock errors, like fs_bdev_freeze(). */ + if (error && count > 1) + error = 0; return error; } -const struct blk_holder_ops fs_holder_ops = { +static const struct blk_holder_ops fs_holder_ops = { .mark_dead = fs_bdev_mark_dead, .sync = fs_bdev_sync, .freeze = fs_bdev_freeze, .thaw = fs_bdev_thaw, }; -EXPORT_SYMBOL_GPL(fs_holder_ops); + +static struct super_dev *super_dev_lookup(dev_t dev, struct super_block *sb) +{ + struct super_dev *it; + struct rhlist_head *list, *pos; + + RCU_LOCKDEP_WARN(!rcu_read_lock_held(), "suspicious super_dev_lookup() usage"); + VFS_WARN_ON_ONCE(!dev); + VFS_WARN_ON_ONCE(!sb); + + list = rhltable_lookup(&super_dev_table, &dev, super_dev_params); + rhl_for_each_entry_rcu(it, pos, list, sd_node) { + if (it->sd_sb == sb) + return it; + } + + return NULL; +} + +static int fs_bdev_register(struct file *bdev_file, struct super_block *sb) +{ + struct super_dev *sb_dev __free(kfree) = NULL; + dev_t dev = file_bdev(bdev_file)->bd_dev; + int err; + + scoped_guard(rcu) { + sb_dev = super_dev_lookup(dev, sb); + if (sb_dev && refcount_inc_not_zero(&sb_dev->sd_ref)) { + retain_and_null_ptr(sb_dev); + return 0; + } + } + + sb_dev = super_dev_alloc(dev, sb); + if (!sb_dev) + return -ENOMEM; + + err = super_dev_insert(sb_dev); + if (err) + return err; + + /* Publish the entry before reading the count; pairs with bdev_freeze(). */ + smp_mb(); + if (atomic_read(&file_bdev(bdev_file)->bd_fsfreeze_count) > 0) { + err = -EBUSY; + super_dev_put(sb_dev); + } + + retain_and_null_ptr(sb_dev); + return err; +} + +/** + * fs_bdev_file_open_by_dev - claim a block device on behalf of a superblock + * @dev: block device number + * @mode: open mode + * @holder: block-layer exclusivity token (a superblock, or the file_system_type + * when the device may be shared by several superblocks of that type) + * @sb: superblock to drive fs_holder_ops events for + * + * Open @dev with &fs_holder_ops and register that @sb uses it, so device + * removal/sync/freeze/thaw are propagated to @sb (and any other superblock + * sharing @dev). Must be paired with fs_bdev_file_release(). + * + * Return: an opened block-device file or an ERR_PTR(). + */ +struct file *fs_bdev_file_open_by_dev(dev_t dev, blk_mode_t mode, void *holder, + struct super_block *sb) +{ + struct file *bdev_file; + int err; + + bdev_file = bdev_file_open_by_dev(dev, mode, holder, &fs_holder_ops); + if (IS_ERR(bdev_file)) + return bdev_file; + + err = fs_bdev_register(bdev_file, sb); + if (err) { + bdev_fput(bdev_file); + return ERR_PTR(err); + } + return bdev_file; +} +EXPORT_SYMBOL_GPL(fs_bdev_file_open_by_dev); + +/** + * fs_bdev_file_open_by_path - claim a block device on behalf of a superblock + * @path: path to the block device + * @mode: open mode + * @holder: block-layer exclusivity token (a superblock, or the file_system_type + * when the device may be shared by several superblocks of that type) + * @sb: superblock to drive fs_holder_ops events for + * + * Open the block device at @path with &fs_holder_ops and register that @sb + * uses it, so device removal/sync/freeze/thaw are propagated to @sb (and any + * other superblock sharing the device). Must be paired with + * fs_bdev_file_release(). + * + * Return: an opened block-device file or an ERR_PTR(). + */ +struct file *fs_bdev_file_open_by_path(const char *path, blk_mode_t mode, + void *holder, struct super_block *sb) +{ + struct file *bdev_file; + int err; + + bdev_file = bdev_file_open_by_path(path, mode, holder, &fs_holder_ops); + if (IS_ERR(bdev_file)) + return bdev_file; + + err = fs_bdev_register(bdev_file, sb); + if (err) { + bdev_fput(bdev_file); + return ERR_PTR(err); + } + return bdev_file; +} +EXPORT_SYMBOL_GPL(fs_bdev_file_open_by_path); + +/** + * fs_bdev_unregister - drop a superblock's claim on a block device + * @bdev_file: file returned by fs_bdev_file_open_by_{dev,path}() + * @sb: superblock the device was claimed for + * + * The inverse of fs_bdev_register(): drop one claim on the {dev, @sb} entry + * (the last claim unregisters it; a pinning cursor defers the actual unlink) + * without closing the device. A caller that must act on the still-open device + * between unregistering and closing - e.g. re-allow freezing one denied for a + * membership change - pairs this with bdev_fput(). fs_bdev_file_release() is + * the common unregister-and-close. + */ +void fs_bdev_unregister(struct file *bdev_file, struct super_block *sb) +{ + dev_t dev = file_bdev(bdev_file)->bd_dev; + struct super_dev *sb_dev; + + rcu_read_lock(); + sb_dev = super_dev_lookup(dev, sb); + rcu_read_unlock(); + super_dev_put(sb_dev); +} +EXPORT_SYMBOL_GPL(fs_bdev_unregister); + +/** + * fs_bdev_file_release - release a block device claimed for a superblock + * @bdev_file: file returned by fs_bdev_file_open_by_{dev,path}() + * @sb: superblock the device was claimed for + * + * Unregister the {dev, @sb} entry, then close the block device. + */ +void fs_bdev_file_release(struct file *bdev_file, struct super_block *sb) +{ + fs_bdev_unregister(bdev_file, sb); + bdev_fput(bdev_file); +} +EXPORT_SYMBOL_GPL(fs_bdev_file_release); int setup_bdev_super(struct super_block *sb, int sb_flags, struct fs_context *fc) @@ -1555,7 +1803,7 @@ int setup_bdev_super(struct super_block *sb, int sb_flags, struct file *bdev_file; struct block_device *bdev; - bdev_file = bdev_file_open_by_dev(sb->s_dev, mode, sb, &fs_holder_ops); + bdev_file = fs_bdev_file_open_by_dev(sb->s_dev, mode, sb, sb); if (IS_ERR(bdev_file)) { if (fc) errorf(fc, "%s: Can't open blockdev", fc->source); @@ -1569,20 +1817,19 @@ int setup_bdev_super(struct super_block *sb, int sb_flags, * writable from userspace even for a read-only block device. */ if ((mode & BLK_OPEN_WRITE) && bdev_read_only(bdev)) { - bdev_fput(bdev_file); + fs_bdev_file_release(bdev_file, sb); return -EACCES; } - /* - * It is enough to check bdev was not frozen before we set - * s_bdev as freezing will wait until SB_BORN is set. - */ + /* The sget_fc() entry is already published; pairs with bdev_freeze(). */ + smp_mb(); if (atomic_read(&bdev->bd_fsfreeze_count) > 0) { if (fc) warnf(fc, "%pg: Can't mount, blockdev is frozen", bdev); - bdev_fput(bdev_file); + fs_bdev_file_release(bdev_file, sb); return -EBUSY; } + spin_lock(&sb_lock); sb->s_bdev_file = bdev_file; sb->s_bdev = bdev; @@ -1671,7 +1918,7 @@ void kill_block_super(struct super_block *sb) generic_shutdown_super(sb); if (bdev) { sync_blockdev(bdev); - bdev_fput(sb->s_bdev_file); + fs_bdev_file_release(sb->s_bdev_file, sb); } } diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index 86d41e077e4d..23ec924162d6 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -303,7 +303,7 @@ static int ubifs_prepare_create(struct inode *dir, struct dentry *dentry, } static int ubifs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode; struct ubifs_info *c = dir->i_sb->s_fs_info; @@ -1031,7 +1031,7 @@ static struct dentry *ubifs_mkdir(struct mnt_idmap *idmap, struct inode *dir, sz_change = CALC_DENT_SIZE(fname_len(&nm)); - inode = ubifs_new_inode(c, dir, S_IFDIR | mode, false); + inode = ubifs_new_inode(c, dir, mode, false); if (IS_ERR(inode)) { err = PTR_ERR(inode); goto out_fname; diff --git a/fs/udf/namei.c b/fs/udf/namei.c index 9a3b7cef3606..b90841ac0a40 100644 --- a/fs/udf/namei.c +++ b/fs/udf/namei.c @@ -371,7 +371,7 @@ static int udf_add_nondir(struct dentry *dentry, struct inode *inode) } static int udf_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode = udf_new_inode(dir, mode); @@ -428,7 +428,7 @@ static struct dentry *udf_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct udf_inode_info *dinfo = UDF_I(dir); struct udf_inode_info *iinfo; - inode = udf_new_inode(dir, S_IFDIR | mode); + inode = udf_new_inode(dir, mode); if (IS_ERR(inode)) return ERR_CAST(inode); diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c index 5b3c85c93242..6703f3bcf76f 100644 --- a/fs/ufs/namei.c +++ b/fs/ufs/namei.c @@ -70,8 +70,7 @@ static struct dentry *ufs_lookup(struct inode * dir, struct dentry *dentry, unsi * with d_instantiate(). */ static int ufs_create (struct mnt_idmap * idmap, - struct inode * dir, struct dentry * dentry, umode_t mode, - bool excl) + struct inode * dir, struct dentry * dentry, umode_t mode) { struct inode *inode; @@ -174,7 +173,7 @@ static struct dentry *ufs_mkdir(struct mnt_idmap * idmap, struct inode * dir, inode_inc_link_count(dir); - inode = ufs_new_inode(dir, S_IFDIR|mode); + inode = ufs_new_inode(dir, mode); err = PTR_ERR(inode); if (IS_ERR(inode)) goto out_dir; diff --git a/fs/ufs/super.c b/fs/ufs/super.c index c4831a8b9b3f..6dcf6d048cce 100644 --- a/fs/ufs/super.c +++ b/fs/ufs/super.c @@ -672,7 +672,7 @@ void ufs_mark_sb_dirty(struct super_block *sb) spin_lock(&sbi->work_lock); if (!sbi->work_queued) { delay = msecs_to_jiffies(dirty_writeback_interval * 10); - queue_delayed_work(system_long_wq, &sbi->sync_work, delay); + queue_delayed_work(system_dfl_long_wq, &sbi->sync_work, delay); sbi->work_queued = 1; } spin_unlock(&sbi->work_lock); diff --git a/fs/vboxsf/dir.c b/fs/vboxsf/dir.c index c5bd3271aa96..0b9eab157432 100644 --- a/fs/vboxsf/dir.c +++ b/fs/vboxsf/dir.c @@ -298,9 +298,9 @@ out: static int vboxsf_dir_mkfile(struct mnt_idmap *idmap, struct inode *parent, struct dentry *dentry, - umode_t mode, bool excl) + umode_t mode) { - return vboxsf_dir_create(parent, dentry, mode, false, excl, NULL); + return vboxsf_dir_create(parent, dentry, mode, false, true, NULL); } static struct dentry *vboxsf_dir_mkdir(struct mnt_idmap *idmap, diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index 48d7dfd3e15f..21d67af781da 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -1631,7 +1631,7 @@ xfs_free_buftarg( fs_put_dax(btp->bt_daxdev, btp->bt_mount); /* the main block device is closed by kill_block_super */ if (btp->bt_bdev != btp->bt_mount->m_super->s_bdev) - bdev_fput(btp->bt_file); + fs_bdev_file_release(btp->bt_file, btp->bt_mount->m_super); kfree(btp); } diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 6339f4956ecb..4a3299abf774 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -293,8 +293,7 @@ xfs_vn_create( struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, - bool flags) + umode_t mode) { return xfs_generic_create(idmap, dir, dentry, mode, 0, NULL); } @@ -306,7 +305,7 @@ xfs_vn_mkdir( struct dentry *dentry, umode_t mode) { - return ERR_PTR(xfs_generic_create(idmap, dir, dentry, mode | S_IFDIR, 0, NULL)); + return ERR_PTR(xfs_generic_create(idmap, dir, dentry, mode, 0, NULL)); } STATIC struct dentry * @@ -338,7 +337,7 @@ STATIC struct dentry * xfs_vn_ci_lookup( struct inode *dir, struct dentry *dentry, - unsigned int flags) + unsigned int flags) { struct xfs_inode *ip; struct xfs_name xname; diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c index 63c4bcbe6c2b..4b2eeb7783f7 100644 --- a/fs/xfs/xfs_super.c +++ b/fs/xfs/xfs_super.c @@ -400,8 +400,8 @@ xfs_blkdev_get( blk_mode_t mode; mode = sb_open_mode(mp->m_super->s_flags); - *bdev_filep = bdev_file_open_by_path(name, mode, - mp->m_super, &fs_holder_ops); + *bdev_filep = fs_bdev_file_open_by_path(name, mode, + mp->m_super, mp->m_super); if (IS_ERR(*bdev_filep)) { error = PTR_ERR(*bdev_filep); *bdev_filep = NULL; @@ -526,7 +526,7 @@ xfs_open_devices( mp->m_logdev_targp = mp->m_ddev_targp; /* Handle won't be used, drop it */ if (logdev_file) - bdev_fput(logdev_file); + fs_bdev_file_release(logdev_file, mp->m_super); } return 0; @@ -541,10 +541,10 @@ xfs_open_devices( mp->m_ddev_targp = NULL; out_close_rtdev: if (rtdev_file) - bdev_fput(rtdev_file); + fs_bdev_file_release(rtdev_file, mp->m_super); out_close_logdev: if (logdev_file) - bdev_fput(logdev_file); + fs_bdev_file_release(logdev_file, mp->m_super); return error; } |
