Here are a set of patches to create a superblock configuration context
prior to setting up a new mount, populating it with the parsed
options/binary data, creating the superblock and then effecting the mount.
This allows namespaces and other information to be conveyed through the
mount procedure. It also allows extra error information to be returned
(so many things can go wrong during a mount that a small integer isn't
really sufficient to convey the issue).
This also allows Miklós Szeredi's idea of doing:
fd = fsopen("nfs");
write(fd, "option=val", ...);
fsmount(fd, "/mnt");
that he presented at LSF-2017 to be implemented (see the relevant patches
in the series), to which I can add:
read(fd, error_buffer, ...);
to read back any error message. I didn't use netlink as that would make it
depend on CONFIG_NET and would introduce network namespacing issues.
I've implemented mount context handling for procfs and nfs.
Significant changes:
ver #2:
(*) Removed the ->fill_super() from sb_config_operations and passed it in
directly to functions that want to call it. NFS now calls
nfs_fill_super() directly rather than jumping through a pointer to it
since there's only the one option at the moment.
(*) Removed ->mnt_ns and ->sb from sb_config and moved ->pid_ns into
proc_sb_config.
(*) Renamed create_super -> get_tree.
(*) Renamed struct mount_context to struct sb_config and amended various
variable names.
(*) sys_fsmount() acquired AT_* flags and MS_* flags (for MNT_* flags)
arguments.
ver #1:
(*) Split the sb_config stuff out into its own header.
(*) Support non-context aware filesystems through a special set of
sb_config operations.
(*) Stored the created superblock and root dentry into the sb_config after
creation rather than directly into a vfsmount. This allows some
arguments to be removed to various NFS functions.
(*) Added an explicit superblock-creation step. This allows a created
superblock to then be mounted multiple times.
(*) Added a flag to say that the sb_config is degraded and cannot have
another go at having a superblock creation whilst getting rid of the
one that says it's already mounted.
Further developments:
(*) Implement sb reconfiguration (for now it returns ENOANO).
(*) Implement mount context support in more filesystems, ext4 being next
on my list.
(*) Move the walk-from-root stuff that nfs has to generic code so that you
can do something akin to:
mount /dev/sda1:/foo/bar /mnt
See nfs_follow_remote_path() and mount_subtree(). This is slightly
tricky in NFS as we have to prevent referral loops.
(*) Move the pid_ns pointer from struct mount_context to struct
proc_mount_context as I'm not sure it's necessary for anything other
than procfs.
(*) Work out how to get at the error message incurred by submounts
encountered during nfs_follow_remote_path().
Should the error message be moved to task_struct and made more
general, perhaps retrieved with a prctl() function?
(*) Clean up/consolidate the security functions. Possibly add a
validation hook to be called at the same time as the mount context
validate op.
The patches can be found here also:
http://git.kernel.org/cgit/linux/kernel/git/dhowells/linux-fs.git/log/?h=mount-context
David
---
David Howells (14):
Provide a function to create a NUL-terminated string from unterminated data
Clean up whitespace in fs/namespace.c
VFS: Make get_mnt_ns() return the namespace
VFS: Make get_filesystem() return the affected filesystem
VFS: Provide empty name qstr
VFS: Introduce a superblock configuration context
Implement fsopen() to prepare for a mount
Implement fsmount() to effect a pre-configured mount
Sample program for driving fsopen/fsmount
procfs: Move proc_fill_super() to fs/proc/root.c
proc: Add superblock config support to procfs
NFS: Add mount context support.
Support legacy filesystems
Add commands to create or update a superblock
Documentation/filesystems/mounting.txt | 470 ++++++++
arch/x86/entry/syscalls/syscall_32.tbl | 2
arch/x86/entry/syscalls/syscall_64.tbl | 2
fs/Makefile | 3
fs/dcache.c | 8
fs/filesystems.c | 3
fs/fsopen.c | 302 +++++
fs/gfs2/dir.c | 3
fs/internal.h | 4
fs/libfs.c | 17
fs/mount.h | 3
fs/namei.c | 3
fs/namespace.c | 495 +++++++--
fs/nfs/Makefile | 2
fs/nfs/client.c | 74 +
fs/nfs/getroot.c | 76 +
fs/nfs/internal.h | 142 +--
fs/nfs/mount.c | 1497 +++++++++++++++++++++++++++
fs/nfs/namespace.c | 76 +
fs/nfs/nfs3_fs.h | 2
fs/nfs/nfs3client.c | 6
fs/nfs/nfs3proc.c | 2
fs/nfs/nfs4_fs.h | 4
fs/nfs/nfs4client.c | 82 +
fs/nfs/nfs4namespace.c | 208 ++--
fs/nfs/nfs4proc.c | 3
fs/nfs/nfs4super.c | 220 ++--
fs/nfs/proc.c | 2
fs/nfs/super.c | 1782 ++------------------------------
fs/nsfs.c | 3
fs/pipe.c | 3
fs/proc/inode.c | 50 -
fs/proc/internal.h | 6
fs/proc/root.c | 210 +++-
fs/sb_config.c | 524 +++++++++
fs/super.c | 110 +-
include/linux/dcache.h | 5
include/linux/fs.h | 16
include/linux/lsm_hooks.h | 47 +
include/linux/mount.h | 4
include/linux/nfs_xdr.h | 7
include/linux/sb_config.h | 100 ++
include/linux/security.h | 40 +
include/linux/string.h | 1
include/linux/syscalls.h | 3
include/uapi/linux/magic.h | 1
kernel/sys_ni.c | 4
mm/util.c | 24
samples/fsmount/test-fsmount.c | 79 +
security/security.c | 45 +
security/selinux/hooks.c | 202 +++-
51 files changed, 4596 insertions(+), 2381 deletions(-)
create mode 100644 Documentation/filesystems/mounting.txt
create mode 100644 fs/fsopen.c
create mode 100644 fs/nfs/mount.c
create mode 100644 fs/sb_config.c
create mode 100644 include/linux/sb_config.h
create mode 100644 samples/fsmount/test-fsmount.c
Add superblock config support to procfs.
Signed-off-by: David Howells <[email protected]>
---
fs/proc/inode.c | 2 -
fs/proc/internal.h | 2 -
fs/proc/root.c | 166 +++++++++++++++++++++++++++++++++-------------------
3 files changed, 108 insertions(+), 62 deletions(-)
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 194fa2d13b7e..df9e586ee1af 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -118,7 +118,7 @@ const struct super_operations proc_sops = {
.drop_inode = generic_delete_inode,
.evict_inode = proc_evict_inode,
.statfs = simple_statfs,
- .remount_fs = proc_remount,
+ .remount_fs_sc = proc_remount,
.show_options = proc_show_options,
};
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index b681533f59dd..4546372c2d13 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -262,7 +262,7 @@ static inline void proc_tty_init(void) {}
extern struct proc_dir_entry proc_root;
extern void proc_self_init(void);
-extern int proc_remount(struct super_block *, int *, char *);
+extern int proc_remount(struct super_block *, struct sb_config *);
/*
* task_[no]mmu.c
diff --git a/fs/proc/root.c b/fs/proc/root.c
index ee1937b37370..da5757d1c518 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -24,9 +24,18 @@
#include <linux/parser.h>
#include <linux/cred.h>
#include <linux/magic.h>
+#include <linux/slab.h>
#include "internal.h"
+struct proc_sb_config {
+ struct sb_config sc;
+ struct pid_namespace *pid_ns;
+ unsigned long mask;
+ int hidepid;
+ int gid;
+};
+
enum {
Opt_gid, Opt_hidepid, Opt_err,
};
@@ -37,56 +46,60 @@ static const match_table_t tokens = {
{Opt_err, NULL},
};
-static int proc_parse_options(char *options, struct pid_namespace *pid)
+static int proc_parse_mount_option(struct sb_config *sc, char *p)
{
- char *p;
+ struct proc_sb_config *cfg = container_of(sc, struct proc_sb_config, sc);
substring_t args[MAX_OPT_ARGS];
- int option;
-
- if (!options)
- return 1;
-
- while ((p = strsep(&options, ",")) != NULL) {
- int token;
- if (!*p)
- continue;
-
- args[0].to = args[0].from = NULL;
- token = match_token(p, tokens, args);
- switch (token) {
- case Opt_gid:
- if (match_int(&args[0], &option))
- return 0;
- pid->pid_gid = make_kgid(current_user_ns(), option);
- break;
- case Opt_hidepid:
- if (match_int(&args[0], &option))
- return 0;
- if (option < HIDEPID_OFF ||
- option > HIDEPID_INVISIBLE) {
- pr_err("proc: hidepid value must be between 0 and 2.\n");
- return 0;
- }
- pid->hide_pid = option;
- break;
- default:
- pr_err("proc: unrecognized mount option \"%s\" "
- "or missing value\n", p);
- return 0;
+ int token;
+
+ args[0].to = args[0].from = NULL;
+ token = match_token(p, tokens, args);
+ switch (token) {
+ case Opt_gid:
+ if (match_int(&args[0], &cfg->gid))
+ return sb_cfg_inval(sc, "procfs: Unparseable gid= argument");
+ break;
+
+ case Opt_hidepid:
+ if (match_int(&args[0], &cfg->hidepid))
+ return sb_cfg_inval(sc, "procfs: Unparseable hidepid= argument");
+ if (cfg->hidepid < HIDEPID_OFF ||
+ cfg->hidepid > HIDEPID_INVISIBLE) {
+ pr_err("proc: hidepid value must be between 0 and 2.\n");
+ return sb_cfg_inval(sc, "procfs: Invalid hidepid= argument");
}
+ break;
+
+ default:
+ pr_err("proc: unrecognized mount option \"%s\" "
+ "or missing value\n", p);
+ return sb_cfg_inval(sc, "procfs: Invalid mount option or missing value");
}
- return 1;
+ cfg->mask |= 1 << token;
+ return 0;
+}
+
+static void proc_set_options(struct super_block *s,
+ struct sb_config *sc,
+ struct pid_namespace *pid_ns,
+ struct user_namespace *user_ns)
+{
+ struct proc_sb_config *cfg = container_of(sc, struct proc_sb_config, sc);
+
+ if (cfg->mask & (1 << Opt_gid))
+ pid_ns->pid_gid = make_kgid(user_ns, cfg->gid);
+ if (cfg->mask & (1 << Opt_hidepid))
+ pid_ns->hide_pid = cfg->hidepid;
}
-static int proc_fill_super(struct super_block *s, void *data, int silent)
+static int proc_fill_super(struct super_block *s, struct sb_config *sc)
{
- struct pid_namespace *ns = get_pid_ns(s->s_fs_info);
+ struct pid_namespace *pid_ns = get_pid_ns(s->s_fs_info);
struct inode *root_inode;
int ret;
- if (!proc_parse_options(data, ns))
- return -EINVAL;
+ proc_set_options(s, sc, pid_ns, current_user_ns());
/* User space would break if executables or devices appear on proc */
s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
@@ -103,7 +116,7 @@ static int proc_fill_super(struct super_block *s, void *data, int silent)
* top of it
*/
s->s_stack_depth = FILESYSTEM_MAX_STACK_DEPTH;
-
+
pde_get(&proc_root);
root_inode = proc_get_inode(s, &proc_root);
if (!root_inode) {
@@ -124,27 +137,45 @@ static int proc_fill_super(struct super_block *s, void *data, int silent)
return proc_setup_thread_self(s);
}
-int proc_remount(struct super_block *sb, int *flags, char *data)
+int proc_remount(struct super_block *sb, struct sb_config *sc)
{
struct pid_namespace *pid = sb->s_fs_info;
sync_filesystem(sb);
- return !proc_parse_options(data, pid);
+
+ if (sc)
+ proc_set_options(sb, sc, pid, current_user_ns());
+ return 0;
}
-static struct dentry *proc_mount(struct file_system_type *fs_type,
- int flags, const char *dev_name, void *data)
+static struct dentry *proc_mount(struct sb_config *sc)
{
- struct pid_namespace *ns;
+ struct proc_sb_config *cfg = container_of(sc, struct proc_sb_config, sc);
- if (flags & MS_KERNMOUNT) {
- ns = data;
- data = NULL;
- } else {
- ns = task_active_pid_ns(current);
- }
+ return mount_ns_sc(sc, proc_fill_super, cfg->pid_ns);
+}
- return mount_ns(fs_type, flags, data, ns, ns->user_ns, proc_fill_super);
+static void proc_sb_config_free(struct sb_config *sc)
+{
+ struct proc_sb_config *cfg = container_of(sc, struct proc_sb_config, sc);
+
+ if (cfg->pid_ns)
+ put_pid_ns(cfg->pid_ns);
+}
+
+static const struct sb_config_operations proc_sb_config_ops = {
+ .free = proc_sb_config_free,
+ .parse_option = proc_parse_mount_option,
+ .mount = proc_mount,
+};
+
+static int proc_init_sb_config(struct sb_config *sc, struct super_block *src_sb)
+{
+ struct proc_sb_config *cfg = container_of(sc, struct proc_sb_config, sc);
+
+ cfg->pid_ns = get_pid_ns(task_active_pid_ns(current));
+ cfg->sc.ops = &proc_sb_config_ops;
+ return 0;
}
static void proc_kill_sb(struct super_block *sb)
@@ -162,7 +193,8 @@ static void proc_kill_sb(struct super_block *sb)
static struct file_system_type proc_fs_type = {
.name = "proc",
- .mount = proc_mount,
+ .sb_config_size = sizeof(struct proc_sb_config),
+ .init_sb_config = proc_init_sb_config,
.kill_sb = proc_kill_sb,
.fs_flags = FS_USERNS_MOUNT,
};
@@ -210,7 +242,7 @@ static struct dentry *proc_root_lookup(struct inode * dir, struct dentry * dentr
{
if (!proc_pid_lookup(dir, dentry, flags))
return NULL;
-
+
return proc_lookup(dir, dentry, flags);
}
@@ -249,12 +281,12 @@ static const struct inode_operations proc_root_inode_operations = {
* This is the root "inode" in the /proc tree..
*/
struct proc_dir_entry proc_root = {
- .low_ino = PROC_ROOT_INO,
- .namelen = 5,
- .mode = S_IFDIR | S_IRUGO | S_IXUGO,
- .nlink = 2,
+ .low_ino = PROC_ROOT_INO,
+ .namelen = 5,
+ .mode = S_IFDIR | S_IRUGO | S_IXUGO,
+ .nlink = 2,
.count = ATOMIC_INIT(1),
- .proc_iops = &proc_root_inode_operations,
+ .proc_iops = &proc_root_inode_operations,
.proc_fops = &proc_root_operations,
.parent = &proc_root,
.subdir = RB_ROOT,
@@ -263,9 +295,23 @@ struct proc_dir_entry proc_root = {
int pid_ns_prepare_proc(struct pid_namespace *ns)
{
+ struct proc_sb_config *cfg;
+ struct sb_config *sc;
struct vfsmount *mnt;
- mnt = kern_mount_data(&proc_fs_type, ns);
+ sc = __vfs_new_sb_config(&proc_fs_type, NULL, 0, SB_CONFIG_FOR_NEW);
+ if (IS_ERR(sc))
+ return PTR_ERR(sc);
+
+ cfg = container_of(sc, struct proc_sb_config, sc);
+ if (cfg->pid_ns != ns) {
+ put_pid_ns(cfg->pid_ns);
+ get_pid_ns(ns);
+ cfg->pid_ns = ns;
+ }
+
+ mnt = kern_mount_data_sc(sc);
+ put_sb_config(sc);
if (IS_ERR(mnt))
return PTR_ERR(mnt);
Support legacy filesystems by creating a set of legacy sb_config operations
that builds up a list of mount options and then invokes fs_type->mount()
within the sb_config mount operation.
All filesystems can then be accessed using sb_config and the
fs_type->mount() call is _only_ used from within legacy_mount(). This
allows some simplification to take place in the core mount code.
---
fs/namespace.c | 36 ++-----------
fs/sb_config.c | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 161 insertions(+), 35 deletions(-)
diff --git a/fs/namespace.c b/fs/namespace.c
index 6e43657d78bd..bb339252c592 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -2577,7 +2577,6 @@ static int do_new_mount(struct path *mountpoint, const char *fstype, int flags,
int mnt_flags, const char *name, void *data)
{
struct sb_config *sc;
- struct vfsmount *mnt;
int err;
if (!fstype)
@@ -2593,40 +2592,17 @@ static int do_new_mount(struct path *mountpoint, const char *fstype, int flags,
if (!sc->device)
goto err_sc;
- if (sc->ops) {
- err = parse_monolithic_mount_data(sc, data);
- if (err < 0)
- goto err_sc;
-
- err = do_new_mount_sc(sc, mountpoint, mnt_flags);
- if (err)
- goto err_sc;
-
- } else {
- mnt = vfs_kern_mount(sc->fs_type, flags, name, data);
- if (!IS_ERR(mnt) && (sc->fs_type->fs_flags & FS_HAS_SUBTYPE) &&
- !mnt->mnt_sb->s_subtype)
- mnt = fs_set_subtype(mnt, fstype);
-
- if (IS_ERR(mnt)) {
- err = PTR_ERR(mnt);
- goto err_sc;
- }
-
- err = -EPERM;
- if (mount_too_revealing(mnt, &mnt_flags))
- goto err_mnt;
+ err = parse_monolithic_mount_data(sc, data);
+ if (err < 0)
+ goto err_sc;
- err = do_add_mount(real_mount(mnt), mountpoint, mnt_flags);
- if (err)
- goto err_mnt;
- }
+ err = do_new_mount_sc(sc, mountpoint, mnt_flags);
+ if (err)
+ goto err_sc;
put_sb_config(sc);
return 0;
-err_mnt:
- mntput(mnt);
err_sc:
if (sc->error_msg)
pr_info("Mount failed: %s\n", sc->error_msg);
diff --git a/fs/sb_config.c b/fs/sb_config.c
index 9c45e269b3cc..62e309cd62ef 100644
--- a/fs/sb_config.c
+++ b/fs/sb_config.c
@@ -25,6 +25,15 @@
#include <net/net_namespace.h>
#include "mount.h"
+struct legacy_sb_config {
+ struct sb_config sc;
+ char *legacy_data; /* Data page for legacy filesystems */
+ char *secdata;
+ unsigned int data_usage;
+};
+
+static const struct sb_config_operations legacy_sb_config_ops;
+
static const match_table_t common_set_mount_options = {
{ MS_DIRSYNC, "dirsync" },
{ MS_I_VERSION, "iversion" },
@@ -186,13 +195,15 @@ struct sb_config *__vfs_new_sb_config(struct file_system_type *fs_type,
enum sb_config_purpose purpose)
{
struct sb_config *sc;
+ size_t sc_size = fs_type->sb_config_size;
int ret;
- BUG_ON(fs_type->init_sb_config &&
- fs_type->sb_config_size < sizeof(*sc));
+ BUG_ON(fs_type->init_sb_config && sc_size < sizeof(*sc));
+
+ if (!fs_type->init_sb_config)
+ sc_size = sizeof(struct legacy_sb_config);
- sc = kzalloc(max_t(size_t, fs_type->sb_config_size, sizeof(*sc)),
- GFP_KERNEL);
+ sc = kzalloc(sc_size, GFP_KERNEL);
if (!sc)
return ERR_PTR(-ENOMEM);
@@ -208,9 +219,11 @@ struct sb_config *__vfs_new_sb_config(struct file_system_type *fs_type,
ret = sc->fs_type->init_sb_config(sc, src_sb);
if (ret < 0)
goto err_sc;
+ } else {
+ sc->ops = &legacy_sb_config_ops;
}
- /* Do the security check last because ->fsopen may change the
+ /* Do the security check last because ->init_sb_config may change the
* namespace subscriptions.
*/
ret = security_sb_config_alloc(sc, src_sb);
@@ -272,11 +285,16 @@ struct sb_config *vfs_sb_reconfig(struct vfsmount *mnt,
struct sb_config *vfs_dup_sb_config(struct sb_config *src_sc)
{
struct sb_config *sc;
+ size_t sc_size;
int ret;
if (!src_sc->ops->dup)
return ERR_PTR(-ENOTSUPP);
+ sc_size = src_sc->fs_type->sb_config_size;
+ if (!src_sc->fs_type->init_sb_config)
+ sc_size = sizeof(struct legacy_sb_config);
+
sc = kmemdup(src_sc, src_sc->fs_type->sb_config_size, GFP_KERNEL);
if (!sc)
return ERR_PTR(-ENOMEM);
@@ -324,3 +342,135 @@ void put_sb_config(struct sb_config *sc)
kfree(sc);
}
EXPORT_SYMBOL(put_sb_config);
+
+/*
+ * Free the config for a filesystem that doesn't support sb_config.
+ */
+static void legacy_sb_config_free(struct sb_config *sc)
+{
+ struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc);
+
+ free_secdata(cfg->secdata);
+ kfree(cfg->legacy_data);
+}
+
+/*
+ * Duplicate a legacy config.
+ */
+static int legacy_sb_config_dup(struct sb_config *sc, struct sb_config *src_sc)
+{
+ struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc);
+ struct legacy_sb_config *src_cfg = container_of(src_sc, struct legacy_sb_config, sc);
+
+ cfg->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!cfg->legacy_data)
+ return -ENOMEM;
+ memcpy(cfg->legacy_data, src_cfg->legacy_data, sizeof(PAGE_SIZE));
+ return 0;
+}
+
+/*
+ * Add an option to a legacy config. We build up a comma-separated list of
+ * options.
+ */
+static int legacy_parse_option(struct sb_config *sc, char *p)
+{
+ struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc);
+ unsigned int usage = cfg->data_usage;
+ size_t len = strlen(p);
+
+ if (len > PAGE_SIZE - 2 - usage)
+ return sb_cfg_inval(sc, "VFS: Insufficient data buffer space");
+ if (memchr(p, ',', len) != NULL)
+ return sb_cfg_inval(sc, "VFS: Options cannot contain commas");
+ if (!cfg->legacy_data) {
+ cfg->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!cfg->legacy_data)
+ return -ENOMEM;
+ }
+
+ cfg->legacy_data[usage++] = ',';
+ memcpy(cfg->legacy_data + usage, p, len);
+ usage += len;
+ cfg->legacy_data[usage] = '\0';
+ cfg->data_usage = usage;
+ return 0;
+}
+
+/*
+ * Add monolithic mount data.
+ */
+static int legacy_monolithic_mount_data(struct sb_config *sc, void *data)
+{
+ struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc);
+
+ if (cfg->data_usage != 0)
+ return sb_cfg_inval(sc, "VFS: Can't mix monolithic and individual options");
+ if (!data)
+ return 0;
+ if (!cfg->legacy_data) {
+ cfg->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!cfg->legacy_data)
+ return -ENOMEM;
+ }
+
+ memcpy(cfg->legacy_data, data, PAGE_SIZE);
+ cfg->data_usage = PAGE_SIZE;
+ return 0;
+}
+
+/*
+ * Use the legacy mount validation step to strip out and process security
+ * config options.
+ */
+static int legacy_validate(struct sb_config *sc)
+{
+ struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc);
+
+ if (!cfg->legacy_data || cfg->sc.fs_type->fs_flags & FS_BINARY_MOUNTDATA)
+ return 0;
+
+ cfg->secdata = alloc_secdata();
+ if (!cfg->secdata)
+ return -ENOMEM;
+
+ return security_sb_copy_data(cfg->legacy_data, cfg->secdata);
+}
+
+/*
+ * Perform a legacy mount.
+ */
+static struct dentry *legacy_mount(struct sb_config *sc)
+{
+ struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc);
+ struct super_block *sb;
+ struct dentry *root;
+ int ret;
+
+ root = cfg->sc.fs_type->mount(cfg->sc.fs_type, cfg->sc.ms_flags,
+ cfg->sc.device, cfg->legacy_data);
+ if (IS_ERR(root))
+ return ERR_CAST(root);
+
+ sb = root->d_sb;
+ BUG_ON(!sb);
+ ret = security_sb_kern_mount(sb, cfg->sc.ms_flags, cfg->secdata);
+ if (ret < 0)
+ goto err_sb;
+
+ return root;
+
+err_sb:
+ dput(root);
+ deactivate_locked_super(sb);
+ return ERR_PTR(ret);
+}
+
+static const struct sb_config_operations legacy_sb_config_ops = {
+ .free = legacy_sb_config_free,
+ .dup = legacy_sb_config_dup,
+ .parse_option = legacy_parse_option,
+ .monolithic_mount_data = legacy_monolithic_mount_data,
+ .validate = legacy_validate,
+ .mount = legacy_mount,
+};
Institute a separate step in the mounting procedure:
(1) Create new sb_config context.
(2) Configure the context.
(3) Create superblock.
(4) Mount the superblock any number of times.
(5) Destroy the context.
Step (3) is added and must be given by userspace before fsmount() may be
used:
mfd = fsopen("nfs4", -1, 0);
E_write(mfd, "d warthog:/root");
E_write(mfd, "o fsc");
E_write(mfd, "o sync");
E_write(mfd, "o intr");
E_write(mfd, "o foo");
E_write(mfd, "create"); <---- here
fsmount(mfd, ...);
This the sb_config context to be used to reconfigure a superblock (not
implemented yet):
mfd = mntopen("/mnt/foo);
E_write(mfd, "o foo=1");
E_write(mfd, "o nobar");
E_write(mfd, "update"); <---- atomically update the superblock
---
Documentation/filesystems/mounting.txt | 54 ++++--
fs/fsopen.c | 43 ++++-
fs/internal.h | 2
fs/libfs.c | 16 ++
fs/namespace.c | 270 +++++++++++++++++---------------
fs/nfs/getroot.c | 76 +++++----
fs/nfs/internal.h | 18 +-
fs/nfs/mount.c | 64 ++++----
fs/nfs/nfs3proc.c | 3
fs/nfs/nfs4_fs.h | 4
fs/nfs/nfs4proc.c | 4
fs/nfs/nfs4super.c | 77 +++++----
fs/nfs/proc.c | 3
fs/nfs/super.c | 46 +++--
fs/proc/root.c | 11 +
fs/sb_config.c | 74 +++++++--
fs/super.c | 72 +--------
include/linux/fs.h | 8 -
include/linux/lsm_hooks.h | 13 +-
include/linux/nfs_xdr.h | 4
include/linux/sb_config.h | 11 +
include/linux/security.h | 11 -
security/security.c | 14 --
security/selinux/hooks.c | 31 +---
24 files changed, 485 insertions(+), 444 deletions(-)
diff --git a/Documentation/filesystems/mounting.txt b/Documentation/filesystems/mounting.txt
index 03e9086f754d..4d920ad1ef05 100644
--- a/Documentation/filesystems/mounting.txt
+++ b/Documentation/filesystems/mounting.txt
@@ -75,6 +75,7 @@ configuration context. This is represented by the sb_config structure:
struct sb_config {
const struct sb_config_operations *ops;
struct file_system_type *fs;
+ struct dentry *root;
struct user_namespace *user_ns;
struct net *net_ns;
const struct cred *cred;
@@ -82,9 +83,9 @@ configuration context. This is represented by the sb_config structure:
void *security;
const char *error_msg;
unsigned int ms_flags;
- bool mounted;
bool sloppy;
bool silent;
+ bool degraded;
enum mount_type mount_type : 8;
};
@@ -122,6 +123,11 @@ The sb_config fields are as follows:
A pointer to the file_system_type of the filesystem that is being
constructed or reconfigured. This retains a ref on the type owner.
+ (*) struct dentry *root
+
+ A pointer to the root of the mountable tree (and indirectly, the
+ superblock thereof). This is filled in by the ->get_tree() op.
+
(*) struct user_namespace *user_ns
(*) struct net *net_ns
@@ -164,12 +170,6 @@ The sb_config fields are as follows:
This holds the MS_* flags mount flags.
- (*) bool mounted
-
- This is set to true once a mount attempt is made. This causes an error to
- be given on subsequent mount attempts with the same context and prevents
- multiple mount attempts.
-
(*) bool sloppy
(*) bool silent
@@ -181,6 +181,12 @@ The sb_config fields are as follows:
[NOTE] silent is probably redundant with ms_flags & MS_SILENT.
+ (*) bool degraded
+
+ This is set if any preallocated resources in the configuration have been
+ used up, thereby rendering the configuration unreusable for the
+ ->get_tree() op.
+
(*) enum mount_type
This indicates the type of mount operation. The available values are:
@@ -217,7 +223,7 @@ The superblock configuration context points to a table of operations:
int (*parse_option)(struct sb_config *sc, char *p);
int (*monolithic_mount_data)(struct sb_config *sc, void *data);
int (*validate)(struct sb_config *sc);
- struct dentry *(*mount)(struct sb_config *sc);
+ int (*get_tree)(struct sb_config *sc);
};
These operations are invoked by the various stages of the mount procedure to
@@ -279,18 +285,18 @@ manage the superblock configuration context. They are as follows:
The return value is as for ->parse_option().
- (*) struct dentry *(*mount)(struct sb_config *sc);
+ (*) int (*get_tree)(struct sb_config *sc);
- Called to effect a new mount or new submount using the information stored
- in the superblock configuration context (remounts go via a different
- vector). It may detach any resources it desires from the superblock
- configuration context and transfer them to the superblock it creates.
+ Called to get or create the mountable root and superblock, using the
+ information stored in the superblock configuration context (remounts go
+ via a different vector). It may detach any resources it desires from the
+ superblock configuration context and transfer them to the superblock it
+ creates.
- On success it should return the dentry that's at the root of the mount.
- In future, sc->root_path will then be applied to this.
+ On success it should set sc->root to the mountable root.
- In the case of an error, it should return a negative error code and invoke
- sb_cfg_inval() or sb_cfg_error().
+ In the case of an error, it should return a negative error code and
+ consider invoking sb_cfg_inval() or sb_cfg_error().
=========================================
@@ -379,7 +385,7 @@ one for destroying a context:
extant mount and initialise the mount parameters from the superblock
underlying that mount. This is for use by remount.
- (*) struct sb_config *vfs_fsopen(const char *fs_name);
+ (*) struct sb_config *vfs_new_sb_config(const char *fs_name);
Create a superblock configuration context given a filesystem name. It is
assumed that the mount flags will be passed in as text options or set
@@ -425,11 +431,19 @@ In the remaining operations, if an error occurs, a negative error code is
returned and, if not obvious, sc->error_msg may have been set to point to a
useful string. This string should not be freed.
+ (*) int vfs_get_tree(struct sb_config *sc);
+
+ Get or create the mountable root and superblock, using the parameters in
+ the parsed configuration to select/configure the superblock. This invokes
+ the ->validate() op and then the ->get_tree() op.
+
+ [NOTE] ->validate() can probably be rolled into ->get_tree() and
+ ->remount_fs_sc().
+
(*) struct vfsmount *vfs_kern_mount_sc(struct sb_config *sc);
Create a mount given the parameters in the specified superblock
- configuration context. This invokes the ->validate() op and then the
- ->mount() op.
+ configuration context.
(*) struct vfsmount *vfs_submount_sc(const struct dentry *mountpoint,
struct sb_config *sc);
diff --git a/fs/fsopen.c b/fs/fsopen.c
index a4e9d5a7ce2b..0cc79a4f8ae7 100644
--- a/fs/fsopen.c
+++ b/fs/fsopen.c
@@ -52,22 +52,25 @@ static ssize_t fs_fs_read(struct file *file, char __user *_buf, size_t len, loff
}
/*
- * Userspace writes configuration data to the fd and we parse it here. For the
- * moment, we assume a single option per write. Each line written is of the form
+ * Userspace writes configuration data and commands to the fd and we parse it
+ * here. For the moment, we assume a single option or command per write. Each
+ * line written is of the form
*
* <option_type><space><stuff...>
+ * <command>
*
* d /dev/sda1 -- Device name
* o noatime -- Option without value
* o cell=grand.central.org -- Option with value
- * r / -- Dir within device to mount
+ * create -- Create a superblock
+ * update -- Reconfigure a superblock
*/
static ssize_t fs_fs_write(struct file *file,
const char __user *_buf, size_t len, loff_t *pos)
{
struct sb_config *sc = file->private_data;
struct inode *inode = file_inode(file);
- char opt[2], *data;
+ char opt[8], *data;
ssize_t ret;
if (len < 3 || len > 4095)
@@ -79,11 +82,21 @@ static ssize_t fs_fs_write(struct file *file,
case 's':
case 'o':
break;
+ case 'c':
+ case 'u':
+ if (len != 6)
+ goto err_bad_cmd;
+ if (copy_from_user(opt, _buf, 6) != 0)
+ return -EFAULT;
+ if (memcmp(opt, "create", 6) == 0 ||
+ memcmp(opt, "update", 6) == 0)
+ break;
+ goto err_bad_cmd;
default:
- return sb_cfg_inval(sc, "VFS: Unsupported write spec");
+ goto err_bad_cmd;
}
if (opt[1] != ' ')
- return sb_cfg_inval(sc, "VFS: Unsupported write spec");
+ goto err_bad_cmd;
data = memdup_user_nul(_buf + 2, len - 2);
if (IS_ERR(data))
@@ -96,10 +109,6 @@ static ssize_t fs_fs_write(struct file *file,
if (ret < 0)
goto err_free;
- ret = -EBUSY;
- if (sc->mounted)
- goto err_unlock;
-
ret = -EINVAL;
switch (opt[0]) {
case 's':
@@ -115,6 +124,18 @@ static ssize_t fs_fs_write(struct file *file,
goto err_unlock;
break;
+ case 'c':
+ ret = vfs_get_tree(sc);
+ if (ret < 0)
+ goto err_unlock;
+ break;
+
+ case 'u':
+ ret = vfs_reconfigure_super(sc);
+ if (ret < 0)
+ goto err_unlock;
+ break;
+
default:
goto err_unlock;
}
@@ -125,6 +146,8 @@ static ssize_t fs_fs_write(struct file *file,
err_free:
kfree(data);
return ret;
+err_bad_cmd:
+ return sb_cfg_inval(sc, "VFS: Unsupported write spec");
}
const struct file_operations fs_fs_fops = {
diff --git a/fs/internal.h b/fs/internal.h
index 6ac2191cb59a..183f248fb819 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -89,8 +89,6 @@ extern struct file *get_empty_filp(void);
*/
extern int do_remount_sb(struct super_block *, int, void *, int, struct sb_config *);
extern bool trylock_super(struct super_block *sb);
-extern struct dentry *mount_fs(struct file_system_type *,
- int, const char *, void *);
extern struct super_block *user_get_super(dev_t);
/*
diff --git a/fs/libfs.c b/fs/libfs.c
index 2733d070b1ef..e2c2b4c48ea3 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -575,13 +575,27 @@ static DEFINE_SPINLOCK(pin_fs_lock);
int simple_pin_fs(struct file_system_type *type, struct vfsmount **mount, int *count)
{
+ struct sb_config *sc;
struct vfsmount *mnt = NULL;
+ int ret;
+
spin_lock(&pin_fs_lock);
if (unlikely(!*mount)) {
spin_unlock(&pin_fs_lock);
- mnt = vfs_kern_mount(type, MS_KERNMOUNT, type->name, NULL);
+
+ sc = __vfs_new_sb_config(type, NULL, MS_KERNMOUNT, SB_CONFIG_FOR_NEW);
+ if (IS_ERR(sc))
+ return PTR_ERR(sc);
+
+ ret = vfs_get_tree(sc);
+ if (ret < 0)
+ return ret;
+
+ mnt = vfs_kern_mount_sc(sc);
+ put_sb_config(sc);
if (IS_ERR(mnt))
return PTR_ERR(mnt);
+
spin_lock(&pin_fs_lock);
if (!*mount)
*mount = mnt;
diff --git a/fs/namespace.c b/fs/namespace.c
index bb339252c592..3056e7fbf5ef 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -962,55 +962,6 @@ static struct mount *skip_mnt_tree(struct mount *p)
return p;
}
-struct vfsmount *
-vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
-{
- struct mount *mnt;
- struct dentry *root;
-
- if (!type)
- return ERR_PTR(-ENODEV);
-
- mnt = alloc_vfsmnt(name);
- if (!mnt)
- return ERR_PTR(-ENOMEM);
-
- if (flags & MS_KERNMOUNT)
- mnt->mnt.mnt_flags = MNT_INTERNAL;
-
- root = mount_fs(type, flags, name, data);
- if (IS_ERR(root)) {
- mnt_free_id(mnt);
- free_vfsmnt(mnt);
- return ERR_CAST(root);
- }
-
- mnt->mnt.mnt_root = root;
- mnt->mnt.mnt_sb = root->d_sb;
- mnt->mnt_mountpoint = mnt->mnt.mnt_root;
- mnt->mnt_parent = mnt;
- lock_mount_hash();
- list_add_tail(&mnt->mnt_instance, &root->d_sb->s_mounts);
- unlock_mount_hash();
- return &mnt->mnt;
-}
-EXPORT_SYMBOL_GPL(vfs_kern_mount);
-
-struct vfsmount *
-vfs_submount(const struct dentry *mountpoint, struct file_system_type *type,
- const char *name, void *data)
-{
- /* Until it is worked out how to pass the user namespace
- * through from the parent mount to the submount don't support
- * unprivileged mounts with submounts.
- */
- if (mountpoint->d_sb->s_user_ns != &init_user_ns)
- return ERR_PTR(-EPERM);
-
- return vfs_kern_mount(type, MS_SUBMOUNT, name, data);
-}
-EXPORT_SYMBOL_GPL(vfs_submount);
-
static struct mount *clone_mnt(struct mount *old, struct dentry *root,
int flag)
{
@@ -2286,18 +2237,12 @@ static int change_mount_flags(struct vfsmount *mnt, int ms_flags)
static int parse_monolithic_mount_data(struct sb_config *sc, void *data)
{
int (*monolithic_mount_data)(struct sb_config *, void *);
- int ret;
monolithic_mount_data = sc->ops->monolithic_mount_data;
if (!monolithic_mount_data)
monolithic_mount_data = generic_monolithic_mount_data;
- ret = monolithic_mount_data(sc, data);
- if (ret < 0)
- return ret;
- if (sc->ops->validate)
- return sc->ops->validate(sc);
- return 0;
+ return monolithic_mount_data(sc, data);
}
/*
@@ -2461,29 +2406,6 @@ static int do_move_mount(struct path *path, const char *old_name)
return err;
}
-static struct vfsmount *fs_set_subtype(struct vfsmount *mnt, const char *fstype)
-{
- int err;
- const char *subtype = strchr(fstype, '.');
- if (subtype) {
- subtype++;
- err = -EINVAL;
- if (!subtype[0])
- goto err;
- } else
- subtype = "";
-
- mnt->mnt_sb->s_subtype = kstrdup(subtype, GFP_KERNEL);
- err = -ENOMEM;
- if (!mnt->mnt_sb->s_subtype)
- goto err;
- return mnt;
-
- err:
- mntput(mnt);
- return ERR_PTR(err);
-}
-
/*
* add a mount into a namespace's mount tree
*/
@@ -2544,11 +2466,10 @@ static int do_new_mount_sc(struct sb_config *sc, struct path *mountpoint,
if (IS_ERR(mnt))
return PTR_ERR(mnt);
- if ((sc->fs_type->fs_flags & FS_HAS_SUBTYPE) &&
- !mnt->mnt_sb->s_subtype) {
- mnt = fs_set_subtype(mnt, sc->fs_type->name);
- if (IS_ERR(mnt))
- return PTR_ERR(mnt);
+ if (sc->subtype && !mnt->mnt_sb->s_subtype) {
+ mnt->mnt_sb->s_subtype = kstrdup(sc->subtype, GFP_KERNEL);
+ if (!mnt->mnt_sb->s_subtype)
+ return -ENOMEM;
}
ret = -EPERM;
@@ -2583,8 +2504,10 @@ static int do_new_mount(struct path *mountpoint, const char *fstype, int flags,
return -EINVAL;
sc = vfs_new_sb_config(fstype);
- if (IS_ERR(sc))
- return PTR_ERR(sc);
+ if (IS_ERR(sc)) {
+ err = PTR_ERR(sc);
+ goto err;
+ }
sc->ms_flags = flags;
err = -ENOMEM;
@@ -2596,6 +2519,10 @@ static int do_new_mount(struct path *mountpoint, const char *fstype, int flags,
if (err < 0)
goto err_sc;
+ err = vfs_get_tree(sc);
+ if (err < 0)
+ goto err_sc;
+
err = do_new_mount_sc(sc, mountpoint, mnt_flags);
if (err)
goto err_sc;
@@ -2607,6 +2534,7 @@ static int do_new_mount(struct path *mountpoint, const char *fstype, int flags,
if (sc->error_msg)
pr_info("Mount failed: %s\n", sc->error_msg);
put_sb_config(sc);
+err:
return err;
}
@@ -3139,54 +3067,87 @@ SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
return ret;
}
-static struct dentry *__do_mount_sc(struct sb_config *sc)
+/**
+ * vfs_get_tree - Get the mountable root
+ * @sc: The superblock configuration context.
+ *
+ * The filesystem is invoked to get or create a superblock which can then later
+ * be used for mounting. The filesystem places a pointer to the root to be
+ * used for mounting in @sc->root.
+ */
+int vfs_get_tree(struct sb_config *sc)
{
struct super_block *sb;
- struct dentry *root;
int ret;
- root = sc->ops->mount(sc);
- if (IS_ERR(root))
- return root;
+ if (sc->root)
+ return -EBUSY;
+
+ if (sc->ops->validate) {
+ ret = sc->ops->validate(sc);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* We assume that the filesystem may transfer preallocated resources
+ * from the configuration context to the superblock, thereby rendering
+ * the config unusable for another attempt at creation if this one
+ * fails.
+ */
+ if (sc->degraded)
+ return sb_cfg_inval(sc, "VFS: The config is degraded");
+ sc->degraded = true;
+
+ /* Get the mountable root in sc->root, with a ref on the root and a ref
+ * on the superblock.
+ */
+ ret = sc->ops->get_tree(sc);
+ if (ret < 0)
+ return ret;
- sb = root->d_sb;
- BUG_ON(!sb);
+ BUG_ON(!sc->root);
+ sb = sc->root->d_sb;
WARN_ON(!sb->s_bdi);
- sb->s_flags |= MS_BORN;
- ret = security_sb_config_kern_mount(sc, sb);
+ ret = security_sb_get_tree(sc);
if (ret < 0)
goto err_sb;
- /*
- * filesystems should never set s_maxbytes larger than MAX_LFS_FILESIZE
- * but s_maxbytes was an unsigned long long for many releases. Throw
+ sb->s_flags |= MS_BORN;
+
+ /* Filesystems should never set s_maxbytes larger than MAX_LFS_FILESIZE
+ * but s_maxbytes was an unsigned long long for many releases. Throw
* this warning for a little while to try and catch filesystems that
* violate this rule.
*/
- WARN((sb->s_maxbytes < 0), "%s set sb->s_maxbytes to "
- "negative value (%lld)\n", sc->fs_type->name, sb->s_maxbytes);
+ WARN(sb->s_maxbytes < 0,
+ "%s set sb->s_maxbytes to negative value (%lld)\n",
+ sc->fs_type->name, sb->s_maxbytes);
up_write(&sb->s_umount);
- return root;
+ return 0;
err_sb:
- dput(root);
+ dput(sc->root);
+ sc->root = NULL;
deactivate_locked_super(sb);
- return ERR_PTR(ret);
+ return ret;
}
+EXPORT_SYMBOL(vfs_get_tree);
+/**
+ * vfs_kern_mount_sc - Create a mount for a configured superblock
+ * sc: The configuration context with the superblock attached
+ *
+ * Create a mount to an already configured superblock. If necessary, the
+ * caller should invoke vfs_create_super() before calling this.
+ */
struct vfsmount *vfs_kern_mount_sc(struct sb_config *sc)
{
- struct dentry *root;
struct mount *mnt;
- int ret;
- if (sc->ops->validate) {
- ret = sc->ops->validate(sc);
- if (ret < 0)
- return ERR_PTR(ret);
- }
+ if (!sc->root)
+ return ERR_PTR(sb_cfg_inval(sc, "VFS: Root must be obtained before mount"));
mnt = alloc_vfsmnt(sc->device ?: "none");
if (!mnt)
@@ -3195,24 +3156,65 @@ struct vfsmount *vfs_kern_mount_sc(struct sb_config *sc)
if (sc->ms_flags & MS_KERNMOUNT)
mnt->mnt.mnt_flags = MNT_INTERNAL;
- root = __do_mount_sc(sc);
- if (IS_ERR(root)) {
- mnt_free_id(mnt);
- free_vfsmnt(mnt);
- return ERR_CAST(root);
- }
-
- mnt->mnt.mnt_root = root;
- mnt->mnt.mnt_sb = root->d_sb;
+ atomic_inc(&sc->root->d_sb->s_active);
+ mnt->mnt.mnt_sb = sc->root->d_sb;
+ mnt->mnt.mnt_root = dget(sc->root);
mnt->mnt_mountpoint = mnt->mnt.mnt_root;
mnt->mnt_parent = mnt;
+
lock_mount_hash();
- list_add_tail(&mnt->mnt_instance, &root->d_sb->s_mounts);
+ list_add_tail(&mnt->mnt_instance, &mnt->mnt.mnt_sb->s_mounts);
unlock_mount_hash();
return &mnt->mnt;
}
EXPORT_SYMBOL_GPL(vfs_kern_mount_sc);
+struct vfsmount *vfs_kern_mount(struct file_system_type *type,
+ int flags, const char *name, void *data)
+{
+ struct sb_config *sc;
+ struct vfsmount *mnt;
+ int ret;
+
+ if (!type)
+ return ERR_PTR(-EINVAL);
+
+ sc = __vfs_new_sb_config(type, NULL, flags, SB_CONFIG_FOR_NEW);
+ if (IS_ERR(sc))
+ return ERR_CAST(sc);
+
+ if (name) {
+ ret = -ENOMEM;
+ sc->device = kstrdup(name, GFP_KERNEL);
+ if (!sc->device)
+ goto err_sc;
+ }
+
+ ret = parse_monolithic_mount_data(sc, data);
+ if (ret < 0)
+ goto err_sc;
+
+ ret = vfs_get_tree(sc);
+ if (ret < 0)
+ goto err_sc;
+
+ mnt = vfs_kern_mount_sc(sc);
+ if (IS_ERR(mnt)) {
+ ret = PTR_ERR(mnt);
+ goto err_sc;
+ }
+
+ put_sb_config(sc);
+ return mnt;
+
+err_sc:
+ if (sc->error_msg)
+ pr_info("Kernmount failed: %s\n", sc->error_msg);
+ put_sb_config(sc);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(vfs_kern_mount);
+
struct vfsmount *
vfs_submount_sc(const struct dentry *mountpoint, struct sb_config *sc)
{
@@ -3228,6 +3230,21 @@ vfs_submount_sc(const struct dentry *mountpoint, struct sb_config *sc)
}
EXPORT_SYMBOL_GPL(vfs_submount_sc);
+struct vfsmount *
+vfs_submount(const struct dentry *mountpoint, struct file_system_type *type,
+ const char *name, void *data)
+{
+ /* Until it is worked out how to pass the user namespace
+ * through from the parent mount to the submount don't support
+ * unprivileged mounts with submounts.
+ */
+ if (mountpoint->d_sb->s_user_ns != &init_user_ns)
+ return ERR_PTR(-EPERM);
+
+ return vfs_kern_mount(type, MS_SUBMOUNT, name, data);
+}
+EXPORT_SYMBOL_GPL(vfs_submount);
+
/*
* Mount a new, prepared superblock (specified by fs_fd) on the location
* specified by dfd and dir_name. dfd can be AT_FDCWD, a dir fd or a container
@@ -3286,17 +3303,14 @@ SYSCALL_DEFINE5(fsmount, int, fs_fd, int, dfd, const char __user *, dir_name,
((sc->ms_flags & MS_MANDLOCK) && !may_mandlock()))
goto err_fsfd;
- /* Prevent further changes. */
+ /* There must be a valid superblock or we can't mount it */
inode = file_inode(f.file);
ret = inode_lock_killable(inode);
- if (ret < 0)
- goto err_fsfd;
- ret = -EBUSY;
- if (!sc->mounted) {
- sc->mounted = true;
- ret = 0;
+ if (ret == 0) {
+ if (!sc->root)
+ ret = sb_cfg_inval(sc, "VFS: Root must be obtained before mount");
+ inode_unlock(inode);
}
- inode_unlock(inode);
if (ret < 0)
goto err_fsfd;
diff --git a/fs/nfs/getroot.c b/fs/nfs/getroot.c
index 391dafaf9182..f564ab76708f 100644
--- a/fs/nfs/getroot.c
+++ b/fs/nfs/getroot.c
@@ -66,68 +66,72 @@ static int nfs_superblock_set_dummy_root(struct super_block *sb, struct inode *i
}
/*
- * get an NFS2/NFS3 root dentry from the root filehandle
+ * get an NFS2/NFS3 root dentry from the root filehandle.
*/
-struct dentry *nfs_get_root(struct super_block *sb, struct nfs_fh *mntfh,
- const char *devname)
+int nfs_get_root(struct super_block *s, struct nfs_sb_config *cfg)
{
- struct nfs_server *server = NFS_SB(sb);
+ struct nfs_server *server = NFS_SB(s);
struct nfs_fsinfo fsinfo;
- struct dentry *ret;
+ struct dentry *root;
struct inode *inode;
- void *name = kstrdup(devname, GFP_KERNEL);
- int error;
+ char *name;
+ int error = -ENOMEM;
+ name = kstrdup(cfg->sc.device, GFP_KERNEL);
if (!name)
- return ERR_PTR(-ENOMEM);
-
+ goto out;
+
/* get the actual root for this mount */
fsinfo.fattr = nfs_alloc_fattr();
- if (fsinfo.fattr == NULL) {
- kfree(name);
- return ERR_PTR(-ENOMEM);
- }
+ if (fsinfo.fattr == NULL)
+ goto out_name;
- error = server->nfs_client->rpc_ops->getroot(server, mntfh, &fsinfo);
+ error = server->nfs_client->rpc_ops->getroot(server, cfg->mntfh, &fsinfo);
if (error < 0) {
dprintk("nfs_get_root: getattr error = %d\n", -error);
- ret = ERR_PTR(error);
- goto out;
+ nfs_cfg_error(cfg, "NFS: Couldn't getattr on root");
+ goto out_fattr;
}
- inode = nfs_fhget(sb, mntfh, fsinfo.fattr, NULL);
+ inode = nfs_fhget(s, cfg->mntfh, fsinfo.fattr, NULL);
if (IS_ERR(inode)) {
dprintk("nfs_get_root: get root inode failed\n");
- ret = ERR_CAST(inode);
- goto out;
+ error = PTR_ERR(inode);
+ nfs_cfg_error(cfg, "NFS: Couldn't get root inode");
+ goto out_fattr;
}
- error = nfs_superblock_set_dummy_root(sb, inode);
- if (error != 0) {
- ret = ERR_PTR(error);
- goto out;
- }
+ error = nfs_superblock_set_dummy_root(s, inode);
+ if (error != 0)
+ goto out_fattr;
/* root dentries normally start off anonymous and get spliced in later
* if the dentry tree reaches them; however if the dentry already
* exists, we'll pick it up at this point and use it as the root
*/
- ret = d_obtain_root(inode);
- if (IS_ERR(ret)) {
+ root = d_obtain_root(inode);
+ if (IS_ERR(root)) {
dprintk("nfs_get_root: get root dentry failed\n");
- goto out;
+ error = PTR_ERR(root);
+ nfs_cfg_error(cfg, "NFS: Couldn't get root dentry");
+ goto out_fattr;
}
- security_d_instantiate(ret, inode);
- spin_lock(&ret->d_lock);
- if (IS_ROOT(ret) && !ret->d_fsdata &&
- !(ret->d_flags & DCACHE_NFSFS_RENAMED)) {
- ret->d_fsdata = name;
+ security_d_instantiate(root, inode);
+ spin_lock(&root->d_lock);
+ if (IS_ROOT(root) && !root->d_fsdata &&
+ !(root->d_flags & DCACHE_NFSFS_RENAMED)) {
+ root->d_fsdata = name;
name = NULL;
}
- spin_unlock(&ret->d_lock);
-out:
- kfree(name);
+ spin_unlock(&root->d_lock);
+ cfg->sc.root = root;
+ error = 0;
+
+out_fattr:
nfs_free_fattr(fsinfo.fattr);
- return ret;
+out_name:
+ kfree(name);
+out:
+ return error;
}
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 11017c30b94d..5f63456df555 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -131,8 +131,7 @@ struct nfs_sb_config {
struct nfs_fh *mntfh;
struct nfs_subversion *nfs_mod;
- int (*set_security)(struct super_block *, struct dentry *,
- struct nfs_sb_config *);
+ int (*set_security)(struct super_block *, struct nfs_sb_config *);
/* Information for a cloned mount. */
struct nfs_clone_mount {
@@ -416,10 +415,10 @@ extern int nfs_wait_atomic_killable(atomic_t *p);
extern const struct super_operations nfs_sops;
extern struct file_system_type nfs_fs_type;
bool nfs_auth_info_match(const struct nfs_auth_info *, rpc_authflavor_t);
-struct dentry *nfs_try_mount(struct nfs_sb_config *);
-int nfs_set_sb_security(struct super_block *, struct dentry *, struct nfs_sb_config *);
-int nfs_clone_sb_security(struct super_block *, struct dentry *, struct nfs_sb_config *);
-struct dentry *nfs_fs_mount_common(struct nfs_server *, struct nfs_sb_config *);
+int nfs_try_get_tree(struct nfs_sb_config *);
+int nfs_set_sb_security(struct super_block *, struct nfs_sb_config *);
+int nfs_clone_sb_security(struct super_block *, struct nfs_sb_config *);
+int nfs_get_tree_common(struct nfs_server *, struct nfs_sb_config *);
void nfs_kill_super(struct super_block *);
int nfs_fill_super(struct super_block *, struct nfs_sb_config *);
@@ -454,12 +453,9 @@ struct vfsmount *nfs_do_submount(struct dentry *, struct nfs_fh *,
struct nfs_fattr *, rpc_authflavor_t);
/* getroot.c */
-extern struct dentry *nfs_get_root(struct super_block *, struct nfs_fh *,
- const char *);
+extern int nfs_get_root(struct super_block *s, struct nfs_sb_config *cfg);
#if IS_ENABLED(CONFIG_NFS_V4)
-extern struct dentry *nfs4_get_root(struct super_block *, struct nfs_fh *,
- const char *);
-
+extern int nfs4_get_root(struct super_block *s, struct nfs_sb_config *cfg);
extern int nfs4_get_rootfh(struct nfs_server *server, struct nfs_fh *mntfh, bool);
#endif
diff --git a/fs/nfs/mount.c b/fs/nfs/mount.c
index c930857413cb..ddbf5b44d2ca 100644
--- a/fs/nfs/mount.c
+++ b/fs/nfs/mount.c
@@ -1274,20 +1274,20 @@ static int nfs_sb_config_validate(struct sb_config *sc)
/*
* Use the preparsed information in the mount context to effect a mount.
*/
-static struct dentry *nfs_ordinary_mount(struct nfs_sb_config *cfg)
+static int nfs_get_ordinary_tree(struct nfs_sb_config *cfg)
{
cfg->set_security = nfs_set_sb_security;
- return cfg->nfs_mod->rpc_ops->try_mount(cfg);
+ return cfg->nfs_mod->rpc_ops->try_get_tree(cfg);
}
/*
* Clone an NFS2/3/4 server record on xdev traversal (FSID-change)
*/
-static struct dentry *nfs_xdev_mount(struct nfs_sb_config *cfg)
+static int nfs_get_xdev_tree(struct nfs_sb_config *cfg)
{
struct nfs_server *server;
- struct dentry *mntroot = ERR_PTR(-ENOMEM);
+ int ret;
dprintk("--> nfs_xdev_mount()\n");
@@ -1300,52 +1300,48 @@ static struct dentry *nfs_xdev_mount(struct nfs_sb_config *cfg)
cfg->selected_flavor);
if (IS_ERR(server))
- mntroot = ERR_CAST(server);
+ ret = PTR_ERR(server);
else
- mntroot = nfs_fs_mount_common(server, cfg);
+ ret = nfs_get_tree_common(server, cfg);
- dprintk("<-- nfs_xdev_mount() = %ld\n",
- IS_ERR(mntroot) ? PTR_ERR(mntroot) : 0L);
- return mntroot;
+ dprintk("<-- nfs_get_xdev_tree() = %d\n", ret);
+ return ret;
}
/*
- * Handle ordinary mounts inspired by the user and cross-FSID mounts.
+ * Create an NFS superblock by the appropriate method.
*/
-struct dentry *nfs_general_mount(struct nfs_sb_config *cfg)
-{
- switch (cfg->mount_type) {
- case NFS_MOUNT_ORDINARY:
- return nfs_ordinary_mount(cfg);
-
- case NFS_MOUNT_CROSS_DEV:
- return nfs_xdev_mount(cfg);
-
- default:
- nfs_cfg_error(cfg, "NFS: Unknown mount type");
- return ERR_PTR(-ENOTSUPP);
- }
-}
-EXPORT_SYMBOL_GPL(nfs_general_mount);
-
-static struct dentry *nfs_fs_mount(struct sb_config *sc)
+static int nfs_get_tree(struct sb_config *sc)
{
struct nfs_sb_config *cfg = container_of(sc, struct nfs_sb_config, sc);
+ int ret;
if (!cfg->nfs_mod) {
pr_warn("Missing nfs_mod\n");
- return ERR_PTR(-EINVAL);
+ return -EINVAL;
}
if (!cfg->nfs_mod->rpc_ops) {
pr_warn("Missing rpc_ops\n");
- return ERR_PTR(-EINVAL);
+ return -EINVAL;
}
- if (!cfg->nfs_mod->rpc_ops->mount) {
- pr_warn("Missing mount\n");
- return ERR_PTR(-EINVAL);
+
+ if (cfg->nfs_mod->rpc_ops->get_tree) {
+ ret = cfg->nfs_mod->rpc_ops->get_tree(cfg);
+ if (ret != 1)
+ return ret;
}
- return cfg->nfs_mod->rpc_ops->mount(cfg);
+ switch (cfg->mount_type) {
+ case NFS_MOUNT_ORDINARY:
+ return nfs_get_ordinary_tree(cfg);
+
+ case NFS_MOUNT_CROSS_DEV:
+ return nfs_get_xdev_tree(cfg);
+
+ default:
+ nfs_cfg_error(cfg, "NFS: Unknown mount type");
+ return -ENOTSUPP;
+ }
}
/*
@@ -1392,7 +1388,7 @@ static const struct sb_config_operations nfs_sb_config_ops = {
.parse_option = nfs_sb_config_parse_option,
.monolithic_mount_data = nfs_monolithic_mount_data,
.validate = nfs_sb_config_validate,
- .mount = nfs_fs_mount,
+ .get_tree = nfs_get_tree,
};
/*
diff --git a/fs/nfs/nfs3proc.c b/fs/nfs/nfs3proc.c
index 1e0349fc33ec..1a8d0f832bda 100644
--- a/fs/nfs/nfs3proc.c
+++ b/fs/nfs/nfs3proc.c
@@ -922,9 +922,8 @@ const struct nfs_rpc_ops nfs_v3_clientops = {
.file_inode_ops = &nfs3_file_inode_operations,
.file_ops = &nfs_file_operations,
.getroot = nfs3_proc_get_root,
- .mount = nfs_general_mount,
.submount = nfs_submount,
- .try_mount = nfs_try_mount,
+ .try_get_tree = nfs_try_get_tree,
.getattr = nfs3_proc_getattr,
.setattr = nfs3_proc_setattr,
.lookup = nfs3_proc_lookup,
diff --git a/fs/nfs/nfs4_fs.h b/fs/nfs/nfs4_fs.h
index 05769b633b76..effcea28d7cc 100644
--- a/fs/nfs/nfs4_fs.h
+++ b/fs/nfs/nfs4_fs.h
@@ -476,8 +476,8 @@ extern bool recover_lost_locks;
#define NFS4_CLIENT_ID_UNIQ_LEN (64)
extern char nfs4_client_id_uniquifier[NFS4_CLIENT_ID_UNIQ_LEN];
-extern struct dentry *nfs4_try_mount(struct nfs_sb_config *);
-extern struct dentry *nfs4_mount(struct nfs_sb_config *);
+extern int nfs4_try_get_tree(struct nfs_sb_config *);
+extern int nfs4_get_tree(struct nfs_sb_config *);
/* nfs4sysctl.c */
#ifdef CONFIG_SYSCTL
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 32d8c10bc45e..da1fb302ee61 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -9318,9 +9318,9 @@ const struct nfs_rpc_ops nfs_v4_clientops = {
.file_inode_ops = &nfs4_file_inode_operations,
.file_ops = &nfs4_file_operations,
.getroot = nfs4_proc_get_root,
- .mount = nfs4_mount,
+ .get_tree = nfs4_get_tree,
.submount = nfs4_submount,
- .try_mount = nfs4_try_mount,
+ .try_get_tree = nfs4_try_get_tree,
.getattr = nfs4_proc_getattr,
.setattr = nfs4_proc_setattr,
.lookup = nfs4_proc_lookup,
diff --git a/fs/nfs/nfs4super.c b/fs/nfs/nfs4super.c
index 7bc27a28d5da..853e20a25333 100644
--- a/fs/nfs/nfs4super.c
+++ b/fs/nfs/nfs4super.c
@@ -18,9 +18,6 @@
static int nfs4_write_inode(struct inode *inode, struct writeback_control *wbc);
static void nfs4_evict_inode(struct inode *inode);
-static struct dentry *nfs4_remote_mount(struct nfs_sb_config *cfg);
-static struct dentry *nfs4_referral_mount(struct nfs_sb_config *cfg);
-static struct dentry *nfs4_remote_referral_mount(struct nfs_sb_config *cfg);
static const struct super_operations nfs4_sops = {
.alloc_inode = nfs_alloc_inode,
@@ -77,7 +74,7 @@ static void nfs4_evict_inode(struct inode *inode)
/*
* Get the superblock for the NFS4 root partition
*/
-static struct dentry *nfs4_remote_mount(struct nfs_sb_config *cfg)
+static int nfs4_get_remote_tree(struct nfs_sb_config *cfg)
{
struct nfs_server *server;
@@ -86,9 +83,9 @@ static struct dentry *nfs4_remote_mount(struct nfs_sb_config *cfg)
/* Get a volume representation */
server = nfs4_create_server(cfg);
if (IS_ERR(server))
- return ERR_CAST(server);
+ return PTR_ERR(server);
- return nfs_fs_mount_common(server, cfg);
+ return nfs_get_tree_common(server, cfg);
}
/*
@@ -104,6 +101,7 @@ static struct vfsmount *nfs_do_root_mount(struct nfs_sb_config *cfg,
struct vfsmount *root_mnt;
char *root_devname;
size_t len;
+ int ret;
root_sc = vfs_dup_sb_config(&cfg->sc);
if (IS_ERR(root_sc))
@@ -126,6 +124,10 @@ static struct vfsmount *nfs_do_root_mount(struct nfs_sb_config *cfg,
snprintf(root_devname, len, "%s:/", hostname);
root_cfg->sc.device = root_devname;
+ ret = vfs_get_tree(&root_cfg->sc);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
root_mnt = vfs_kern_mount_sc(&root_cfg->sc);
out_sc:
put_sb_config(root_sc);
@@ -219,12 +221,12 @@ static struct dentry *nfs_follow_remote_path(struct vfsmount *root_mnt,
return dentry;
}
-struct dentry *nfs4_try_mount(struct nfs_sb_config *cfg)
+int nfs4_try_get_tree(struct nfs_sb_config *cfg)
{
struct vfsmount *root_mnt;
- struct dentry *res;
+ struct dentry *root;
- dfprintk(MOUNT, "--> nfs4_try_mount()\n");
+ dfprintk(MOUNT, "--> nfs4_try_get_tree()\n");
/* We create a mount for the server's root, walk to the requested
* location and then create another mount for that.
@@ -232,74 +234,83 @@ struct dentry *nfs4_try_mount(struct nfs_sb_config *cfg)
root_mnt = nfs_do_root_mount(cfg, cfg->nfs_server.hostname,
NFS4_MOUNT_REMOTE);
if (IS_ERR(root_mnt))
- return ERR_CAST(root_mnt);
+ return PTR_ERR(root_mnt);
- res = nfs_follow_remote_path(root_mnt, cfg->nfs_server.export_path);
- if (IS_ERR(res))
+ root = nfs_follow_remote_path(root_mnt, cfg->nfs_server.export_path);
+ if (IS_ERR(root)) {
nfs_cfg_error(cfg, "NFS4: Couldn't follow remote path");
+ dfprintk(MOUNT, "<-- nfs4_try_mount() = %ld [error]\n",
+ PTR_ERR(root));
+ return PTR_ERR(root);
+ }
- dfprintk(MOUNT, "<-- nfs4_try_mount() = %d%s\n",
- PTR_ERR_OR_ZERO(res),
- IS_ERR(res) ? " [error]" : "");
- return res;
+ cfg->sc.root = root;
+ dfprintk(MOUNT, "<-- nfs4_try_get_tree() = 0\n");
+ return 0;
}
-static struct dentry *nfs4_remote_referral_mount(struct nfs_sb_config *cfg)
+static int nfs4_get_remote_referral_tree(struct nfs_sb_config *cfg)
{
struct nfs_server *server;
- dprintk("--> nfs4_referral_get_sb()\n");
+ dprintk("--> nfs4_get_remote_referral_tree()\n");
cfg->set_security = nfs_clone_sb_security;
if (!cfg->clone_data.cloned)
- return ERR_PTR(-EINVAL);
+ return -EINVAL;
/* create a new volume representation */
server = nfs4_create_referral_server(cfg);
if (IS_ERR(server))
- return ERR_CAST(server);
+ return PTR_ERR(server);
- return nfs_fs_mount_common(server, cfg);
+ return nfs_get_tree_common(server, cfg);
}
/*
* Create an NFS4 server record on referral traversal
*/
-static struct dentry *nfs4_referral_mount(struct nfs_sb_config *cfg)
+static int nfs4_get_referral_tree(struct nfs_sb_config *cfg)
{
struct vfsmount *root_mnt;
- struct dentry *res;
+ struct dentry *root;
dprintk("--> nfs4_referral_mount()\n");
root_mnt = nfs_do_root_mount(cfg, cfg->nfs_server.hostname,
NFS4_MOUNT_REMOTE_REFERRAL);
- res = nfs_follow_remote_path(root_mnt, cfg->nfs_server.export_path);
- dprintk("<-- nfs4_referral_mount() = %d%s\n",
- PTR_ERR_OR_ZERO(res),
- IS_ERR(res) ? " [error]" : "");
- return res;
+ root = nfs_follow_remote_path(root_mnt, cfg->nfs_server.export_path);
+ if (IS_ERR(root)) {
+ nfs_cfg_error(cfg, "NFS4: Couldn't follow remote path");
+ dfprintk(MOUNT, "<-- nfs4_referral_mount() = %ld [error]\n",
+ PTR_ERR(root));
+ return PTR_ERR(root);
+ }
+
+ cfg->sc.root = root;
+ dfprintk(MOUNT, "<-- nfs4_get_referral_tree() = 0\n");
+ return 0;
}
/*
* Handle special NFS4 mount types.
*/
-struct dentry *nfs4_mount(struct nfs_sb_config *cfg)
+int nfs4_get_tree(struct nfs_sb_config *cfg)
{
switch (cfg->mount_type) {
case NFS4_MOUNT_REMOTE:
- return nfs4_remote_mount(cfg);
+ return nfs4_get_remote_tree(cfg);
case NFS4_MOUNT_REFERRAL:
- return nfs4_referral_mount(cfg);
+ return nfs4_get_referral_tree(cfg);
case NFS4_MOUNT_REMOTE_REFERRAL:
- return nfs4_remote_referral_mount(cfg);
+ return nfs4_get_remote_referral_tree(cfg);
default:
- return nfs_general_mount(cfg);
+ return 1;
}
}
diff --git a/fs/nfs/proc.c b/fs/nfs/proc.c
index edae9cd50412..9f1cae2a101c 100644
--- a/fs/nfs/proc.c
+++ b/fs/nfs/proc.c
@@ -704,9 +704,8 @@ const struct nfs_rpc_ops nfs_v2_clientops = {
.file_inode_ops = &nfs_file_inode_operations,
.file_ops = &nfs_file_operations,
.getroot = nfs_proc_get_root,
- .mount = nfs_general_mount,
.submount = nfs_submount,
- .try_mount = nfs_try_mount,
+ .try_get_tree = nfs_try_get_tree,
.getattr = nfs_proc_getattr,
.setattr = nfs_proc_setattr,
.lookup = nfs_proc_lookup,
diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index 8551bfb4e7da..32918f67d1fe 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -855,7 +855,7 @@ static struct nfs_server *nfs_try_mount_request(struct nfs_sb_config *cfg)
return cfg->nfs_mod->rpc_ops->create_server(cfg);
}
-struct dentry *nfs_try_mount(struct nfs_sb_config *cfg)
+int nfs_try_get_tree(struct nfs_sb_config *cfg)
{
struct nfs_server *server;
@@ -866,12 +866,12 @@ struct dentry *nfs_try_mount(struct nfs_sb_config *cfg)
if (IS_ERR(server)) {
nfs_cfg_error(cfg, "NFS: Couldn't create server");
- return ERR_CAST(server);
+ return PTR_ERR(server);
}
- return nfs_fs_mount_common(server, cfg);
+ return nfs_get_tree_common(server, cfg);
}
-EXPORT_SYMBOL_GPL(nfs_try_mount);
+EXPORT_SYMBOL_GPL(nfs_try_get_tree);
#define NFS_REMOUNT_CMP_FLAGMASK ~(NFS_MOUNT_INTR \
@@ -1179,40 +1179,38 @@ static void nfs_get_cache_cookie(struct super_block *sb,
}
#endif
-int nfs_set_sb_security(struct super_block *s, struct dentry *mntroot,
- struct nfs_sb_config *cfg)
+int nfs_set_sb_security(struct super_block *sb, struct nfs_sb_config *cfg)
{
int error;
unsigned long kflags = 0, kflags_out = 0;
- if (NFS_SB(s)->caps & NFS_CAP_SECURITY_LABEL)
+ if (NFS_SB(sb)->caps & NFS_CAP_SECURITY_LABEL)
kflags |= SECURITY_LSM_NATIVE_LABELS;
- error = security_sb_set_mnt_opts(s, cfg->sc.security,
+ error = security_sb_set_mnt_opts(sb, cfg->sc.security,
kflags, &kflags_out);
if (error)
goto err;
- if (NFS_SB(s)->caps & NFS_CAP_SECURITY_LABEL &&
- !(kflags_out & SECURITY_LSM_NATIVE_LABELS))
- NFS_SB(s)->caps &= ~NFS_CAP_SECURITY_LABEL;
+ if (NFS_SB(sb)->caps & NFS_CAP_SECURITY_LABEL &&
+ !(kflags_out & SECURITY_LSM_NATIVE_LABELS))
+ NFS_SB(sb)->caps &= ~NFS_CAP_SECURITY_LABEL;
err:
return error;
}
EXPORT_SYMBOL_GPL(nfs_set_sb_security);
-int nfs_clone_sb_security(struct super_block *s, struct dentry *mntroot,
- struct nfs_sb_config *cfg)
+int nfs_clone_sb_security(struct super_block *sb, struct nfs_sb_config *cfg)
{
/* clone any lsm security options from the parent to the new sb */
- if (d_inode(mntroot)->i_op != NFS_SB(s)->nfs_client->rpc_ops->dir_inode_ops)
+ if (d_inode(cfg->sc.root)->i_op !=
+ NFS_SB(sb)->nfs_client->rpc_ops->dir_inode_ops)
return -ESTALE;
- return security_sb_clone_mnt_opts(cfg->clone_data.sb, s);
+ return security_sb_clone_mnt_opts(cfg->clone_data.sb, sb);
}
EXPORT_SYMBOL_GPL(nfs_clone_sb_security);
-struct dentry *nfs_fs_mount_common(struct nfs_server *server,
- struct nfs_sb_config *cfg)
+int nfs_get_tree_common(struct nfs_server *server, struct nfs_sb_config *cfg)
{
struct super_block *s;
struct dentry *mntroot = ERR_PTR(-ENOMEM);
@@ -1238,7 +1236,7 @@ struct dentry *nfs_fs_mount_common(struct nfs_server *server,
s = sget(cfg->nfs_mod->nfs_fs, compare_super, nfs_set_super, cfg->sc.ms_flags,
&sb_mntdata);
if (IS_ERR(s)) {
- mntroot = ERR_CAST(s);
+ error = PTR_ERR(s);
nfs_cfg_error(cfg, "NFS: Couldn't get superblock");
goto out_err_nosb;
}
@@ -1256,20 +1254,21 @@ struct dentry *nfs_fs_mount_common(struct nfs_server *server,
nfs_get_cache_cookie(s, cfg);
}
- mntroot = nfs_get_root(s, cfg->mntfh, cfg->sc.device);
- if (IS_ERR(mntroot)) {
+ error = nfs_get_root(s, cfg);
+ if (error < 0) {
nfs_cfg_error(cfg, "NFS: Couldn't get root dentry");
goto error_splat_super;
}
- error = cfg->set_security(s, mntroot, cfg);
+ error = cfg->set_security(s, cfg);
if (error)
goto error_splat_root;
s->s_flags |= MS_ACTIVE;
+ error = 0;
out:
- return mntroot;
+ return error;
out_err_nosb:
nfs_free_server(server);
@@ -1277,12 +1276,11 @@ struct dentry *nfs_fs_mount_common(struct nfs_server *server,
error_splat_root:
dput(mntroot);
- mntroot = ERR_PTR(error);
error_splat_super:
deactivate_locked_super(s);
goto out;
}
-EXPORT_SYMBOL_GPL(nfs_fs_mount_common);
+EXPORT_SYMBOL_GPL(nfs_get_tree_common);
/*
* Destroy an NFS2/3 superblock
diff --git a/fs/proc/root.c b/fs/proc/root.c
index da5757d1c518..8781415be2e0 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -148,7 +148,7 @@ int proc_remount(struct super_block *sb, struct sb_config *sc)
return 0;
}
-static struct dentry *proc_mount(struct sb_config *sc)
+static int proc_get_tree(struct sb_config *sc)
{
struct proc_sb_config *cfg = container_of(sc, struct proc_sb_config, sc);
@@ -166,7 +166,7 @@ static void proc_sb_config_free(struct sb_config *sc)
static const struct sb_config_operations proc_sb_config_ops = {
.free = proc_sb_config_free,
.parse_option = proc_parse_mount_option,
- .mount = proc_mount,
+ .get_tree = proc_get_tree,
};
static int proc_init_sb_config(struct sb_config *sc, struct super_block *src_sb)
@@ -298,6 +298,7 @@ int pid_ns_prepare_proc(struct pid_namespace *ns)
struct proc_sb_config *cfg;
struct sb_config *sc;
struct vfsmount *mnt;
+ int ret;
sc = __vfs_new_sb_config(&proc_fs_type, NULL, 0, SB_CONFIG_FOR_NEW);
if (IS_ERR(sc))
@@ -310,6 +311,12 @@ int pid_ns_prepare_proc(struct pid_namespace *ns)
cfg->pid_ns = ns;
}
+ ret = vfs_get_tree(sc);
+ if (ret < 0) {
+ put_sb_config(sc);
+ return ret;
+ }
+
mnt = kern_mount_data_sc(sc);
put_sb_config(sc);
if (IS_ERR(mnt))
diff --git a/fs/sb_config.c b/fs/sb_config.c
index 62e309cd62ef..6b6853b6d769 100644
--- a/fs/sb_config.c
+++ b/fs/sb_config.c
@@ -127,9 +127,6 @@ int vfs_parse_mount_option(struct sb_config *sc, char *p)
{
int ret;
- if (sc->mounted)
- return -EBUSY;
-
ret = vfs_parse_ms_mount_option(sc, p);
if (ret < 0)
return ret;
@@ -324,19 +321,44 @@ struct sb_config *vfs_dup_sb_config(struct sb_config *src_sc)
EXPORT_SYMBOL(vfs_dup_sb_config);
/**
+ * vfs_reconfigure_super - Reconfigure a superblock.
+ * @sc: The configuration updates to apply
+ */
+int vfs_reconfigure_super(struct sb_config *sc)
+{
+ pr_notice("*** vfs_reconfigure_super()\n");
+
+ if (!sc->root)
+ return -EINVAL;
+ return -ENOANO; // TODO
+}
+EXPORT_SYMBOL(vfs_reconfigure_super);
+
+/**
* put_sb_config - Dispose of a superblock configuration context.
* @sc: The context to dispose of.
*/
void put_sb_config(struct sb_config *sc)
{
+ struct super_block *sb;
+
+ if (sc->root) {
+ sb = sc->root->d_sb;
+ dput(sc->root);
+ sc->root = NULL;
+ deactivate_super(sb);
+ }
+
if (sc->ops && sc->ops->free)
sc->ops->free(sc);
+
security_sb_config_free(sc);
if (sc->net_ns)
put_net(sc->net_ns);
put_user_ns(sc->user_ns);
if (sc->cred)
put_cred(sc->cred);
+ kfree(sc->subtype);
put_filesystem(sc->fs_type);
kfree(sc->device);
kfree(sc);
@@ -438,9 +460,30 @@ static int legacy_validate(struct sb_config *sc)
}
/*
- * Perform a legacy mount.
+ * Determine the superblock subtype.
*/
-static struct dentry *legacy_mount(struct sb_config *sc)
+static int legacy_set_subtype(struct sb_config *sc)
+{
+ const char *subtype = strchr(sc->fs_type->name, '.');
+
+ if (subtype) {
+ subtype++;
+ if (!subtype[0])
+ return -EINVAL;
+ } else {
+ subtype = "";
+ }
+
+ sc->subtype = kstrdup(subtype, GFP_KERNEL);
+ if (!sc->subtype)
+ return -ENOMEM;
+ return 0;
+}
+
+/*
+ * Get a mountable root with the legacy mount command.
+ */
+static int legacy_get_tree(struct sb_config *sc)
{
struct legacy_sb_config *cfg = container_of(sc, struct legacy_sb_config, sc);
struct super_block *sb;
@@ -448,22 +491,27 @@ static struct dentry *legacy_mount(struct sb_config *sc)
int ret;
root = cfg->sc.fs_type->mount(cfg->sc.fs_type, cfg->sc.ms_flags,
- cfg->sc.device, cfg->legacy_data);
+ cfg->sc.device, cfg->legacy_data);
if (IS_ERR(root))
- return ERR_CAST(root);
+ return PTR_ERR(root);
sb = root->d_sb;
BUG_ON(!sb);
- ret = security_sb_kern_mount(sb, cfg->sc.ms_flags, cfg->secdata);
- if (ret < 0)
- goto err_sb;
- return root;
+ if ((cfg->sc.fs_type->fs_flags & FS_HAS_SUBTYPE) &&
+ !sc->subtype) {
+ ret = legacy_set_subtype(sc);
+ if (ret < 0)
+ goto err_sb;
+ }
+
+ cfg->sc.root = root;
+ return 0;
err_sb:
dput(root);
deactivate_locked_super(sb);
- return ERR_PTR(ret);
+ return ret;
}
static const struct sb_config_operations legacy_sb_config_ops = {
@@ -472,5 +520,5 @@ static const struct sb_config_operations legacy_sb_config_ops = {
.parse_option = legacy_parse_option,
.monolithic_mount_data = legacy_monolithic_mount_data,
.validate = legacy_validate,
- .mount = legacy_mount,
+ .get_tree = legacy_get_tree,
};
diff --git a/fs/super.c b/fs/super.c
index 4d923a775bd0..7ca217552977 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -1058,10 +1058,9 @@ struct dentry *mount_ns(struct file_system_type *fs_type,
EXPORT_SYMBOL(mount_ns);
-struct dentry *mount_ns_sc(struct sb_config *sc,
- int (*fill_super)(struct super_block *sb,
- struct sb_config *sc),
- void *ns)
+int mount_ns_sc(struct sb_config *sc,
+ int (*fill_super)(struct super_block *sb, struct sb_config *sc),
+ void *ns)
{
struct super_block *sb;
@@ -1070,25 +1069,29 @@ struct dentry *mount_ns_sc(struct sb_config *sc,
*/
if (!(sc->ms_flags & MS_KERNMOUNT) &&
!ns_capable(sc->user_ns, CAP_SYS_ADMIN))
- return ERR_PTR(-EPERM);
+ return -EPERM;
sb = sget_userns(sc->fs_type, ns_test_super, ns_set_super,
sc->ms_flags, sc->user_ns, ns);
if (IS_ERR(sb))
- return ERR_CAST(sb);
+ return PTR_ERR(sb);
if (!sb->s_root) {
int err;
err = fill_super(sb, sc);
if (err) {
deactivate_locked_super(sb);
- return ERR_PTR(err);
+ return err;
}
sb->s_flags |= MS_ACTIVE;
}
- return dget(sb->s_root);
+ if (!sc->root) {
+ sc->root = sb->s_root;
+ dget(sb->s_root);
+ }
+ return 0;
}
EXPORT_SYMBOL(mount_ns_sc);
@@ -1246,59 +1249,6 @@ struct dentry *mount_single(struct file_system_type *fs_type,
}
EXPORT_SYMBOL(mount_single);
-struct dentry *
-mount_fs(struct file_system_type *type, int flags, const char *name, void *data)
-{
- struct dentry *root;
- struct super_block *sb;
- char *secdata = NULL;
- int error = -ENOMEM;
-
- if (data && !(type->fs_flags & FS_BINARY_MOUNTDATA)) {
- secdata = alloc_secdata();
- if (!secdata)
- goto out;
-
- error = security_sb_copy_data(data, secdata);
- if (error)
- goto out_free_secdata;
- }
-
- root = type->mount(type, flags, name, data);
- if (IS_ERR(root)) {
- error = PTR_ERR(root);
- goto out_free_secdata;
- }
- sb = root->d_sb;
- BUG_ON(!sb);
- WARN_ON(!sb->s_bdi);
- sb->s_flags |= MS_BORN;
-
- error = security_sb_kern_mount(sb, flags, secdata);
- if (error)
- goto out_sb;
-
- /*
- * filesystems should never set s_maxbytes larger than MAX_LFS_FILESIZE
- * but s_maxbytes was an unsigned long long for many releases. Throw
- * this warning for a little while to try and catch filesystems that
- * violate this rule.
- */
- WARN((sb->s_maxbytes < 0), "%s set sb->s_maxbytes to "
- "negative value (%lld)\n", type->name, sb->s_maxbytes);
-
- up_write(&sb->s_umount);
- free_secdata(secdata);
- return root;
-out_sb:
- dput(root);
- deactivate_locked_super(sb);
-out_free_secdata:
- free_secdata(secdata);
-out:
- return ERR_PTR(error);
-}
-
/*
* Setup private BDI for given superblock. It gets automatically cleaned up
* in generic_shutdown_super().
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 3ebfedd1bfb8..2b96e7ae539f 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2048,10 +2048,10 @@ struct file_system_type {
#define MODULE_ALIAS_FS(NAME) MODULE_ALIAS("fs-" NAME)
-extern struct dentry *mount_ns_sc(struct sb_config *mc,
- int (*fill_super)(struct super_block *sb,
- struct sb_config *sc),
- void *ns);
+extern int mount_ns_sc(struct sb_config *mc,
+ int (*fill_super)(struct super_block *sb,
+ struct sb_config *sc),
+ void *ns);
extern struct dentry *mount_ns(struct file_system_type *fs_type,
int flags, void *data, void *ns, struct user_namespace *user_ns,
int (*fill_super)(struct super_block *, void *, int));
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index e8e473ce5ddd..006c4b486169 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -97,10 +97,11 @@
* filesystem.
* @sc indicates the superblock configuration context.
* @p indicates the option in "key[=val]" form.
- * @sb_config_kern_mount:
- * Equivalent of sb_kern_mount, but with a superblock configuration context.
+ * @sb_get_tree:
+ * Assign the security to a newly created superblock.
* @sc indicates the superblock configuration context.
- * @src_sb indicates the new superblock.
+ * @sc->root indicates the root that will be mounted.
+ * @sc->root->d_sb points to the superblock.
* @sb_config_mountpoint:
* Equivalent of sb_mount, but with an sb_config.
* @sc indicates the superblock configuration context.
@@ -1393,14 +1394,13 @@ union security_list_options {
int (*sb_config_dup)(struct sb_config *sc, struct sb_config *src_sc);
void (*sb_config_free)(struct sb_config *sc);
int (*sb_config_parse_option)(struct sb_config *sc, char *opt);
- int (*sb_config_kern_mount)(struct sb_config *sc, struct super_block *sb);
+ int (*sb_get_tree)(struct sb_config *sc);
int (*sb_config_mountpoint)(struct sb_config *sc, struct path *mountpoint);
int (*sb_alloc_security)(struct super_block *sb);
void (*sb_free_security)(struct super_block *sb);
int (*sb_copy_data)(char *orig, char *copy);
int (*sb_remount)(struct super_block *sb, void *data);
- int (*sb_kern_mount)(struct super_block *sb, int flags, void *data);
int (*sb_show_options)(struct seq_file *m, struct super_block *sb);
int (*sb_statfs)(struct dentry *dentry);
int (*sb_mount)(const char *dev_name, const struct path *path,
@@ -1708,13 +1708,12 @@ struct security_hook_heads {
struct list_head sb_config_dup;
struct list_head sb_config_free;
struct list_head sb_config_parse_option;
- struct list_head sb_config_kern_mount;
+ struct list_head sb_get_tree;
struct list_head sb_config_mountpoint;
struct list_head sb_alloc_security;
struct list_head sb_free_security;
struct list_head sb_copy_data;
struct list_head sb_remount;
- struct list_head sb_kern_mount;
struct list_head sb_show_options;
struct list_head sb_statfs;
struct list_head sb_mount;
diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
index 9377afd5ecc8..733f62542644 100644
--- a/include/linux/nfs_xdr.h
+++ b/include/linux/nfs_xdr.h
@@ -1554,10 +1554,10 @@ struct nfs_rpc_ops {
int (*getroot) (struct nfs_server *, struct nfs_fh *,
struct nfs_fsinfo *);
- struct dentry *(*mount)(struct nfs_sb_config *);
+ int (*get_tree)(struct nfs_sb_config *);
struct vfsmount *(*submount) (struct nfs_server *, struct dentry *,
struct nfs_fh *, struct nfs_fattr *);
- struct dentry *(*try_mount) (struct nfs_sb_config *);
+ int (*try_get_tree) (struct nfs_sb_config *);
int (*getattr) (struct nfs_server *, struct nfs_fh *,
struct nfs_fattr *, struct nfs4_label *);
int (*setattr) (struct dentry *, struct nfs_fattr *,
diff --git a/include/linux/sb_config.h b/include/linux/sb_config.h
index 0b21e381d9f0..5d84175386d3 100644
--- a/include/linux/sb_config.h
+++ b/include/linux/sb_config.h
@@ -38,21 +38,26 @@ enum sb_config_purpose {
* allocated is specified in struct file_system_type::sb_config_size and this
* must include sufficient space for the sb_config struct.
*
+ * Superblock creation fills in ->root whereas reconfiguration begins with this
+ * already set.
+ *
* See Documentation/filesystems/mounting.txt
*/
struct sb_config {
const struct sb_config_operations *ops;
struct file_system_type *fs_type;
+ struct dentry *root; /* The root and superblock */
struct user_namespace *user_ns; /* The user namespace for this mount */
struct net *net_ns; /* The network namespace for this mount */
const struct cred *cred; /* The mounter's credentials */
char *device; /* The device name or mount target */
+ char *subtype; /* The subtype to set on the superblock */
void *security; /* The LSM context */
const char *error_msg; /* Error string to be read by read() */
unsigned int ms_flags; /* The superblock flags (MS_*) */
- bool mounted; /* Set when mounted */
bool sloppy; /* Unrecognised options are okay */
bool silent;
+ bool degraded; /* True if the config can't be reused */
enum sb_config_purpose purpose : 8;
};
@@ -62,7 +67,7 @@ struct sb_config_operations {
int (*parse_option)(struct sb_config *sc, char *p);
int (*monolithic_mount_data)(struct sb_config *sc, void *data);
int (*validate)(struct sb_config *sc);
- struct dentry *(*mount)(struct sb_config *sc);
+ int (*get_tree)(struct sb_config *sc);
};
extern const struct file_operations fs_fs_fops;
@@ -77,6 +82,8 @@ extern struct sb_config *vfs_sb_reconfig(struct vfsmount *mnt,
extern struct sb_config *vfs_dup_sb_config(struct sb_config *src);
extern int vfs_parse_mount_option(struct sb_config *sc, char *data);
extern int generic_monolithic_mount_data(struct sb_config *sc, void *data);
+extern int vfs_get_tree(struct sb_config *sc);
+extern int vfs_reconfigure_super(struct sb_config *sc);
extern void put_sb_config(struct sb_config *sc);
static inline void sb_cfg_error(struct sb_config *sc, const char *msg)
diff --git a/include/linux/security.h b/include/linux/security.h
index f95dc555cf29..f6baa8f01068 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -225,13 +225,12 @@ int security_sb_config_alloc(struct sb_config *sc, struct super_block *sb);
int security_sb_config_dup(struct sb_config *sc, struct sb_config *src_sc);
void security_sb_config_free(struct sb_config *sc);
int security_sb_config_parse_option(struct sb_config *sc, char *opt);
-int security_sb_config_kern_mount(struct sb_config *sc, struct super_block *sb);
+int security_sb_get_tree(struct sb_config *sc);
int security_sb_config_mountpoint(struct sb_config *sc, struct path *mountpoint);
int security_sb_alloc(struct super_block *sb);
void security_sb_free(struct super_block *sb);
int security_sb_copy_data(char *orig, char *copy);
int security_sb_remount(struct super_block *sb, void *data);
-int security_sb_kern_mount(struct super_block *sb, int flags, void *data);
int security_sb_show_options(struct seq_file *m, struct super_block *sb);
int security_sb_statfs(struct dentry *dentry);
int security_sb_mount(const char *dev_name, const struct path *path,
@@ -537,8 +536,7 @@ static inline int security_sb_config_parse_option(struct sb_config *sc, char *op
{
return 0;
}
-static inline int security_sb_config_kern_mount(struct sb_config *sc,
- struct super_block *sb)
+static inline int security_sb_get_tree(struct sb_config *sc)
{
return 0;
}
@@ -566,11 +564,6 @@ static inline int security_sb_remount(struct super_block *sb, void *data)
return 0;
}
-static inline int security_sb_kern_mount(struct super_block *sb, int flags, void *data)
-{
- return 0;
-}
-
static inline int security_sb_show_options(struct seq_file *m,
struct super_block *sb)
{
diff --git a/security/security.c b/security/security.c
index 8b2b7e6b3112..a0af89fd12af 100644
--- a/security/security.c
+++ b/security/security.c
@@ -329,9 +329,9 @@ int security_sb_config_parse_option(struct sb_config *sc, char *opt)
return call_int_hook(sb_config_parse_option, 0, sc, opt);
}
-int security_sb_config_kern_mount(struct sb_config *sc, struct super_block *sb)
+int security_sb_get_tree(struct sb_config *sc)
{
- return call_int_hook(sb_config_kern_mount, 0, sc, sb);
+ return call_int_hook(sb_get_tree, 0, sc);
}
int security_sb_config_mountpoint(struct sb_config *sc, struct path *mountpoint)
@@ -360,11 +360,6 @@ int security_sb_remount(struct super_block *sb, void *data)
return call_int_hook(sb_remount, 0, sb, data);
}
-int security_sb_kern_mount(struct super_block *sb, int flags, void *data)
-{
- return call_int_hook(sb_kern_mount, 0, sb, flags, data);
-}
-
int security_sb_show_options(struct seq_file *m, struct super_block *sb)
{
return call_int_hook(sb_show_options, 0, m, sb);
@@ -1694,8 +1689,7 @@ struct security_hook_heads security_hook_heads = {
.sb_config_free = LIST_HEAD_INIT(security_hook_heads.sb_config_free),
.sb_config_parse_option =
LIST_HEAD_INIT(security_hook_heads.sb_config_parse_option),
- .sb_config_kern_mount =
- LIST_HEAD_INIT(security_hook_heads.sb_config_kern_mount),
+ .sb_get_tree = LIST_HEAD_INIT(security_hook_heads.sb_get_tree),
.sb_config_mountpoint =
LIST_HEAD_INIT(security_hook_heads.sb_config_mountpoint),
.sb_alloc_security =
@@ -1704,8 +1698,6 @@ struct security_hook_heads security_hook_heads = {
LIST_HEAD_INIT(security_hook_heads.sb_free_security),
.sb_copy_data = LIST_HEAD_INIT(security_hook_heads.sb_copy_data),
.sb_remount = LIST_HEAD_INIT(security_hook_heads.sb_remount),
- .sb_kern_mount =
- LIST_HEAD_INIT(security_hook_heads.sb_kern_mount),
.sb_show_options =
LIST_HEAD_INIT(security_hook_heads.sb_show_options),
.sb_statfs = LIST_HEAD_INIT(security_hook_heads.sb_statfs),
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index e1bf18af72f5..e5218172a44d 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2775,25 +2775,6 @@ static int selinux_sb_remount(struct super_block *sb, void *data)
goto out_free_opts;
}
-static int selinux_sb_kern_mount(struct super_block *sb, int flags, void *data)
-{
- const struct cred *cred = current_cred();
- struct common_audit_data ad;
- int rc;
-
- rc = superblock_doinit(sb, data);
- if (rc)
- return rc;
-
- /* Allow all mounts performed by the kernel */
- if (flags & MS_KERNMOUNT)
- return 0;
-
- ad.type = LSM_AUDIT_DATA_DENTRY;
- ad.u.dentry = sb->s_root;
- return superblock_has_perm(cred, sb, FILESYSTEM__MOUNT, &ad);
-}
-
static int selinux_sb_statfs(struct dentry *dentry)
{
const struct cred *cred = current_cred();
@@ -2967,14 +2948,13 @@ static int selinux_sb_config_parse_option(struct sb_config *sc, char *opt)
return sb_cfg_inval(sc, "SELinux: Incompatible mount options");
}
-static int selinux_sb_config_kern_mount(struct sb_config *sc,
- struct super_block *sb)
+static int selinux_sb_get_tree(struct sb_config *sc)
{
const struct cred *cred = current_cred();
struct common_audit_data ad;
int rc;
- rc = selinux_set_mnt_opts(sb, sc->security, 0, NULL);
+ rc = selinux_set_mnt_opts(sc->root->d_sb, sc->security, 0, NULL);
if (rc)
return rc;
@@ -2983,8 +2963,8 @@ static int selinux_sb_config_kern_mount(struct sb_config *sc,
return 0;
ad.type = LSM_AUDIT_DATA_DENTRY;
- ad.u.dentry = sb->s_root;
- rc = superblock_has_perm(cred, sb, FILESYSTEM__MOUNT, &ad);
+ ad.u.dentry = sc->root;
+ rc = superblock_has_perm(cred, sc->root->d_sb, FILESYSTEM__MOUNT, &ad);
if (rc < 0)
sb_cfg_error(sc, "SELinux: Mount of superblock not permitted");
return rc;
@@ -6311,14 +6291,13 @@ static struct security_hook_list selinux_hooks[] = {
LSM_HOOK_INIT(sb_config_dup, selinux_sb_config_dup),
LSM_HOOK_INIT(sb_config_free, selinux_sb_config_free),
LSM_HOOK_INIT(sb_config_parse_option, selinux_sb_config_parse_option),
- LSM_HOOK_INIT(sb_config_kern_mount, selinux_sb_config_kern_mount),
+ LSM_HOOK_INIT(sb_get_tree, selinux_sb_get_tree),
LSM_HOOK_INIT(sb_config_mountpoint, selinux_sb_config_mountpoint),
LSM_HOOK_INIT(sb_alloc_security, selinux_sb_alloc_security),
LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security),
LSM_HOOK_INIT(sb_copy_data, selinux_sb_copy_data),
LSM_HOOK_INIT(sb_remount, selinux_sb_remount),
- LSM_HOOK_INIT(sb_kern_mount, selinux_sb_kern_mount),
LSM_HOOK_INIT(sb_show_options, selinux_sb_show_options),
LSM_HOOK_INIT(sb_statfs, selinux_sb_statfs),
LSM_HOOK_INIT(sb_mount, selinux_mount),
Move proc_fill_super() to fs/proc/root.c as that's where the other
superblock stuff is.
Signed-off-by: David Howells <[email protected]>
---
fs/proc/inode.c | 48 +-----------------------------------------------
fs/proc/internal.h | 4 +---
fs/proc/root.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 49 insertions(+), 51 deletions(-)
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 2cc7a8030275..194fa2d13b7e 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -22,7 +22,6 @@
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/mount.h>
-#include <linux/magic.h>
#include <linux/uaccess.h>
@@ -113,7 +112,7 @@ static int proc_show_options(struct seq_file *seq, struct dentry *root)
return 0;
}
-static const struct super_operations proc_sops = {
+const struct super_operations proc_sops = {
.alloc_inode = proc_alloc_inode,
.destroy_inode = proc_destroy_inode,
.drop_inode = generic_delete_inode,
@@ -470,48 +469,3 @@ struct inode *proc_get_inode(struct super_block *sb, struct proc_dir_entry *de)
pde_put(de);
return inode;
}
-
-int proc_fill_super(struct super_block *s, void *data, int silent)
-{
- struct pid_namespace *ns = get_pid_ns(s->s_fs_info);
- struct inode *root_inode;
- int ret;
-
- if (!proc_parse_options(data, ns))
- return -EINVAL;
-
- /* User space would break if executables or devices appear on proc */
- s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
- s->s_flags |= MS_NODIRATIME | MS_NOSUID | MS_NOEXEC;
- s->s_blocksize = 1024;
- s->s_blocksize_bits = 10;
- s->s_magic = PROC_SUPER_MAGIC;
- s->s_op = &proc_sops;
- s->s_time_gran = 1;
-
- /*
- * procfs isn't actually a stacking filesystem; however, there is
- * too much magic going on inside it to permit stacking things on
- * top of it
- */
- s->s_stack_depth = FILESYSTEM_MAX_STACK_DEPTH;
-
- pde_get(&proc_root);
- root_inode = proc_get_inode(s, &proc_root);
- if (!root_inode) {
- pr_err("proc_fill_super: get root inode failed\n");
- return -ENOMEM;
- }
-
- s->s_root = d_make_root(root_inode);
- if (!s->s_root) {
- pr_err("proc_fill_super: allocate dentry failed\n");
- return -ENOMEM;
- }
-
- ret = proc_setup_self(s);
- if (ret) {
- return ret;
- }
- return proc_setup_thread_self(s);
-}
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index c5ae09b6c726..b681533f59dd 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -197,13 +197,12 @@ struct pde_opener {
struct completion *c;
};
extern const struct inode_operations proc_link_inode_operations;
-
extern const struct inode_operations proc_pid_link_inode_operations;
+extern const struct super_operations proc_sops;
extern void proc_init_inodecache(void);
void set_proc_pid_nlink(void);
extern struct inode *proc_get_inode(struct super_block *, struct proc_dir_entry *);
-extern int proc_fill_super(struct super_block *, void *data, int flags);
extern void proc_entry_rundown(struct proc_dir_entry *);
/*
@@ -261,7 +260,6 @@ static inline void proc_tty_init(void) {}
* root.c
*/
extern struct proc_dir_entry proc_root;
-extern int proc_parse_options(char *options, struct pid_namespace *pid);
extern void proc_self_init(void);
extern int proc_remount(struct super_block *, int *, char *);
diff --git a/fs/proc/root.c b/fs/proc/root.c
index 3c47399bd095..ee1937b37370 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -23,6 +23,7 @@
#include <linux/pid_namespace.h>
#include <linux/parser.h>
#include <linux/cred.h>
+#include <linux/magic.h>
#include "internal.h"
@@ -36,7 +37,7 @@ static const match_table_t tokens = {
{Opt_err, NULL},
};
-int proc_parse_options(char *options, struct pid_namespace *pid)
+static int proc_parse_options(char *options, struct pid_namespace *pid)
{
char *p;
substring_t args[MAX_OPT_ARGS];
@@ -78,6 +79,51 @@ int proc_parse_options(char *options, struct pid_namespace *pid)
return 1;
}
+static int proc_fill_super(struct super_block *s, void *data, int silent)
+{
+ struct pid_namespace *ns = get_pid_ns(s->s_fs_info);
+ struct inode *root_inode;
+ int ret;
+
+ if (!proc_parse_options(data, ns))
+ return -EINVAL;
+
+ /* User space would break if executables or devices appear on proc */
+ s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
+ s->s_flags |= MS_NODIRATIME | MS_NOSUID | MS_NOEXEC;
+ s->s_blocksize = 1024;
+ s->s_blocksize_bits = 10;
+ s->s_magic = PROC_SUPER_MAGIC;
+ s->s_op = &proc_sops;
+ s->s_time_gran = 1;
+
+ /*
+ * procfs isn't actually a stacking filesystem; however, there is
+ * too much magic going on inside it to permit stacking things on
+ * top of it
+ */
+ s->s_stack_depth = FILESYSTEM_MAX_STACK_DEPTH;
+
+ pde_get(&proc_root);
+ root_inode = proc_get_inode(s, &proc_root);
+ if (!root_inode) {
+ pr_err("proc_fill_super: get root inode failed\n");
+ return -ENOMEM;
+ }
+
+ s->s_root = d_make_root(root_inode);
+ if (!s->s_root) {
+ pr_err("proc_fill_super: allocate dentry failed\n");
+ return -ENOMEM;
+ }
+
+ ret = proc_setup_self(s);
+ if (ret) {
+ return ret;
+ }
+ return proc_setup_thread_self(s);
+}
+
int proc_remount(struct super_block *sb, int *flags, char *data)
{
struct pid_namespace *pid = sb->s_fs_info;
Provide a function, kmemdup_nul(), that will create a NUL-terminated string
from an unterminated character array where the length is known in advance.
This is better than kstrndup() in situations where we already know the
string length as the strnlen() in kstrndup() is superfluous.
Signed-off-by: David Howells <[email protected]>
---
include/linux/string.h | 1 +
mm/util.c | 24 ++++++++++++++++++++++++
2 files changed, 25 insertions(+)
diff --git a/include/linux/string.h b/include/linux/string.h
index 26b6f6a66f83..0c88c0a1a72b 100644
--- a/include/linux/string.h
+++ b/include/linux/string.h
@@ -123,6 +123,7 @@ extern char *kstrdup(const char *s, gfp_t gfp) __malloc;
extern const char *kstrdup_const(const char *s, gfp_t gfp);
extern char *kstrndup(const char *s, size_t len, gfp_t gfp);
extern void *kmemdup(const void *src, size_t len, gfp_t gfp);
+extern char *kmemdup_nul(const char *s, size_t len, gfp_t gfp);
extern char **argv_split(gfp_t gfp, const char *str, int *argcp);
extern void argv_free(char **argv);
diff --git a/mm/util.c b/mm/util.c
index 656dc5e37a87..be68664bfb73 100644
--- a/mm/util.c
+++ b/mm/util.c
@@ -83,6 +83,8 @@ EXPORT_SYMBOL(kstrdup_const);
* @s: the string to duplicate
* @max: read at most @max chars from @s
* @gfp: the GFP mask used in the kmalloc() call when allocating memory
+ *
+ * Note: Use kmemdup_nul() instead if the size is known exactly.
*/
char *kstrndup(const char *s, size_t max, gfp_t gfp)
{
@@ -121,6 +123,28 @@ void *kmemdup(const void *src, size_t len, gfp_t gfp)
EXPORT_SYMBOL(kmemdup);
/**
+ * kmemdup_nul - Create a NUL-terminated string from unterminated data
+ * @s: The data to stringify
+ * @len: The size of the data
+ * @gfp: the GFP mask used in the kmalloc() call when allocating memory
+ */
+char *kmemdup_nul(const char *s, size_t len, gfp_t gfp)
+{
+ char *buf;
+
+ if (!s)
+ return NULL;
+
+ buf = kmalloc_track_caller(len + 1, gfp);
+ if (buf) {
+ memcpy(buf, s, len);
+ buf[len] = '\0';
+ }
+ return buf;
+}
+EXPORT_SYMBOL(kmemdup_nul);
+
+/**
* memdup_user - duplicate memory region from user space
*
* @src: source address in user space
Clean up line terminal whitespace in fs/namespace.c.
Signed-off-by: David Howells <[email protected]>
---
fs/namespace.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/fs/namespace.c b/fs/namespace.c
index cc1375eff88c..db034b6afd43 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -1659,7 +1659,7 @@ void __detach_mounts(struct dentry *dentry)
namespace_unlock();
}
-/*
+/*
* Is the caller allowed to modify his namespace?
*/
static inline bool may_mount(void)
@@ -2213,7 +2213,7 @@ static int do_loopback(struct path *path, const char *old_name,
err = -EINVAL;
if (mnt_ns_loop(old_path.dentry))
- goto out;
+ goto out;
mp = lock_mount(path);
err = PTR_ERR(mp);
Make get_mnt_ns() return the namespace it got a ref on for consistency with
other namespace ref getting functions.
Signed-off-by: David Howells <[email protected]>
---
fs/mount.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/fs/mount.h b/fs/mount.h
index 2826543a131d..b1e99b38f2ee 100644
--- a/fs/mount.h
+++ b/fs/mount.h
@@ -108,9 +108,10 @@ static inline void detach_mounts(struct dentry *dentry)
__detach_mounts(dentry);
}
-static inline void get_mnt_ns(struct mnt_namespace *ns)
+static inline struct mnt_namespace *get_mnt_ns(struct mnt_namespace *ns)
{
atomic_inc(&ns->count);
+ return ns;
}
extern seqlock_t mount_lock;
Make get_filesystem() return a pointer to the filesystem on which it just
got a ref.
Suggested-by: Rasmus Villemoes <[email protected]>
Signed-off-by: David Howells <[email protected]>
---
fs/filesystems.c | 3 ++-
include/linux/fs.h | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/fs/filesystems.c b/fs/filesystems.c
index cac75547d35c..591e52d23ed4 100644
--- a/fs/filesystems.c
+++ b/fs/filesystems.c
@@ -33,9 +33,10 @@ static struct file_system_type *file_systems;
static DEFINE_RWLOCK(file_systems_lock);
/* WARNING: This can be used only if we _already_ own a reference */
-void get_filesystem(struct file_system_type *fs)
+struct file_system_type *get_filesystem(struct file_system_type *fs)
{
__module_get(fs->owner);
+ return fs;
}
void put_filesystem(struct file_system_type *fs)
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 30e5c14bd743..45ac992133fc 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2953,7 +2953,7 @@ extern int generic_block_fiemap(struct inode *inode,
struct fiemap_extent_info *fieinfo, u64 start,
u64 len, get_block_t *get_block);
-extern void get_filesystem(struct file_system_type *fs);
+extern struct file_system_type *get_filesystem(struct file_system_type *fs);
extern void put_filesystem(struct file_system_type *fs);
extern struct file_system_type *get_fs_type(const char *name);
extern struct super_block *get_super(struct block_device *);
Provide an empty name (ie. "") qstr for general use.
Signed-off-by: David Howells <[email protected]>
---
fs/dcache.c | 8 ++++++--
fs/gfs2/dir.c | 3 +--
fs/namei.c | 3 +--
fs/nsfs.c | 3 +--
fs/pipe.c | 3 +--
include/linux/dcache.h | 5 +++++
6 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/fs/dcache.c b/fs/dcache.c
index 95d71eda8142..9a17ee020996 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -90,6 +90,11 @@ EXPORT_SYMBOL(rename_lock);
static struct kmem_cache *dentry_cache __read_mostly;
+const struct qstr empty_name = QSTR_INIT("", 0);
+EXPORT_SYMBOL(empty_name);
+const struct qstr slash_name = QSTR_INIT("/", 1);
+EXPORT_SYMBOL(slash_name);
+
/*
* This is the single most critical data structure when it comes
* to the dcache: the hashtable for lookups. Somebody should try
@@ -1580,8 +1585,7 @@ struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
*/
dentry->d_iname[DNAME_INLINE_LEN-1] = 0;
if (unlikely(!name)) {
- static const struct qstr anon = QSTR_INIT("/", 1);
- name = &anon;
+ name = &slash_name;
dname = dentry->d_iname;
} else if (name->len > DNAME_INLINE_LEN-1) {
size_t size = offsetof(struct external_name, name[1]);
diff --git a/fs/gfs2/dir.c b/fs/gfs2/dir.c
index 79113219be5f..a5dfff6a033e 100644
--- a/fs/gfs2/dir.c
+++ b/fs/gfs2/dir.c
@@ -872,7 +872,6 @@ static struct gfs2_leaf *new_leaf(struct inode *inode, struct buffer_head **pbh,
struct buffer_head *bh;
struct gfs2_leaf *leaf;
struct gfs2_dirent *dent;
- struct qstr name = { .name = "" };
struct timespec tv = current_time(inode);
error = gfs2_alloc_blocks(ip, &bn, &n, 0, NULL);
@@ -896,7 +895,7 @@ static struct gfs2_leaf *new_leaf(struct inode *inode, struct buffer_head **pbh,
leaf->lf_sec = cpu_to_be64(tv.tv_sec);
memset(leaf->lf_reserved2, 0, sizeof(leaf->lf_reserved2));
dent = (struct gfs2_dirent *)(leaf+1);
- gfs2_qstr2dirent(&name, bh->b_size - sizeof(struct gfs2_leaf), dent);
+ gfs2_qstr2dirent(&empty_name, bh->b_size - sizeof(struct gfs2_leaf), dent);
*pbh = bh;
return leaf;
}
diff --git a/fs/namei.c b/fs/namei.c
index 19dcf62133cc..0c2af2c7794f 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -3368,7 +3368,6 @@ static int do_last(struct nameidata *nd,
struct dentry *vfs_tmpfile(struct dentry *dentry, umode_t mode, int open_flag)
{
- static const struct qstr name = QSTR_INIT("/", 1);
struct dentry *child = NULL;
struct inode *dir = dentry->d_inode;
struct inode *inode;
@@ -3382,7 +3381,7 @@ struct dentry *vfs_tmpfile(struct dentry *dentry, umode_t mode, int open_flag)
if (!dir->i_op->tmpfile)
goto out_err;
error = -ENOMEM;
- child = d_alloc(dentry, &name);
+ child = d_alloc(dentry, &slash_name);
if (unlikely(!child))
goto out_err;
error = dir->i_op->tmpfile(dir, child, mode);
diff --git a/fs/nsfs.c b/fs/nsfs.c
index 323f492e0822..253cf59b27a1 100644
--- a/fs/nsfs.c
+++ b/fs/nsfs.c
@@ -53,7 +53,6 @@ static void nsfs_evict(struct inode *inode)
static void *__ns_get_path(struct path *path, struct ns_common *ns)
{
struct vfsmount *mnt = nsfs_mnt;
- struct qstr qname = { .name = "", };
struct dentry *dentry;
struct inode *inode;
unsigned long d;
@@ -85,7 +84,7 @@ static void *__ns_get_path(struct path *path, struct ns_common *ns)
inode->i_fop = &ns_file_operations;
inode->i_private = ns;
- dentry = d_alloc_pseudo(mnt->mnt_sb, &qname);
+ dentry = d_alloc_pseudo(mnt->mnt_sb, &empty_name);
if (!dentry) {
iput(inode);
return ERR_PTR(-ENOMEM);
diff --git a/fs/pipe.c b/fs/pipe.c
index 73b84baf58f8..97e5be897753 100644
--- a/fs/pipe.c
+++ b/fs/pipe.c
@@ -739,13 +739,12 @@ int create_pipe_files(struct file **res, int flags)
struct inode *inode = get_pipe_inode();
struct file *f;
struct path path;
- static struct qstr name = { .name = "" };
if (!inode)
return -ENFILE;
err = -ENOMEM;
- path.dentry = d_alloc_pseudo(pipe_mnt->mnt_sb, &name);
+ path.dentry = d_alloc_pseudo(pipe_mnt->mnt_sb, &empty_name);
if (!path.dentry)
goto err_inode;
path.mnt = mntget(pipe_mnt);
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index d2e38dc6172c..3f65a4fa72ed 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -55,6 +55,11 @@ struct qstr {
#define QSTR_INIT(n,l) { { { .len = l } }, .name = n }
+extern const char empty_string[];
+extern const struct qstr empty_name;
+extern const char slash_string[];
+extern const struct qstr slash_name;
+
struct dentry_stat_t {
long nr_dentry;
long nr_unused;
Introduce a superblock configuration context concept to be used during
superblock creation for mount and superblock reconfiguration for remount.
This is allocated at the beginning of the mount procedure and into it is
placed:
(1) Filesystem type.
(2) Namespaces.
(3) Device name.
(4) Superblock flags (MS_*).
(5) Security details.
(6) Filesystem-specific data, as set by the mount options.
It also gives a place in which to hang an error message for later retrieval
(see the mount-by-fd syscall later in this series).
Rather than calling fs_type->mount(), an sb_config struct is created and
fs_type->init_sb_config() is called to set it up. fs_type->sb_config_size
says how much space should be allocated for the config context. The
sb_config struct is placed at the beginning and any extra space is for the
filesystem's use.
A set of operations have to be set by ->init_sb_config() to provide
freeing, duplication, option parsing, binary data parsing, validation,
mounting and superblock filling.
It should be noted that, whilst this patch adds a lot of lines of code,
there is quite a bit of duplication with existing code that can be
eliminated should all filesystems be converted over.
Signed-off-by: David Howells <[email protected]>
---
Documentation/filesystems/mounting.txt | 456 ++++++++++++++++++++++++++++++++
fs/Makefile | 3
fs/internal.h | 2
fs/libfs.c | 1
fs/namespace.c | 256 ++++++++++++++++--
fs/nfs/nfs4super.c | 1
fs/proc/root.c | 1
fs/sb_config.c | 326 +++++++++++++++++++++++
fs/super.c | 54 +++-
include/linux/fs.h | 14 +
include/linux/lsm_hooks.h | 38 +++
include/linux/mount.h | 4
include/linux/sb_config.h | 93 +++++++
include/linux/security.h | 29 ++
security/security.c | 32 ++
security/selinux/hooks.c | 170 ++++++++++++
16 files changed, 1449 insertions(+), 31 deletions(-)
create mode 100644 Documentation/filesystems/mounting.txt
create mode 100644 fs/sb_config.c
create mode 100644 include/linux/sb_config.h
diff --git a/Documentation/filesystems/mounting.txt b/Documentation/filesystems/mounting.txt
new file mode 100644
index 000000000000..03e9086f754d
--- /dev/null
+++ b/Documentation/filesystems/mounting.txt
@@ -0,0 +1,456 @@
+ ===================
+ FILESYSTEM MOUNTING
+ ===================
+
+CONTENTS
+
+ (1) Overview.
+
+ (2) The superblock configuration context.
+
+ (3) The superblock config operations.
+
+ (4) Superblock config security.
+
+ (5) VFS superblock config operations.
+
+
+========
+OVERVIEW
+========
+
+The creation of new mounts is now to be done in a multistep process:
+
+ (1) Create a superblock configuration context.
+
+ (2) Parse the options and attach them to the context. Options may be passed
+ individually from userspace.
+
+ (3) Validate and pre-process the context.
+
+ (4) Get or create a superblock and mountable root.
+
+ (5) Perform the mount.
+
+ (6) Return an error message attached to the context.
+
+ (7) Destroy the context.
+
+To support this, the file_system_type struct gains two new fields:
+
+ unsigned short sb_config_size;
+
+which indicates the total amount of space that should be allocated for context
+data (see the Superblock Configuration Context section), and:
+
+ int (*init_sb_config)(struct sb_config *sc, struct super_block *src_sb);
+
+which is invoked to set up the filesystem-specific parts of a superblock
+configuration context, including the additional space. The src_sb parameter is
+used to convey the superblock from which the filesystem may draw extra
+information (such as namespaces), for submount (SB_CONFIG_FOR_SUBMOUNT) or
+remount (SB_CONFIG_FOR_REMOUNT) purposes or it will be NULL.
+
+Note that security initialisation is done *after* the filesystem is called so
+that the namespaces may be adjusted first.
+
+And the super_operations struct gains one:
+
+ int (*remount_fs_sc) (struct super_block *, struct sb_config *);
+
+This shadows the ->remount_fs() operation and takes a prepared superblock
+configuration context instead of the mount flags and data page. It may modify
+the ms_flags in the context for the caller to pick up.
+
+[NOTE] remount_fs_sc is intended as a replacement for remount_fs.
+
+
+====================================
+THE SUPERBLOCK CONFIGURATION CONTEXT
+====================================
+
+The creation and reconfiguration of a superblock is governed by a superblock
+configuration context. This is represented by the sb_config structure:
+
+ struct sb_config {
+ const struct sb_config_operations *ops;
+ struct file_system_type *fs;
+ struct user_namespace *user_ns;
+ struct net *net_ns;
+ const struct cred *cred;
+ char *device;
+ void *security;
+ const char *error_msg;
+ unsigned int ms_flags;
+ bool mounted;
+ bool sloppy;
+ bool silent;
+ enum mount_type mount_type : 8;
+ };
+
+When the VFS creates this, it allocates ->sb_config_size bytes (as specified by
+the file_system_type object) to hold both the sb_config struct and any extra
+data required by the filesystem. The sb_config struct is placed at the
+beginning of this space. Any extra space beyond that is for use by the
+filesystem. The filesystem should wrap the struct in its own, e.g.:
+
+ struct nfs_sb_config {
+ struct sb_config sc;
+ ...
+ };
+
+placing the sb_config struct first. container_of() can then be used. The
+file_system_type would be initialised thus:
+
+ struct file_system_type nfs = {
+ ...
+ .sb_config_size = sizeof(struct nfs_sb_config),
+ .init_sb_config = nfs_init_sb_config,
+ ...
+ };
+
+The sb_config fields are as follows:
+
+ (*) const struct sb_config_operations *ops
+
+ These are operations that can be done on a superblock configuration
+ context (see below). This must be set by the ->init_sb_config()
+ file_system_type operation.
+
+ (*) struct file_system_type *fs
+
+ A pointer to the file_system_type of the filesystem that is being
+ constructed or reconfigured. This retains a ref on the type owner.
+
+ (*) struct user_namespace *user_ns
+ (*) struct net *net_ns
+
+ This is a subset of the namespaces in use by the invoking process. This
+ retains a ref on each namespace. The subscribed namespaces may be
+ replaced by the filesystem to reflect other sources, such as the parent
+ mount superblock on an automount.
+
+ (*) struct cred *cred
+
+ The mounter's credentials. This retains a ref on the credentials.
+
+ (*) char *device
+
+ This is the device to be mounted. It may be a block device
+ (e.g. /dev/sda1) or something more exotic, such as the "host:/path" that
+ NFS desires.
+
+ (*) void *security
+
+ A place for the LSMs to hang their security data for the superblock. The
+ relevant security operations are described below.
+
+ (*) const char *error_msg
+
+ A place for the VFS and the filesystem to hang an error message. This
+ should be in the form of a static string that doesn't need deallocation
+ and the pointer to which can just be overwritten. Under some
+ circumstances, this can be retrieved by userspace.
+
+ Note that the existence of the error string is expected to be guaranteed
+ by the reference on the file_system_type object held by ->fs or any
+ filesystem-specific reference held in the filesystem context until the
+ ->free() operation is called.
+
+ Use sb_cfg_error() and sb_cfg_inval() to set this rather than setting it
+ directly.
+
+ (*) unsigned int ms_flags
+
+ This holds the MS_* flags mount flags.
+
+ (*) bool mounted
+
+ This is set to true once a mount attempt is made. This causes an error to
+ be given on subsequent mount attempts with the same context and prevents
+ multiple mount attempts.
+
+ (*) bool sloppy
+ (*) bool silent
+
+ These are set if the sloppy or silent mount options are given.
+
+ [NOTE] sloppy is probably unnecessary when userspace passes over one
+ option at a time since the error can just be ignored if userspace deems it
+ to be unimportant.
+
+ [NOTE] silent is probably redundant with ms_flags & MS_SILENT.
+
+ (*) enum mount_type
+
+ This indicates the type of mount operation. The available values are:
+
+ SB_CONFIG_FOR_NEW -- New mount
+ SB_CONFIG_FOR_SUBMOUNT -- New automatic submount of extant mount
+ SB_CONFIG_FOR_REMOUNT -- Change an existing mount
+
+The mount context is created by calling __vfs_new_sb_config(),
+vfs_new_sb_config(), vfs_sb_reconfig() or vfs_dup_sb_config() and is destroyed
+with put_sb_config(). Note that the structure is not refcounted.
+
+VFS, security and filesystem mount options are set individually with
+vfs_parse_mount_option() or in bulk with generic_monolithic_mount_data().
+
+When mounting, the filesystem is allowed to take data from any of the pointers
+and attach it to the superblock (or whatever), provided it clears the pointer
+in the mount context.
+
+The filesystem is also allowed to allocate resources and pin them with the
+mount context. For instance, NFS might pin the appropriate protocol version
+module.
+
+
+================================
+THE SUPERBLOCK CONFIG OPERATIONS
+================================
+
+The superblock configuration context points to a table of operations:
+
+ struct sb_config_operations {
+ void (*free)(struct sb_config *sc);
+ int (*dup)(struct sb_config *sc, struct sb_config *src_sc);
+ int (*parse_option)(struct sb_config *sc, char *p);
+ int (*monolithic_mount_data)(struct sb_config *sc, void *data);
+ int (*validate)(struct sb_config *sc);
+ struct dentry *(*mount)(struct sb_config *sc);
+ };
+
+These operations are invoked by the various stages of the mount procedure to
+manage the superblock configuration context. They are as follows:
+
+ (*) void (*free)(struct sb_config *sc);
+
+ Called to clean up the filesystem-specific part of the superblock
+ configuration context when the context is destroyed. It should be aware
+ that parts of the context may have been removed and NULL'd out by
+ ->mount().
+
+ (*) int (*dup)(struct sb_config *sc, struct sb_config *src_sc);
+
+ Called when a superblock configuration context has been duplicated to get
+ any refs or copy any non-referenced resources held in the
+ filesystem-specific part of the superblock configuration context. An
+ error may be returned to indicate failure to do this.
+
+ [!] Note that even if this fails, put_sb_config() will be called
+ immediately thereafter, so ->dup() *must* make the filesystem-specific
+ part safe for ->free().
+
+ (*) int (*parse_option)(struct sb_config *sc, char *p);
+
+ Called when an option is to be added to the superblock configuration
+ context. p points to the option string, likely in "key[=val]" format.
+ VFS-specific options will have been weeded out and sc->ms_flags updated in
+ the context. Security options will also have been weeded out and
+ sc->security updated.
+
+ If successful, 0 should be returned and a negative error code otherwise.
+ If an ambiguous error (such as -EINVAL) is returned, sb_cfg_error() or
+ sb_cfg_inval() should be used to provide a string that provides more
+ information.
+
+ (*) int (*monolithic_mount_data)(struct sb_config *sc, void *data);
+
+ Called when the mount(2) system call is invoked to pass the entire data
+ page in one go. If this is expected to be just a list of "key[=val]"
+ items separated by commas, then this may be set to NULL.
+
+ The return value is as for ->parse_option().
+
+ If the filesystem (eg. NFS) needs to examine the data first and then
+ finds it's the standard key-val list then it may pass it off to:
+
+ int generic_monolithic_mount_data(struct sb_config *sc, void *data);
+
+ (*) int (*validate)(struct sb_config *sc);
+
+ Called when all the options have been applied and the mount is about to
+ take place. It is should check for inconsistencies from mount options
+ and it is also allowed to do preliminary resource acquisition. For
+ instance, the core NFS module could load the NFS protocol module here.
+
+ Note that if sc->mount_type == SB_CONFIG_FOR_REMOUNT, some of the options
+ necessary for a new mount may not be set.
+
+ The return value is as for ->parse_option().
+
+ (*) struct dentry *(*mount)(struct sb_config *sc);
+
+ Called to effect a new mount or new submount using the information stored
+ in the superblock configuration context (remounts go via a different
+ vector). It may detach any resources it desires from the superblock
+ configuration context and transfer them to the superblock it creates.
+
+ On success it should return the dentry that's at the root of the mount.
+ In future, sc->root_path will then be applied to this.
+
+ In the case of an error, it should return a negative error code and invoke
+ sb_cfg_inval() or sb_cfg_error().
+
+
+=========================================
+SUPERBLOCK CONFIGURATION CONTEXT SECURITY
+========================================
+
+The superblock configuration context contains a security points that the LSMs can use for
+building up a security context for the superblock to be mounted. There are a
+number of operations used by the new mount code for this purpose:
+
+ (*) int security_sb_config_alloc(struct sb_config *sc,
+ struct super_block *src_sb);
+
+ Called to initialise sc->security (which is preset to NULL) and allocate
+ any resources needed. It should return 0 on success and a negative error
+ code on failure.
+
+ src_sb is non-NULL in the case of a remount (SB_CONFIG_FOR_REMOUNT) in
+ which case it indicates the superblock to be remounted or in the case of a
+ submount (SB_CONFIG_FOR_SUBMOUNT) in which case it indicates the parent
+ superblock.
+
+ (*) int security_sb_config_dup(struct sb_config *sc,
+ struct sb_config *src_mc);
+
+ Called to initialise sc->security (which is preset to NULL) and allocate
+ any resources needed. The original superblock configuration context is pointed to by src_mc
+ and may be used for reference. It should return 0 on success and a
+ negative error code on failure.
+
+ (*) void security_sb_config_free(struct sb_config *sc);
+
+ Called to clean up anything attached to sc->security. Note that the
+ contents may have been transferred to a superblock and the pointer NULL'd
+ out during mount.
+
+ (*) int security_sb_config_parse_option(struct sb_config *sc, char *opt);
+
+ Called for each mount option. The mount options are in "key[=val]"
+ form. An active LSM may reject one with an error, pass one over and
+ return 0 or consume one and return 1. If consumed, the option isn't
+ passed on to the filesystem.
+
+ If it returns an error, more information can be returned with
+ sb_cfg_inval() or sb_cfg_error().
+
+ (*) int security_sb_get_tree(struct sb_config *sc);
+
+ Called during the mount procedure to verify that the specified superblock
+ is allowed to be mounted and to transfer the security data there.
+
+ On success, it should return 0; otherwise it should return an error and
+ perhaps call sb_cfg_inval() or sb_cfg_error() to indicate the problem. It
+ should not return -ENOMEM as this should be taken care of in advance.
+
+ [NOTE] Should I add a security_sb_config_validate() operation so that the
+ LSM has the opportunity to allocate stuff and check the options as a
+ whole?
+
+
+================================
+VFS SUPERBLOCK CONFIG OPERATIONS
+================================
+
+There are four operations for creating a superblock configuration context and
+one for destroying a context:
+
+ (*) struct sb_config *__vfs_new_sb_config(struct file_system_type *fs_type,
+ struct super_block *src_sb;
+ unsigned int ms_flags);
+
+ Create a superblock configuration context given a filesystem type pointer.
+ This allocates the superblock configuration context, sets the flags,
+ initialises the security and calls fs_type->init_sb_config() to initialise
+ the filesystem context.
+
+ src_sb can be NULL or it may indicate a superblock that is going to be
+ remounted (SB_CONFIG_FOR_REMOUNT) or a superblock that is the parent of a
+ submount (SB_CONFIG_FOR_SUBMOUNT). This superblock is provided as a
+ source of namespace information.
+
+ (*) struct sb_config *vfs_sb_reconfig(struct vfsmount *mnt,
+ unsigned int ms_flags);
+
+ Create a superblock configuration context from the same filesystem as an
+ extant mount and initialise the mount parameters from the superblock
+ underlying that mount. This is for use by remount.
+
+ (*) struct sb_config *vfs_fsopen(const char *fs_name);
+
+ Create a superblock configuration context given a filesystem name. It is
+ assumed that the mount flags will be passed in as text options or set
+ directly later. This is intended to be called from sys_mount() or
+ sys_fsopen(). This copies current's namespaces to the superblock
+ configuration context.
+
+ (*) struct sb_config *vfs_dup_sb_config(struct sb_config *src_sc);
+
+ Duplicate a superblock configuration context, copying any options noted
+ and duplicating or additionally referencing any resources held therein.
+ This is available for use where a filesystem has to get a mount within a
+ mount, such as NFS4 does by internally mounting the root of the target
+ server and then doing a private pathwalk to the target directory.
+
+ (*) void put_sb_config(struct sb_config *sc);
+
+ Destroy a superblock configuration context, releasing any resources it
+ holds. This calls the ->free() operation. This is intended to be called
+ by anyone who created a superblock configuration context.
+
+ [!] superblock configuration contexts are not refcounted, so this causes
+ unconditional destruction.
+
+In all the above operations, apart from the put op, the return is a mount
+context pointer or a negative error code. No error string is saved as the
+error string is only guaranteed as long as the file_system_type is pinned (and
+thus the module).
+
+The next operations can be used to cache an error message in the context for
+the caller to collect.
+
+ (*) void sb_cfg_error(struct sb_config *sc, const char *msg);
+
+ Set an error message for the caller to pick up. For lifetime rules, see
+ the ->error_msg member description.
+
+ (*) void sb_cfg_inval(struct sb_config *sc, const char *msg);
+
+ As sb_cfg_error(), but returns -EINVAL for use with tail calling.
+
+In the remaining operations, if an error occurs, a negative error code is
+returned and, if not obvious, sc->error_msg may have been set to point to a
+useful string. This string should not be freed.
+
+ (*) struct vfsmount *vfs_kern_mount_sc(struct sb_config *sc);
+
+ Create a mount given the parameters in the specified superblock
+ configuration context. This invokes the ->validate() op and then the
+ ->mount() op.
+
+ (*) struct vfsmount *vfs_submount_sc(const struct dentry *mountpoint,
+ struct sb_config *sc);
+
+ Create a mount given a superblock configuration context and set
+ MS_SUBMOUNT on it. A wrapper around vfs_kern_mount_sc(). This is
+ intended to be called from filesystems that have automount points (NFS,
+ AFS, ...).
+
+ (*) int vfs_parse_mount_option(struct sb_config *sc, char *data);
+
+ Supply a single mount option to the superblock configuration context. The
+ mount option should likely be in a "key[=val]" string form. The option is
+ first checked to see if it corresponds to a standard mount flag (in which
+ case it is used to mark an MS_xxx flag and consumed) or a security option
+ (in which case the LSM consumes it) before it is passed on to the
+ filesystem.
+
+ (*) int generic_monolithic_mount_data(struct sb_config *sc, void *data);
+
+ Parse a sys_mount() data page, assuming the form to be a text list
+ consisting of key[=val] options separated by commas. Each item in the
+ list is passed to vfs_mount_option(). This is the default when the
+ ->monolithic_mount_data() operation is NULL.
diff --git a/fs/Makefile b/fs/Makefile
index 7bbaca9c67b1..8f5142525866 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -11,7 +11,8 @@ obj-y := open.o read_write.o file_table.o super.o \
attr.o bad_inode.o file.o filesystems.o namespace.o \
seq_file.o xattr.o libfs.o fs-writeback.o \
pnode.o splice.o sync.o utimes.o \
- stack.o fs_struct.o statfs.o fs_pin.o nsfs.o
+ stack.o fs_struct.o statfs.o fs_pin.o nsfs.o \
+ sb_config.o
ifeq ($(CONFIG_BLOCK),y)
obj-y += buffer.o block_dev.o direct-io.o mpage.o
diff --git a/fs/internal.h b/fs/internal.h
index 076751d90ba2..6ac2191cb59a 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -87,7 +87,7 @@ extern struct file *get_empty_filp(void);
/*
* super.c
*/
-extern int do_remount_sb(struct super_block *, int, void *, int);
+extern int do_remount_sb(struct super_block *, int, void *, int, struct sb_config *);
extern bool trylock_super(struct super_block *sb);
extern struct dentry *mount_fs(struct file_system_type *,
int, const char *, void *);
diff --git a/fs/libfs.c b/fs/libfs.c
index a8b62e5d43a9..2733d070b1ef 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -9,6 +9,7 @@
#include <linux/slab.h>
#include <linux/cred.h>
#include <linux/mount.h>
+#include <linux/sb_config.h>
#include <linux/vfs.h>
#include <linux/quotaops.h>
#include <linux/mutex.h>
diff --git a/fs/namespace.c b/fs/namespace.c
index db034b6afd43..8ade7252ee34 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -25,7 +25,9 @@
#include <linux/magic.h>
#include <linux/bootmem.h>
#include <linux/task_work.h>
+#include <linux/file.h>
#include <linux/sched/task.h>
+#include <linux/sb_config.h>
#include "pnode.h"
#include "internal.h"
@@ -1596,7 +1598,7 @@ static int do_umount(struct mount *mnt, int flags)
return -EPERM;
down_write(&sb->s_umount);
if (!(sb->s_flags & MS_RDONLY))
- retval = do_remount_sb(sb, MS_RDONLY, NULL, 0);
+ retval = do_remount_sb(sb, MS_RDONLY, NULL, 0, NULL);
up_write(&sb->s_umount);
return retval;
}
@@ -2279,6 +2281,26 @@ static int change_mount_flags(struct vfsmount *mnt, int ms_flags)
}
/*
+ * Parse the monolithic page of mount data given to sys_mount().
+ */
+static int parse_monolithic_mount_data(struct sb_config *sc, void *data)
+{
+ int (*monolithic_mount_data)(struct sb_config *, void *);
+ int ret;
+
+ monolithic_mount_data = sc->ops->monolithic_mount_data;
+ if (!monolithic_mount_data)
+ monolithic_mount_data = generic_monolithic_mount_data;
+
+ ret = monolithic_mount_data(sc, data);
+ if (ret < 0)
+ return ret;
+ if (sc->ops->validate)
+ return sc->ops->validate(sc);
+ return 0;
+}
+
+/*
* change filesystem flags. dir should be a physical root of filesystem.
* If you've mounted a non-root directory somewhere and want to do remount
* on it - tough luck.
@@ -2286,13 +2308,14 @@ static int change_mount_flags(struct vfsmount *mnt, int ms_flags)
static int do_remount(struct path *path, int flags, int mnt_flags,
void *data)
{
+ struct sb_config *sc = NULL;
int err;
struct super_block *sb = path->mnt->mnt_sb;
struct mount *mnt = real_mount(path->mnt);
+ struct file_system_type *type = sb->s_type;
if (!check_mnt(mnt))
return -EINVAL;
-
if (path->dentry != path->mnt->mnt_root)
return -EINVAL;
@@ -2323,9 +2346,19 @@ static int do_remount(struct path *path, int flags, int mnt_flags,
return -EPERM;
}
- err = security_sb_remount(sb, data);
- if (err)
- return err;
+ if (type->init_sb_config) {
+ sc = vfs_sb_reconfig(path->mnt, flags);
+ if (IS_ERR(sc))
+ return PTR_ERR(sc);
+
+ err = parse_monolithic_mount_data(sc, data);
+ if (err < 0)
+ goto err_sc;
+ } else {
+ err = security_sb_remount(sb, data);
+ if (err)
+ return err;
+ }
down_write(&sb->s_umount);
if (flags & MS_BIND)
@@ -2333,7 +2366,7 @@ static int do_remount(struct path *path, int flags, int mnt_flags,
else if (!capable(CAP_SYS_ADMIN))
err = -EPERM;
else
- err = do_remount_sb(sb, flags, data, 0);
+ err = do_remount_sb(sb, flags, data, 0, sc);
if (!err) {
lock_mount_hash();
mnt_flags |= mnt->mnt.mnt_flags & ~MNT_USER_SETTABLE_MASK;
@@ -2342,6 +2375,9 @@ static int do_remount(struct path *path, int flags, int mnt_flags,
unlock_mount_hash();
}
up_write(&sb->s_umount);
+err_sc:
+ if (sc)
+ put_sb_config(sc);
return err;
}
@@ -2495,40 +2531,106 @@ static int do_add_mount(struct mount *newmnt, struct path *path, int mnt_flags)
static bool mount_too_revealing(struct vfsmount *mnt, int *new_mnt_flags);
/*
+ * Create a new mount using a superblock configuration and request it
+ * be added to the namespace tree.
+ */
+static int do_new_mount_sc(struct sb_config *sc, struct path *mountpoint,
+ unsigned int mnt_flags)
+{
+ struct vfsmount *mnt;
+ int ret;
+
+ mnt = vfs_kern_mount_sc(sc);
+ if (IS_ERR(mnt))
+ return PTR_ERR(mnt);
+
+ if ((sc->fs_type->fs_flags & FS_HAS_SUBTYPE) &&
+ !mnt->mnt_sb->s_subtype) {
+ mnt = fs_set_subtype(mnt, sc->fs_type->name);
+ if (IS_ERR(mnt))
+ return PTR_ERR(mnt);
+ }
+
+ ret = -EPERM;
+ if (mount_too_revealing(mnt, &mnt_flags)) {
+ sb_cfg_error(sc, "VFS: Mount too revealing");
+ goto err_mnt;
+ }
+
+ ret = do_add_mount(real_mount(mnt), mountpoint, mnt_flags);
+ if (ret < 0) {
+ sb_cfg_error(sc, "VFS: Failed to add mount");
+ goto err_mnt;
+ }
+ return ret;
+
+err_mnt:
+ mntput(mnt);
+ return ret;
+}
+
+/*
* create a new mount for userspace and request it to be added into the
* namespace's tree
*/
-static int do_new_mount(struct path *path, const char *fstype, int flags,
+static int do_new_mount(struct path *mountpoint, const char *fstype, int flags,
int mnt_flags, const char *name, void *data)
{
- struct file_system_type *type;
+ struct sb_config *sc;
struct vfsmount *mnt;
int err;
if (!fstype)
return -EINVAL;
- type = get_fs_type(fstype);
- if (!type)
- return -ENODEV;
+ sc = vfs_new_sb_config(fstype);
+ if (IS_ERR(sc))
+ return PTR_ERR(sc);
+ sc->ms_flags = flags;
- mnt = vfs_kern_mount(type, flags, name, data);
- if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) &&
- !mnt->mnt_sb->s_subtype)
- mnt = fs_set_subtype(mnt, fstype);
+ err = -ENOMEM;
+ sc->device = kstrdup(name, GFP_KERNEL);
+ if (!sc->device)
+ goto err_sc;
- put_filesystem(type);
- if (IS_ERR(mnt))
- return PTR_ERR(mnt);
+ if (sc->ops) {
+ err = parse_monolithic_mount_data(sc, data);
+ if (err < 0)
+ goto err_sc;
- if (mount_too_revealing(mnt, &mnt_flags)) {
- mntput(mnt);
- return -EPERM;
+ err = do_new_mount_sc(sc, mountpoint, mnt_flags);
+ if (err)
+ goto err_sc;
+
+ } else {
+ mnt = vfs_kern_mount(sc->fs_type, flags, name, data);
+ if (!IS_ERR(mnt) && (sc->fs_type->fs_flags & FS_HAS_SUBTYPE) &&
+ !mnt->mnt_sb->s_subtype)
+ mnt = fs_set_subtype(mnt, fstype);
+
+ if (IS_ERR(mnt)) {
+ err = PTR_ERR(mnt);
+ goto err_sc;
+ }
+
+ err = -EPERM;
+ if (mount_too_revealing(mnt, &mnt_flags))
+ goto err_mnt;
+
+ err = do_add_mount(real_mount(mnt), mountpoint, mnt_flags);
+ if (err)
+ goto err_mnt;
}
- err = do_add_mount(real_mount(mnt), path, mnt_flags);
- if (err)
- mntput(mnt);
+ put_sb_config(sc);
+ return 0;
+
+err_mnt:
+ mntput(mnt);
+err_sc:
+ if (sc->error_msg)
+ pr_info("Mount failed: %s\n", sc->error_msg);
+ put_sb_config(sc);
return err;
}
@@ -3061,6 +3163,95 @@ SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
return ret;
}
+static struct dentry *__do_mount_sc(struct sb_config *sc)
+{
+ struct super_block *sb;
+ struct dentry *root;
+ int ret;
+
+ root = sc->ops->mount(sc);
+ if (IS_ERR(root))
+ return root;
+
+ sb = root->d_sb;
+ BUG_ON(!sb);
+ WARN_ON(!sb->s_bdi);
+ sb->s_flags |= MS_BORN;
+
+ ret = security_sb_config_kern_mount(sc, sb);
+ if (ret < 0)
+ goto err_sb;
+
+ /*
+ * filesystems should never set s_maxbytes larger than MAX_LFS_FILESIZE
+ * but s_maxbytes was an unsigned long long for many releases. Throw
+ * this warning for a little while to try and catch filesystems that
+ * violate this rule.
+ */
+ WARN((sb->s_maxbytes < 0), "%s set sb->s_maxbytes to "
+ "negative value (%lld)\n", sc->fs_type->name, sb->s_maxbytes);
+
+ up_write(&sb->s_umount);
+ return root;
+
+err_sb:
+ dput(root);
+ deactivate_locked_super(sb);
+ return ERR_PTR(ret);
+}
+
+struct vfsmount *vfs_kern_mount_sc(struct sb_config *sc)
+{
+ struct dentry *root;
+ struct mount *mnt;
+ int ret;
+
+ if (sc->ops->validate) {
+ ret = sc->ops->validate(sc);
+ if (ret < 0)
+ return ERR_PTR(ret);
+ }
+
+ mnt = alloc_vfsmnt(sc->device ?: "none");
+ if (!mnt)
+ return ERR_PTR(-ENOMEM);
+
+ if (sc->ms_flags & MS_KERNMOUNT)
+ mnt->mnt.mnt_flags = MNT_INTERNAL;
+
+ root = __do_mount_sc(sc);
+ if (IS_ERR(root)) {
+ mnt_free_id(mnt);
+ free_vfsmnt(mnt);
+ return ERR_CAST(root);
+ }
+
+ mnt->mnt.mnt_root = root;
+ mnt->mnt.mnt_sb = root->d_sb;
+ mnt->mnt_mountpoint = mnt->mnt.mnt_root;
+ mnt->mnt_parent = mnt;
+ lock_mount_hash();
+ list_add_tail(&mnt->mnt_instance, &root->d_sb->s_mounts);
+ unlock_mount_hash();
+ return &mnt->mnt;
+}
+EXPORT_SYMBOL_GPL(vfs_kern_mount_sc);
+
+struct vfsmount *
+vfs_submount_sc(const struct dentry *mountpoint, struct sb_config *sc)
+{
+ /* Until it is worked out how to pass the user namespace
+ * through from the parent mount to the submount don't support
+ * unprivileged mounts with submounts.
+ */
+ if (mountpoint->d_sb->s_user_ns != &init_user_ns)
+ return ERR_PTR(-EPERM);
+
+ sc->ms_flags = MS_SUBMOUNT;
+ return vfs_kern_mount_sc(sc);
+}
+EXPORT_SYMBOL_GPL(vfs_submount_sc);
+
/*
* Return true if path is reachable from root
*
@@ -3302,6 +3493,23 @@ struct vfsmount *kern_mount_data(struct file_system_type *type, void *data)
}
EXPORT_SYMBOL_GPL(kern_mount_data);
+struct vfsmount *kern_mount_data_sc(struct sb_config *sc)
+{
+ struct vfsmount *mnt;
+
+ sc->ms_flags = MS_KERNMOUNT;
+ mnt = vfs_kern_mount_sc(sc);
+ if (!IS_ERR(mnt)) {
+ /*
+ * it is a longterm mount, don't release mnt until
+ * we unmount before file sys is unregistered
+ */
+ real_mount(mnt)->mnt_ns = MNT_NS_INTERNAL;
+ }
+ return mnt;
+}
+EXPORT_SYMBOL_GPL(kern_mount_data_sc);
+
void kern_unmount(struct vfsmount *mnt)
{
/* release long term mount so mount point can be released */
diff --git a/fs/nfs/nfs4super.c b/fs/nfs/nfs4super.c
index 6fb7cb6b3f4b..967fa04d5c76 100644
--- a/fs/nfs/nfs4super.c
+++ b/fs/nfs/nfs4super.c
@@ -3,6 +3,7 @@
*/
#include <linux/init.h>
#include <linux/module.h>
+#include <linux/mount.h>
#include <linux/nfs4_mount.h>
#include <linux/nfs_fs.h>
#include "delegation.h"
diff --git a/fs/proc/root.c b/fs/proc/root.c
index deecb397daa3..3c47399bd095 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -19,6 +19,7 @@
#include <linux/bitops.h>
#include <linux/user_namespace.h>
#include <linux/mount.h>
+#include <linux/sb_config.h>
#include <linux/pid_namespace.h>
#include <linux/parser.h>
#include <linux/cred.h>
diff --git a/fs/sb_config.c b/fs/sb_config.c
new file mode 100644
index 000000000000..9c45e269b3cc
--- /dev/null
+++ b/fs/sb_config.c
@@ -0,0 +1,326 @@
+/* Provide a way to create a superblock configuration context within the kernel
+ * that allows a superblock to be set up prior to mounting.
+ *
+ * Copyright (C) 2017 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/sb_config.h>
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/nsproxy.h>
+#include <linux/slab.h>
+#include <linux/magic.h>
+#include <linux/security.h>
+#include <linux/parser.h>
+#include <linux/mnt_namespace.h>
+#include <linux/pid_namespace.h>
+#include <linux/user_namespace.h>
+#include <net/net_namespace.h>
+#include "mount.h"
+
+static const match_table_t common_set_mount_options = {
+ { MS_DIRSYNC, "dirsync" },
+ { MS_I_VERSION, "iversion" },
+ { MS_LAZYTIME, "lazytime" },
+ { MS_MANDLOCK, "mand" },
+ { MS_NOATIME, "noatime" },
+ { MS_NODEV, "nodev" },
+ { MS_NODIRATIME, "nodiratime" },
+ { MS_NOEXEC, "noexec" },
+ { MS_NOSUID, "nosuid" },
+ { MS_POSIXACL, "posixacl" },
+ { MS_RDONLY, "ro" },
+ { MS_REC, "rec" },
+ { MS_RELATIME, "relatime" },
+ { MS_STRICTATIME, "strictatime" },
+ { MS_SYNCHRONOUS, "sync" },
+ { MS_VERBOSE, "verbose" },
+ { },
+};
+
+static const match_table_t common_clear_mount_options = {
+ { MS_LAZYTIME, "nolazytime" },
+ { MS_MANDLOCK, "nomand" },
+ { MS_NODEV, "dev" },
+ { MS_NOEXEC, "exec" },
+ { MS_NOSUID, "suid" },
+ { MS_RDONLY, "rw" },
+ { MS_RELATIME, "norelatime" },
+ { MS_SILENT, "silent" },
+ { MS_STRICTATIME, "nostrictatime" },
+ { MS_SYNCHRONOUS, "async" },
+ { },
+};
+
+static const match_table_t forbidden_mount_options = {
+ { MS_BIND, "bind" },
+ { MS_MOVE, "move" },
+ { MS_PRIVATE, "private" },
+ { MS_REMOUNT, "remount" },
+ { MS_SHARED, "shared" },
+ { MS_SLAVE, "slave" },
+ { MS_UNBINDABLE, "unbindable" },
+ { },
+};
+
+/*
+ * Check for a common mount option.
+ */
+static int vfs_parse_ms_mount_option(struct sb_config *sc, char *data)
+{
+ substring_t args[MAX_OPT_ARGS];
+ unsigned int token;
+
+ token = match_token(data, common_set_mount_options, args);
+ if (token) {
+ sc->ms_flags |= token;
+ return 1;
+ }
+
+ token = match_token(data, common_clear_mount_options, args);
+ if (token) {
+ sc->ms_flags &= ~token;
+ return 1;
+ }
+
+ token = match_token(data, forbidden_mount_options, args);
+ if (token)
+ return sb_cfg_inval(sc, "Mount option, not superblock option");
+
+ return 0;
+}
+
+/**
+ * vfs_parse_mount_option - Add a single mount option to a superblock config
+ * @mc: The superblock configuration to modify
+ * @p: The option to apply.
+ *
+ * A single mount option in string form is applied to the superblock
+ * configuration being set up. Certain standard options (for example "ro") are
+ * translated into flag bits without going to the filesystem. The active
+ * security module is allowed to observe and poach options. Any other options
+ * are passed over to the filesystem to parse.
+ *
+ * This may be called multiple times for a context.
+ *
+ * Returns 0 on success and a negative error code on failure. In the event of
+ * failure, sc->error may have been set to a non-allocated string that gives
+ * more information.
+ */
+int vfs_parse_mount_option(struct sb_config *sc, char *p)
+{
+ int ret;
+
+ if (sc->mounted)
+ return -EBUSY;
+
+ ret = vfs_parse_ms_mount_option(sc, p);
+ if (ret < 0)
+ return ret;
+ if (ret == 1)
+ return 0;
+
+ ret = security_sb_config_parse_option(sc, p);
+ if (ret < 0)
+ return ret;
+ if (ret == 1)
+ return 0;
+
+ return sc->ops->parse_option(sc, p);
+}
+EXPORT_SYMBOL(vfs_parse_mount_option);
+
+/**
+ * generic_monolithic_mount_data - Parse key[=val][,key[=val]]* mount data
+ * @mc: The superblock configuration to fill in.
+ * @data: The data to parse
+ *
+ * Parse a blob of data that's in key[=val][,key[=val]]* form. This can be
+ * called from the ->monolithic_mount_data() sb_config operation.
+ *
+ * Returns 0 on success or the error returned by the ->parse_option() sb_config
+ * operation on failure.
+ */
+int generic_monolithic_mount_data(struct sb_config *ctx, void *data)
+{
+ char *options = data, *p;
+ int ret;
+
+ if (!options)
+ return 0;
+
+ while ((p = strsep(&options, ",")) != NULL) {
+ if (*p) {
+ ret = vfs_parse_mount_option(ctx, p);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(generic_monolithic_mount_data);
+
+/**
+ * __vfs_new_sb_config - Create a superblock config.
+ * @fs_type: The filesystem type.
+ * @src_sb: A superblock from which this one derives (or NULL)
+ * @ms_flags: Superblock flags and op flags (such as MS_REMOUNT)
+ * @purpose: The purpose that this configuration shall be used for.
+ *
+ * Open a filesystem and create a mount context. The mount context is
+ * initialised with the supplied flags and, if a submount/automount from
+ * another superblock (@src_sb), may have parameters such as namespaces copied
+ * across from that superblock.
+ */
+struct sb_config *__vfs_new_sb_config(struct file_system_type *fs_type,
+ struct super_block *src_sb,
+ unsigned int ms_flags,
+ enum sb_config_purpose purpose)
+{
+ struct sb_config *sc;
+ int ret;
+
+ BUG_ON(fs_type->init_sb_config &&
+ fs_type->sb_config_size < sizeof(*sc));
+
+ sc = kzalloc(max_t(size_t, fs_type->sb_config_size, sizeof(*sc)),
+ GFP_KERNEL);
+ if (!sc)
+ return ERR_PTR(-ENOMEM);
+
+ sc->purpose = purpose;
+ sc->ms_flags = ms_flags;
+ sc->fs_type = get_filesystem(fs_type);
+ sc->net_ns = get_net(current->nsproxy->net_ns);
+ sc->user_ns = get_user_ns(current_user_ns());
+ sc->cred = get_current_cred();
+
+ /* TODO: Make all filesystems support this unconditionally */
+ if (sc->fs_type->init_sb_config) {
+ ret = sc->fs_type->init_sb_config(sc, src_sb);
+ if (ret < 0)
+ goto err_sc;
+ }
+
+ /* Do the security check last because ->fsopen may change the
+ * namespace subscriptions.
+ */
+ ret = security_sb_config_alloc(sc, src_sb);
+ if (ret < 0)
+ goto err_sc;
+
+ return sc;
+
+err_sc:
+ put_sb_config(sc);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(__vfs_new_sb_config);
+
+/**
+ * vfs_new_sb_config - Create a superblock config for a new mount.
+ * @fs_name: The name of the filesystem
+ *
+ * Open a filesystem and create a superblock config context for a new mount
+ * that will hold the mount options, device name, security details, etc.. Note
+ * that the caller should check the ->ops pointer in the returned context to
+ * determine whether the filesystem actually supports the superblock context
+ * itself.
+ */
+struct sb_config *vfs_new_sb_config(const char *fs_name)
+{
+ struct file_system_type *fs_type;
+ struct sb_config *sc;
+
+ fs_type = get_fs_type(fs_name);
+ if (!fs_type)
+ return ERR_PTR(-ENODEV);
+
+ sc = __vfs_new_sb_config(fs_type, NULL, 0, SB_CONFIG_FOR_NEW);
+ put_filesystem(fs_type);
+ return sc;
+}
+EXPORT_SYMBOL(vfs_new_sb_config);
+
+/**
+ * vfs_sb_reconfig - Create a superblock config for remount/reconfiguration
+ * @mnt: The mountpoint to open
+ * @ms_flags: Superblock flags and op flags (such as MS_REMOUNT)
+ *
+ * Open a mounted filesystem and create a mount context such that a remount can
+ * be effected.
+ */
+struct sb_config *vfs_sb_reconfig(struct vfsmount *mnt,
+ unsigned int ms_flags)
+{
+ return __vfs_new_sb_config(mnt->mnt_sb->s_type, mnt->mnt_sb,
+ ms_flags, SB_CONFIG_FOR_REMOUNT);
+}
+
+/**
+ * vfs_dup_sc_config: Duplicate a superblock configuration context.
+ * @src_sc: The context to copy.
+ */
+struct sb_config *vfs_dup_sb_config(struct sb_config *src_sc)
+{
+ struct sb_config *sc;
+ int ret;
+
+ if (!src_sc->ops->dup)
+ return ERR_PTR(-ENOTSUPP);
+
+ sc = kmemdup(src_sc, src_sc->fs_type->sb_config_size, GFP_KERNEL);
+ if (!sc)
+ return ERR_PTR(-ENOMEM);
+
+ sc->device = NULL;
+ sc->security = NULL;
+ sc->error_msg = NULL;
+ get_filesystem(sc->fs_type);
+ get_net(sc->net_ns);
+ get_user_ns(sc->user_ns);
+ get_cred(sc->cred);
+
+ /* Can't call put until we've called ->dup */
+ ret = sc->ops->dup(sc, src_sc);
+ if (ret < 0)
+ goto err_sc;
+
+ ret = security_sb_config_dup(sc, src_sc);
+ if (ret < 0)
+ goto err_sc;
+ return sc;
+
+err_sc:
+ put_sb_config(sc);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(vfs_dup_sb_config);
+
+/**
+ * put_sb_config - Dispose of a superblock configuration context.
+ * @sc: The context to dispose of.
+ */
+void put_sb_config(struct sb_config *sc)
+{
+ if (sc->ops && sc->ops->free)
+ sc->ops->free(sc);
+ security_sb_config_free(sc);
+ if (sc->net_ns)
+ put_net(sc->net_ns);
+ put_user_ns(sc->user_ns);
+ if (sc->cred)
+ put_cred(sc->cred);
+ put_filesystem(sc->fs_type);
+ kfree(sc->device);
+ kfree(sc);
+}
+EXPORT_SYMBOL(put_sb_config);
diff --git a/fs/super.c b/fs/super.c
index adb0c0de428c..4d923a775bd0 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -34,6 +34,7 @@
#include <linux/fsnotify.h>
#include <linux/lockdep.h>
#include <linux/user_namespace.h>
+#include <linux/sb_config.h>
#include "internal.h"
@@ -805,10 +806,13 @@ struct super_block *user_get_super(dev_t dev)
* @flags: numeric part of options
* @data: the rest of options
* @force: whether or not to force the change
+ * @sc: the superblock config for filesystems that support it
+ * (NULL if called from emergency or umount)
*
* Alters the mount options of a mounted file system.
*/
-int do_remount_sb(struct super_block *sb, int flags, void *data, int force)
+int do_remount_sb(struct super_block *sb, int flags, void *data, int force,
+ struct sb_config *sc)
{
int retval;
int remount_ro;
@@ -850,8 +854,14 @@ int do_remount_sb(struct super_block *sb, int flags, void *data, int force)
}
}
- if (sb->s_op->remount_fs) {
- retval = sb->s_op->remount_fs(sb, &flags, data);
+ if (sb->s_op->remount_fs_sc ||
+ sb->s_op->remount_fs) {
+ if (sb->s_op->remount_fs_sc) {
+ retval = sb->s_op->remount_fs_sc(sb, sc);
+ flags = sc->ms_flags;
+ } else {
+ retval = sb->s_op->remount_fs(sb, &flags, data);
+ }
if (retval) {
if (!force)
goto cancel_readonly;
@@ -898,7 +908,7 @@ static void do_emergency_remount(struct work_struct *work)
/*
* What lock protects sb->s_flags??
*/
- do_remount_sb(sb, MS_RDONLY, NULL, 1);
+ do_remount_sb(sb, MS_RDONLY, NULL, 1, NULL);
}
up_write(&sb->s_umount);
spin_lock(&sb_lock);
@@ -1048,6 +1058,40 @@ struct dentry *mount_ns(struct file_system_type *fs_type,
EXPORT_SYMBOL(mount_ns);
+struct dentry *mount_ns_sc(struct sb_config *sc,
+ int (*fill_super)(struct super_block *sb,
+ struct sb_config *sc),
+ void *ns)
+{
+ struct super_block *sb;
+
+ /* Don't allow mounting unless the caller has CAP_SYS_ADMIN
+ * over the namespace.
+ */
+ if (!(sc->ms_flags & MS_KERNMOUNT) &&
+ !ns_capable(sc->user_ns, CAP_SYS_ADMIN))
+ return ERR_PTR(-EPERM);
+
+ sb = sget_userns(sc->fs_type, ns_test_super, ns_set_super,
+ sc->ms_flags, sc->user_ns, ns);
+ if (IS_ERR(sb))
+ return ERR_CAST(sb);
+
+ if (!sb->s_root) {
+ int err;
+ err = fill_super(sb, sc);
+ if (err) {
+ deactivate_locked_super(sb);
+ return ERR_PTR(err);
+ }
+
+ sb->s_flags |= MS_ACTIVE;
+ }
+
+ return dget(sb->s_root);
+}
+EXPORT_SYMBOL(mount_ns_sc);
+
#ifdef CONFIG_BLOCK
static int set_bdev_super(struct super_block *s, void *data)
{
@@ -1196,7 +1240,7 @@ struct dentry *mount_single(struct file_system_type *fs_type,
}
s->s_flags |= MS_ACTIVE;
} else {
- do_remount_sb(s, flags, data, 0);
+ do_remount_sb(s, flags, data, 0, NULL);
}
return dget(s->s_root);
}
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 45ac992133fc..3ebfedd1bfb8 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -55,6 +55,7 @@ struct workqueue_struct;
struct iov_iter;
struct fscrypt_info;
struct fscrypt_operations;
+struct sb_config;
extern void __init inode_init(void);
extern void __init inode_init_early(void);
@@ -701,6 +702,11 @@ static inline void inode_unlock(struct inode *inode)
up_write(&inode->i_rwsem);
}
+static inline int inode_lock_killable(struct inode *inode)
+{
+ return down_write_killable(&inode->i_rwsem);
+}
+
static inline void inode_lock_shared(struct inode *inode)
{
down_read(&inode->i_rwsem);
@@ -1786,6 +1792,7 @@ struct super_operations {
int (*unfreeze_fs) (struct super_block *);
int (*statfs) (struct dentry *, struct kstatfs *);
int (*remount_fs) (struct super_block *, int *, char *);
+ int (*remount_fs_sc) (struct super_block *, struct sb_config *);
void (*umount_begin) (struct super_block *);
int (*show_options)(struct seq_file *, struct dentry *);
@@ -2020,8 +2027,10 @@ struct file_system_type {
#define FS_HAS_SUBTYPE 4
#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
#define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */
+ unsigned short sb_config_size; /* Size of superblock config context to allocate */
struct dentry *(*mount) (struct file_system_type *, int,
const char *, void *);
+ int (*init_sb_config)(struct sb_config *, struct super_block *);
void (*kill_sb) (struct super_block *);
struct module *owner;
struct file_system_type * next;
@@ -2039,6 +2048,10 @@ struct file_system_type {
#define MODULE_ALIAS_FS(NAME) MODULE_ALIAS("fs-" NAME)
+extern struct dentry *mount_ns_sc(struct sb_config *mc,
+ int (*fill_super)(struct super_block *sb,
+ struct sb_config *sc),
+ void *ns);
extern struct dentry *mount_ns(struct file_system_type *fs_type,
int flags, void *data, void *ns, struct user_namespace *user_ns,
int (*fill_super)(struct super_block *, void *, int));
@@ -2105,6 +2118,7 @@ extern int register_filesystem(struct file_system_type *);
extern int unregister_filesystem(struct file_system_type *);
extern struct vfsmount *kern_mount_data(struct file_system_type *, void *data);
#define kern_mount(type) kern_mount_data(type, NULL)
+extern struct vfsmount *kern_mount_data_sc(struct sb_config *);
extern void kern_unmount(struct vfsmount *mnt);
extern int may_umount_tree(struct vfsmount *);
extern int may_umount(struct vfsmount *);
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index e29d4c62a3c8..6238fccaf5a4 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -75,6 +75,33 @@
* should enable secure mode.
* @bprm contains the linux_binprm structure.
*
+ * Security hooks for mount using fd context.
+ *
+ * @sb_config_alloc:
+ * Allocate and attach a security structure to sc->security. This pointer
+ * is initialised to NULL by the caller.
+ * @sc indicates the new superblock configuration context.
+ * @src_sb indicates the source superblock of a submount.
+ * @sb_config_dup:
+ * Allocate and attach a security structure to sc->security. This pointer
+ * is initialised to NULL by the caller.
+ * @sc indicates the new superblock configuration context.
+ * @src_sc indicates the original superblock configuration context.
+ * @sb_config_free:
+ * Clean up a superblock configuration context.
+ * @sc indicates the superblock configuration context.
+ * @sb_config_parse_option:
+ * Userspace provided an option to configure a superblock. The LSM may
+ * reject it with an error and may use it for itself, in which case it
+ * should return 1; otherwise it should return 0 to pass it on to the
+ * filesystem.
+ * @sc indicates the superblock configuration context.
+ * @p indicates the option in "key[=val]" form.
+ * @sb_config_kern_mount:
+ * Equivalent of sb_kern_mount, but with a superblock configuration context.
+ * @sc indicates the superblock configuration context.
+ * @src_sb indicates the new superblock.
+ *
* Security hooks for filesystem operations.
*
* @sb_alloc_security:
@@ -1358,6 +1385,12 @@ union security_list_options {
void (*bprm_committing_creds)(struct linux_binprm *bprm);
void (*bprm_committed_creds)(struct linux_binprm *bprm);
+ int (*sb_config_alloc)(struct sb_config *sc, struct super_block *src_sb);
+ int (*sb_config_dup)(struct sb_config *sc, struct sb_config *src_sc);
+ void (*sb_config_free)(struct sb_config *sc);
+ int (*sb_config_parse_option)(struct sb_config *sc, char *opt);
+ int (*sb_config_kern_mount)(struct sb_config *sc, struct super_block *sb);
+
int (*sb_alloc_security)(struct super_block *sb);
void (*sb_free_security)(struct super_block *sb);
int (*sb_copy_data)(char *orig, char *copy);
@@ -1666,6 +1699,11 @@ struct security_hook_heads {
struct list_head bprm_secureexec;
struct list_head bprm_committing_creds;
struct list_head bprm_committed_creds;
+ struct list_head sb_config_alloc;
+ struct list_head sb_config_dup;
+ struct list_head sb_config_free;
+ struct list_head sb_config_parse_option;
+ struct list_head sb_config_kern_mount;
struct list_head sb_alloc_security;
struct list_head sb_free_security;
struct list_head sb_copy_data;
diff --git a/include/linux/mount.h b/include/linux/mount.h
index 8e0352af06b7..a5dca6abc4d5 100644
--- a/include/linux/mount.h
+++ b/include/linux/mount.h
@@ -20,6 +20,7 @@ struct super_block;
struct vfsmount;
struct dentry;
struct mnt_namespace;
+struct sb_config;
#define MNT_NOSUID 0x01
#define MNT_NODEV 0x02
@@ -90,9 +91,12 @@ struct file_system_type;
extern struct vfsmount *vfs_kern_mount(struct file_system_type *type,
int flags, const char *name,
void *data);
+extern struct vfsmount *vfs_kern_mount_sc(struct sb_config *sc);
extern struct vfsmount *vfs_submount(const struct dentry *mountpoint,
struct file_system_type *type,
const char *name, void *data);
+extern struct vfsmount *vfs_submount_sc(const struct dentry *mountpoint,
+ struct sb_config *sc);
extern void mnt_set_expiry(struct vfsmount *mnt, struct list_head *expiry_list);
extern void mark_mounts_for_expiry(struct list_head *mounts);
diff --git a/include/linux/sb_config.h b/include/linux/sb_config.h
new file mode 100644
index 000000000000..0b21e381d9f0
--- /dev/null
+++ b/include/linux/sb_config.h
@@ -0,0 +1,93 @@
+/* Superblock configuration and creation handling.
+ *
+ * Copyright (C) 2017 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#ifndef _LINUX_SB_CONFIG_H
+#define _LINUX_SB_CONFIG_H
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+
+struct cred;
+struct dentry;
+struct file_operations;
+struct file_system_type;
+struct mnt_namespace;
+struct net;
+struct pid_namespace;
+struct super_block;
+struct user_namespace;
+struct vfsmount;
+
+enum sb_config_purpose {
+ SB_CONFIG_FOR_NEW, /* New superblock for direct mount */
+ SB_CONFIG_FOR_SUBMOUNT, /* New superblock for automatic submount */
+ SB_CONFIG_FOR_REMOUNT, /* Superblock reconfiguration for remount */
+};
+
+/*
+ * Superblock configuration context as allocated and constructed by the
+ * ->init_sb_config() file_system_type operation. The size of the object
+ * allocated is specified in struct file_system_type::sb_config_size and this
+ * must include sufficient space for the sb_config struct.
+ *
+ * See Documentation/filesystems/mounting.txt
+ */
+struct sb_config {
+ const struct sb_config_operations *ops;
+ struct file_system_type *fs_type;
+ struct user_namespace *user_ns; /* The user namespace for this mount */
+ struct net *net_ns; /* The network namespace for this mount */
+ const struct cred *cred; /* The mounter's credentials */
+ char *device; /* The device name or mount target */
+ void *security; /* The LSM context */
+ const char *error_msg; /* Error string to be read by read() */
+ unsigned int ms_flags; /* The superblock flags (MS_*) */
+ bool mounted; /* Set when mounted */
+ bool sloppy; /* Unrecognised options are okay */
+ bool silent;
+ enum sb_config_purpose purpose : 8;
+};
+
+struct sb_config_operations {
+ void (*free)(struct sb_config *sc);
+ int (*dup)(struct sb_config *sc, struct sb_config *src);
+ int (*parse_option)(struct sb_config *sc, char *p);
+ int (*monolithic_mount_data)(struct sb_config *sc, void *data);
+ int (*validate)(struct sb_config *sc);
+ struct dentry *(*mount)(struct sb_config *sc);
+};
+
+extern const struct file_operations fs_fs_fops;
+
+extern struct sb_config *vfs_new_sb_config(const char *fs_name);
+extern struct sb_config *__vfs_new_sb_config(struct file_system_type *fs_type,
+ struct super_block *src_sb,
+ unsigned int ms_flags,
+ enum sb_config_purpose purpose);
+extern struct sb_config *vfs_sb_reconfig(struct vfsmount *mnt,
+ unsigned int ms_flags);
+extern struct sb_config *vfs_dup_sb_config(struct sb_config *src);
+extern int vfs_parse_mount_option(struct sb_config *sc, char *data);
+extern int generic_monolithic_mount_data(struct sb_config *sc, void *data);
+extern void put_sb_config(struct sb_config *sc);
+
+static inline void sb_cfg_error(struct sb_config *sc, const char *msg)
+{
+ sc->error_msg = msg;
+}
+
+static inline int sb_cfg_inval(struct sb_config *sc, const char *msg)
+{
+ sb_cfg_error(sc, msg);
+ return -EINVAL;
+}
+
+#endif /* _LINUX_SB_CONFIG_H */
diff --git a/include/linux/security.h b/include/linux/security.h
index 96899fad7016..49a7254aa30a 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -55,6 +55,7 @@ struct msg_queue;
struct xattr;
struct xfrm_sec_ctx;
struct mm_struct;
+struct sb_config;
/* If capable should audit the security request */
#define SECURITY_CAP_NOAUDIT 0
@@ -220,6 +221,11 @@ int security_bprm_check(struct linux_binprm *bprm);
void security_bprm_committing_creds(struct linux_binprm *bprm);
void security_bprm_committed_creds(struct linux_binprm *bprm);
int security_bprm_secureexec(struct linux_binprm *bprm);
+int security_sb_config_alloc(struct sb_config *sc, struct super_block *sb);
+int security_sb_config_dup(struct sb_config *sc, struct sb_config *src_sc);
+void security_sb_config_free(struct sb_config *sc);
+int security_sb_config_parse_option(struct sb_config *sc, char *opt);
+int security_sb_config_kern_mount(struct sb_config *sc, struct super_block *sb);
int security_sb_alloc(struct super_block *sb);
void security_sb_free(struct super_block *sb);
int security_sb_copy_data(char *orig, char *copy);
@@ -513,6 +519,29 @@ static inline int security_bprm_secureexec(struct linux_binprm *bprm)
return cap_bprm_secureexec(bprm);
}
+static inline int security_sb_config_alloc(struct sb_config *sc,
+ struct super_block *src_sb)
+{
+ return 0;
+}
+static inline int security_sb_config_dup(struct sb_config *sc,
+ struct sb_config *src_sc)
+{
+ return 0;
+}
+static inline void security_sb_config_free(struct sb_config *sc)
+{
+}
+static inline int security_sb_config_parse_option(struct sb_config *sc, char *opt)
+{
+ return 0;
+}
+static inline int security_sb_config_kern_mount(struct sb_config *sc,
+ struct super_block *sb)
+{
+ return 0;
+}
+
static inline int security_sb_alloc(struct super_block *sb)
{
return 0;
diff --git a/security/security.c b/security/security.c
index 23555c5504f6..fa9037186634 100644
--- a/security/security.c
+++ b/security/security.c
@@ -309,6 +309,31 @@ int security_bprm_secureexec(struct linux_binprm *bprm)
return call_int_hook(bprm_secureexec, 0, bprm);
}
+int security_sb_config_alloc(struct sb_config *sc, struct super_block *src_sb)
+{
+ return call_int_hook(sb_config_alloc, 0, sc, src_sb);
+}
+
+int security_sb_config_dup(struct sb_config *sc, struct sb_config *src_sc)
+{
+ return call_int_hook(sb_config_dup, 0, sc, src_sc);
+}
+
+void security_sb_config_free(struct sb_config *sc)
+{
+ call_void_hook(sb_config_free, sc);
+}
+
+int security_sb_config_parse_option(struct sb_config *sc, char *opt)
+{
+ return call_int_hook(sb_config_parse_option, 0, sc, opt);
+}
+
+int security_sb_config_kern_mount(struct sb_config *sc, struct super_block *sb)
+{
+ return call_int_hook(sb_config_kern_mount, 0, sc, sb);
+}
+
int security_sb_alloc(struct super_block *sb)
{
return call_int_hook(sb_alloc_security, 0, sb);
@@ -1659,6 +1684,13 @@ struct security_hook_heads security_hook_heads = {
LIST_HEAD_INIT(security_hook_heads.bprm_committing_creds),
.bprm_committed_creds =
LIST_HEAD_INIT(security_hook_heads.bprm_committed_creds),
+ .sb_config_alloc = LIST_HEAD_INIT(security_hook_heads.sb_config_alloc),
+ .sb_config_dup = LIST_HEAD_INIT(security_hook_heads.sb_config_dup),
+ .sb_config_free = LIST_HEAD_INIT(security_hook_heads.sb_config_free),
+ .sb_config_parse_option =
+ LIST_HEAD_INIT(security_hook_heads.sb_config_parse_option),
+ .sb_config_kern_mount =
+ LIST_HEAD_INIT(security_hook_heads.sb_config_kern_mount),
.sb_alloc_security =
LIST_HEAD_INIT(security_hook_heads.sb_alloc_security),
.sb_free_security =
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 0c2ac318aa7f..142be91888c9 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -47,6 +47,7 @@
#include <linux/fdtable.h>
#include <linux/namei.h>
#include <linux/mount.h>
+#include <linux/sb_config.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_ipv6.h>
#include <linux/tty.h>
@@ -2826,6 +2827,169 @@ static int selinux_umount(struct vfsmount *mnt, int flags)
FILESYSTEM__UNMOUNT, NULL);
}
+/* fsopen mount context operations */
+
+static int selinux_sb_config_alloc(struct sb_config *sc,
+ struct super_block *src_sb)
+{
+ struct security_mnt_opts *opts;
+
+ opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+ if (!opts)
+ return -ENOMEM;
+
+ sc->security = opts;
+ return 0;
+}
+
+static int selinux_sb_config_dup(struct sb_config *sc,
+ struct sb_config *src_sc)
+{
+ const struct security_mnt_opts *src = src_sc->security;
+ struct security_mnt_opts *opts;
+ int i, n;
+
+ opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+ if (!opts)
+ return -ENOMEM;
+ sc->security = opts;
+
+ if (!src || !src->num_mnt_opts)
+ return 0;
+ n = opts->num_mnt_opts = src->num_mnt_opts;
+
+ if (src->mnt_opts) {
+ opts->mnt_opts = kcalloc(n, sizeof(char *), GFP_KERNEL);
+ if (!opts->mnt_opts)
+ return -ENOMEM;
+
+ for (i = 0; i < n; i++) {
+ if (src->mnt_opts[i]) {
+ opts->mnt_opts[i] = kstrdup(src->mnt_opts[i],
+ GFP_KERNEL);
+ if (!opts->mnt_opts[i])
+ return -ENOMEM;
+ }
+ }
+ }
+
+ if (src->mnt_opts_flags) {
+ opts->mnt_opts_flags = kmemdup(src->mnt_opts_flags,
+ n * sizeof(int), GFP_KERNEL);
+ if (!opts->mnt_opts_flags)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void selinux_sb_config_free(struct sb_config *sc)
+{
+ struct security_mnt_opts *opts = sc->security;
+
+ security_free_mnt_opts(opts);
+ sc->security = NULL;
+}
+
+static int selinux_sb_config_parse_option(struct sb_config *sc, char *opt)
+{
+ struct security_mnt_opts *opts = sc->security;
+ substring_t args[MAX_OPT_ARGS];
+ unsigned int have;
+ char *c, **oo;
+ int token, ctx, i, *of;
+
+ token = match_token(opt, tokens, args);
+ if (token == Opt_error)
+ return 0; /* Doesn't belong to us. */
+
+ have = 0;
+ for (i = 0; i < opts->num_mnt_opts; i++)
+ have |= 1 << opts->mnt_opts_flags[i];
+ if (have & (1 << token))
+ return sb_cfg_inval(sc, "SELinux: Duplicate mount options");
+
+ switch (token) {
+ case Opt_context:
+ if (have & (1 << Opt_defcontext))
+ goto incompatible;
+ ctx = CONTEXT_MNT;
+ goto copy_context_string;
+
+ case Opt_fscontext:
+ ctx = FSCONTEXT_MNT;
+ goto copy_context_string;
+
+ case Opt_rootcontext:
+ ctx = ROOTCONTEXT_MNT;
+ goto copy_context_string;
+
+ case Opt_defcontext:
+ if (have & (1 << Opt_context))
+ goto incompatible;
+ ctx = DEFCONTEXT_MNT;
+ goto copy_context_string;
+
+ case Opt_labelsupport:
+ return 1;
+
+ default:
+ return sb_cfg_inval(sc, "SELinux: Unknown mount option");
+ }
+
+copy_context_string:
+ if (opts->num_mnt_opts > 3)
+ return sb_cfg_inval(sc, "SELinux: Too many options");
+
+ of = krealloc(opts->mnt_opts_flags,
+ (opts->num_mnt_opts + 1) * sizeof(int), GFP_KERNEL);
+ if (!of)
+ return -ENOMEM;
+ of[opts->num_mnt_opts] = 0;
+ opts->mnt_opts_flags = of;
+
+ oo = krealloc(opts->mnt_opts,
+ (opts->num_mnt_opts + 1) * sizeof(char *), GFP_KERNEL);
+ if (!oo)
+ return -ENOMEM;
+ oo[opts->num_mnt_opts] = NULL;
+ opts->mnt_opts = oo;
+
+ c = match_strdup(&args[0]);
+ if (!c)
+ return -ENOMEM;
+ opts->mnt_opts[opts->num_mnt_opts] = c;
+ opts->mnt_opts_flags[opts->num_mnt_opts] = ctx;
+ opts->num_mnt_opts++;
+ return 1;
+
+incompatible:
+ return sb_cfg_inval(sc, "SELinux: Incompatible mount options");
+}
+
+static int selinux_sb_config_kern_mount(struct sb_config *sc,
+ struct super_block *sb)
+{
+ const struct cred *cred = current_cred();
+ struct common_audit_data ad;
+ int rc;
+
+ rc = selinux_set_mnt_opts(sb, sc->security, 0, NULL);
+ if (rc)
+ return rc;
+
+ /* Allow all mounts performed by the kernel */
+ if (sc->ms_flags & MS_KERNMOUNT)
+ return 0;
+
+ ad.type = LSM_AUDIT_DATA_DENTRY;
+ ad.u.dentry = sb->s_root;
+ rc = superblock_has_perm(cred, sb, FILESYSTEM__MOUNT, &ad);
+ if (rc < 0)
+ sb_cfg_error(sc, "SELinux: Mount of superblock not permitted");
+ return rc;
+}
+
/* inode security operations */
static int selinux_inode_alloc_security(struct inode *inode)
@@ -6131,6 +6295,12 @@ static struct security_hook_list selinux_hooks[] = {
LSM_HOOK_INIT(bprm_committed_creds, selinux_bprm_committed_creds),
LSM_HOOK_INIT(bprm_secureexec, selinux_bprm_secureexec),
+ LSM_HOOK_INIT(sb_config_alloc, selinux_sb_config_alloc),
+ LSM_HOOK_INIT(sb_config_dup, selinux_sb_config_dup),
+ LSM_HOOK_INIT(sb_config_free, selinux_sb_config_free),
+ LSM_HOOK_INIT(sb_config_parse_option, selinux_sb_config_parse_option),
+ LSM_HOOK_INIT(sb_config_kern_mount, selinux_sb_config_kern_mount),
+
LSM_HOOK_INIT(sb_alloc_security, selinux_sb_alloc_security),
LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security),
LSM_HOOK_INIT(sb_copy_data, selinux_sb_copy_data),
---
samples/fsmount/test-fsmount.c | 79 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 79 insertions(+)
create mode 100644 samples/fsmount/test-fsmount.c
diff --git a/samples/fsmount/test-fsmount.c b/samples/fsmount/test-fsmount.c
new file mode 100644
index 000000000000..98b0258ae08f
--- /dev/null
+++ b/samples/fsmount/test-fsmount.c
@@ -0,0 +1,79 @@
+/* fd-based mount test.
+ *
+ * Copyright (C) 2017 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+
+#define E(x) do { if ((x) == -1) { perror(#x); exit(1); } } while(0)
+
+static __attribute__((noreturn))
+void mount_error(int fd, const char *s)
+{
+ char buf[4096];
+ int err, n;
+
+ err = errno;
+ n = read(fd, buf, sizeof(buf));
+ errno = err;
+ if (n > 0) {
+ n -= 2;
+ fprintf(stderr, "Error: '%s': %*.*s: %m\n", s, n, n, buf + 2);
+ } else {
+ fprintf(stderr, "%s: %m\n", s);
+ }
+ exit(1);
+}
+
+#define E_write(fd, s) \
+ do { \
+ if (write(fd, s, sizeof(s) - 1) == -1) \
+ mount_error(fd, s); \
+ } while (0)
+
+static inline int fsopen(const char *fs_name, int reserved, int flags)
+{
+ return syscall(333, fs_name, reserved, flags);
+}
+
+static inline int fsmount(int fsfd, int dfd, const char *path,
+ unsigned int at_flags, unsigned int flags)
+{
+ return syscall(334, fsfd, dfd, path, at_flags, flags);
+}
+
+int main()
+{
+ int mfd;
+
+ /* Mount an NFS filesystem */
+ mfd = fsopen("nfs4", -1, 0);
+ if (mfd == -1) {
+ perror("fsopen");
+ exit(1);
+ }
+
+ E_write(mfd, "s warthog:/data");
+ E_write(mfd, "o fsc");
+ E_write(mfd, "o sync");
+ E_write(mfd, "o intr");
+ E_write(mfd, "o vers=4.2");
+ E_write(mfd, "o addr=90.155.74.18");
+ E_write(mfd, "o clientaddr=90.155.74.21");
+ if (fsmount(mfd, AT_FDCWD, "/mnt", 0, 0) < 0)
+ mount_error(mfd, "fsmount");
+ E(close(mfd));
+
+ exit(0);
+}
Provide an fsopen() system call that starts the process of preparing to
mount, using an fd as a context handle. fsopen() is given the name of the
filesystem that will be used:
int mfd = fsopen(const char *fsname, int reserved,
int open_flags);
where reserved should be -1 for the moment (it will be used to pass the
namespace information in future) and open_flags can be 0 or O_CLOEXEC.
For example:
mfd = fsopen("ext4", -1, O_CLOEXEC);
write(mfd, "s /dev/sdb1"); // note I'm ignoring write's length arg
write(mfd, "o noatime");
write(mfd, "o acl");
write(mfd, "o user_attr");
write(mfd, "o iversion");
write(mfd, "o ");
write(mfd, "r /my/container"); // root inside the fs
fsmount(mfd, container_fd, "/mnt", AT_NO_FOLLOW);
mfd = fsopen("afs", -1);
write(mfd, "s %grand.central.org:root.cell");
write(mfd, "o cell=grand.central.org");
write(mfd, "r /");
fsmount(mfd, AT_FDCWD, "/mnt", 0);
If an error is reported at any step, an error message may be available to be
read() back (ENODATA will be reported if there isn't an error available) in
the form:
"e <subsys>:<problem>"
"e SELinux:Mount on mountpoint not permitted"
Once fsmount() has been called, further write() calls will incur EBUSY,
even if the fsmount() fails. read() is still possible to retrieve error
information.
The fsopen() syscall creates a mount context and hangs it of the fd that it
returns.
Netlink is not used because it is optional.
Signed-off-by: David Howells <[email protected]>
---
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
fs/Makefile | 2
fs/fsopen.c | 279 ++++++++++++++++++++++++++++++++
include/linux/syscalls.h | 1
include/uapi/linux/magic.h | 1
kernel/sys_ni.c | 3
7 files changed, 287 insertions(+), 1 deletion(-)
create mode 100644 fs/fsopen.c
diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index 448ac2161112..9bf8d4c62f85 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -391,3 +391,4 @@
382 i386 pkey_free sys_pkey_free
383 i386 statx sys_statx
384 i386 arch_prctl sys_arch_prctl compat_sys_arch_prctl
+385 i386 fsopen sys_fsopen
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index 5aef183e2f85..9b198c5fc412 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -339,6 +339,7 @@
330 common pkey_alloc sys_pkey_alloc
331 common pkey_free sys_pkey_free
332 common statx sys_statx
+333 common fsopen sys_fsopen
#
# x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/fs/Makefile b/fs/Makefile
index 8f5142525866..b8fcf48b0400 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -12,7 +12,7 @@ obj-y := open.o read_write.o file_table.o super.o \
seq_file.o xattr.o libfs.o fs-writeback.o \
pnode.o splice.o sync.o utimes.o \
stack.o fs_struct.o statfs.o fs_pin.o nsfs.o \
- sb_config.o
+ sb_config.o fsopen.o
ifeq ($(CONFIG_BLOCK),y)
obj-y += buffer.o block_dev.o direct-io.o mpage.o
diff --git a/fs/fsopen.c b/fs/fsopen.c
new file mode 100644
index 000000000000..a4e9d5a7ce2b
--- /dev/null
+++ b/fs/fsopen.c
@@ -0,0 +1,279 @@
+/* Filesystem access-by-fd.
+ *
+ * Copyright (C) 2017 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/sb_config.h>
+#include <linux/mount.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/file.h>
+#include <linux/magic.h>
+#include <linux/syscalls.h>
+
+static struct vfsmount *fs_fs_mnt __read_mostly;
+
+static int fs_fs_release(struct inode *inode, struct file *file)
+{
+ struct sb_config *sc = file->private_data;
+
+ file->private_data = NULL;
+
+ put_sb_config(sc);
+ return 0;
+}
+
+/*
+ * Read any error message back from the fd. Will be prefixed by "e ".
+ */
+static ssize_t fs_fs_read(struct file *file, char __user *_buf, size_t len, loff_t *pos)
+{
+ struct sb_config *sc = file->private_data;
+ const char *msg;
+ size_t mlen;
+
+ msg = READ_ONCE(sc->error_msg);
+ if (!msg)
+ return -ENODATA;
+
+ mlen = strlen(msg);
+ if (mlen + 2 > len)
+ return -ETOOSMALL;
+ if (copy_to_user(_buf, "e ", 2) != 0 ||
+ copy_to_user(_buf + 2, msg, mlen) != 0)
+ return -EFAULT;
+ return mlen + 2;
+}
+
+/*
+ * Userspace writes configuration data to the fd and we parse it here. For the
+ * moment, we assume a single option per write. Each line written is of the form
+ *
+ * <option_type><space><stuff...>
+ *
+ * d /dev/sda1 -- Device name
+ * o noatime -- Option without value
+ * o cell=grand.central.org -- Option with value
+ * r / -- Dir within device to mount
+ */
+static ssize_t fs_fs_write(struct file *file,
+ const char __user *_buf, size_t len, loff_t *pos)
+{
+ struct sb_config *sc = file->private_data;
+ struct inode *inode = file_inode(file);
+ char opt[2], *data;
+ ssize_t ret;
+
+ if (len < 3 || len > 4095)
+ return -EINVAL;
+
+ if (copy_from_user(opt, _buf, 2) != 0)
+ return -EFAULT;
+ switch (opt[0]) {
+ case 's':
+ case 'o':
+ break;
+ default:
+ return sb_cfg_inval(sc, "VFS: Unsupported write spec");
+ }
+ if (opt[1] != ' ')
+ return sb_cfg_inval(sc, "VFS: Unsupported write spec");
+
+ data = memdup_user_nul(_buf + 2, len - 2);
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ /* From this point onwards we need to lock the fd against someone
+ * trying to mount it.
+ */
+ ret = inode_lock_killable(inode);
+ if (ret < 0)
+ goto err_free;
+
+ ret = -EBUSY;
+ if (sc->mounted)
+ goto err_unlock;
+
+ ret = -EINVAL;
+ switch (opt[0]) {
+ case 's':
+ if (sc->device)
+ goto err_unlock;
+ sc->device = data;
+ data = NULL;
+ break;
+
+ case 'o':
+ ret = vfs_parse_mount_option(sc, data);
+ if (ret < 0)
+ goto err_unlock;
+ break;
+
+ default:
+ goto err_unlock;
+ }
+
+ ret = len;
+err_unlock:
+ inode_unlock(inode);
+err_free:
+ kfree(data);
+ return ret;
+}
+
+const struct file_operations fs_fs_fops = {
+ .read = fs_fs_read,
+ .write = fs_fs_write,
+ .release = fs_fs_release,
+ .llseek = no_llseek,
+};
+
+/*
+ * Indicate the name we want to display the filesystem file as.
+ */
+static char *fs_fs_dname(struct dentry *dentry, char *buffer, int buflen)
+{
+ return dynamic_dname(dentry, buffer, buflen, "fs:[%lu]",
+ d_inode(dentry)->i_ino);
+}
+
+static const struct dentry_operations fs_fs_dentry_operations = {
+ .d_dname = fs_fs_dname,
+};
+
+/*
+ * Create a file that can be used to configure a new mount.
+ */
+static struct file *create_fs_file(struct sb_config *sc)
+{
+ struct inode *inode;
+ struct file *f;
+ struct path path;
+ int ret;
+
+ inode = alloc_anon_inode(fs_fs_mnt->mnt_sb);
+ if (!inode)
+ return ERR_PTR(-ENFILE);
+ inode->i_fop = &fs_fs_fops;
+
+ ret = -ENOMEM;
+ path.dentry = d_alloc_pseudo(fs_fs_mnt->mnt_sb, &empty_name);
+ if (!path.dentry)
+ goto err_inode;
+ path.mnt = mntget(fs_fs_mnt);
+
+ d_instantiate(path.dentry, inode);
+
+ f = alloc_file(&path, FMODE_READ | FMODE_WRITE, &fs_fs_fops);
+ if (IS_ERR(f)) {
+ ret = PTR_ERR(f);
+ goto err_file;
+ }
+
+ f->private_data = sc;
+ return f;
+
+err_file:
+ path_put(&path);
+ return ERR_PTR(ret);
+
+err_inode:
+ iput(inode);
+ return ERR_PTR(ret);
+}
+
+ const struct super_operations fs_fs_ops = {
+ .drop_inode = generic_delete_inode,
+ .destroy_inode = free_inode_nonrcu,
+ .statfs = simple_statfs,
+};
+
+static struct dentry *fs_fs_mount(struct file_system_type *fs_type,
+ int flags, const char *dev_name,
+ void *data)
+{
+ return mount_pseudo(fs_type, "fs_fs:", &fs_fs_ops,
+ &fs_fs_dentry_operations, FS_FS_MAGIC);
+}
+
+static struct file_system_type fs_fs_type = {
+ .name = "fs_fs",
+ .mount = fs_fs_mount,
+ .kill_sb = kill_anon_super,
+};
+
+static int __init init_fs_fs(void)
+{
+ int ret;
+
+ ret = register_filesystem(&fs_fs_type);
+ if (ret < 0)
+ panic("Cannot register fs_fs\n");
+
+ fs_fs_mnt = kern_mount(&fs_fs_type);
+ if (IS_ERR(fs_fs_mnt))
+ panic("Cannot mount fs_fs: %ld\n", PTR_ERR(fs_fs_mnt));
+ return 0;
+}
+
+fs_initcall(init_fs_fs);
+
+/*
+ * Open a filesystem by name so that it can be configured for mounting.
+ *
+ * We are allowed to specify a container in which the filesystem will be
+ * opened, thereby indicating which namespaces will be used (notably, which
+ * network namespace will be used for network filesystems).
+ */
+SYSCALL_DEFINE3(fsopen, const char __user *, _fs_name, int, reserved,
+ unsigned int, flags)
+{
+ struct sb_config *sc;
+ struct file *file;
+ const char *fs_name;
+ int fd, ret;
+
+ if (flags & ~O_CLOEXEC || reserved != -1)
+ return -EINVAL;
+
+ fs_name = strndup_user(_fs_name, PAGE_SIZE);
+ if (IS_ERR(fs_name))
+ return PTR_ERR(fs_name);
+
+ sc = vfs_new_sb_config(fs_name);
+ kfree(fs_name);
+ if (IS_ERR(sc))
+ return PTR_ERR(sc);
+
+ ret = -ENOTSUPP;
+ if (!sc->ops)
+ goto err_sc;
+
+ file = create_fs_file(sc);
+ if (IS_ERR(file)) {
+ ret = PTR_ERR(file);
+ goto err_sc;
+ }
+
+ ret = get_unused_fd_flags(flags & O_CLOEXEC);
+ if (ret < 0)
+ goto err_file;
+
+ fd = ret;
+ fd_install(fd, file);
+ return fd;
+
+err_file:
+ fput(file);
+ return ret;
+
+err_sc:
+ put_sb_config(sc);
+ return ret;
+}
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 980c3c9b06f8..91ec8802ad5d 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -905,5 +905,6 @@ asmlinkage long sys_pkey_alloc(unsigned long flags, unsigned long init_val);
asmlinkage long sys_pkey_free(int pkey);
asmlinkage long sys_statx(int dfd, const char __user *path, unsigned flags,
unsigned mask, struct statx __user *buffer);
+asmlinkage long sys_fsopen(const char *fs_name, int containerfd, unsigned int flags);
#endif
diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h
index e230af2e6855..88ae83492f7c 100644
--- a/include/uapi/linux/magic.h
+++ b/include/uapi/linux/magic.h
@@ -84,5 +84,6 @@
#define UDF_SUPER_MAGIC 0x15013346
#define BALLOON_KVM_MAGIC 0x13661366
#define ZSMALLOC_MAGIC 0x58295829
+#define FS_FS_MAGIC 0x66736673
#endif /* __LINUX_MAGIC_H__ */
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index 8acef8576ce9..de1dc63e7e47 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -258,3 +258,6 @@ cond_syscall(sys_membarrier);
cond_syscall(sys_pkey_mprotect);
cond_syscall(sys_pkey_alloc);
cond_syscall(sys_pkey_free);
+
+/* fd-based mount */
+cond_syscall(sys_fsopen);
Provide a system call by which a filesystem opened with fsopen() and
configured by a series of writes can be mounted:
int ret = fsmount(int fsfd, int dfd, const char *path,
unsigned int at_flags, unsigned int flags);
where fsfd is the fd returned by fsopen(), dfd, path and at_flags locate
the mountpoint and flags are the applicable MS_* flags. dfd can be
AT_FDCWD or an fd open to a directory.
In the event that fsmount() fails, it may be possible to get an error
message by calling read(). If no message is available, ENODATA will be
reported.
Signed-off-by: David Howells <[email protected]>
---
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
fs/namespace.c | 99 ++++++++++++++++++++++++++++++++
include/linux/lsm_hooks.h | 6 ++
include/linux/security.h | 6 ++
include/linux/syscalls.h | 2 +
kernel/sys_ni.c | 1
security/security.c | 7 ++
security/selinux/hooks.c | 13 ++++
9 files changed, 136 insertions(+)
diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index 9bf8d4c62f85..abe6ea95e0e6 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -392,3 +392,4 @@
383 i386 statx sys_statx
384 i386 arch_prctl sys_arch_prctl compat_sys_arch_prctl
385 i386 fsopen sys_fsopen
+386 i386 fsmount sys_fsmount
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index 9b198c5fc412..0977c5079831 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -340,6 +340,7 @@
331 common pkey_free sys_pkey_free
332 common statx sys_statx
333 common fsopen sys_fsopen
+334 common fsmount sys_fsmount
#
# x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/fs/namespace.c b/fs/namespace.c
index 8ade7252ee34..6e43657d78bd 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3253,6 +3253,105 @@ vfs_submount_sc(const struct dentry *mountpoint, struct sb_config *sc)
EXPORT_SYMBOL_GPL(vfs_submount_sc);
/*
+ * Mount a new, prepared superblock (specified by fs_fd) on the location
+ * specified by dfd and dir_name. dfd can be AT_FDCWD, a dir fd or a container
+ * fd. This cannot be used for binding, moving or remounting mounts.
+ */
+SYSCALL_DEFINE5(fsmount, int, fs_fd, int, dfd, const char __user *, dir_name,
+ unsigned int, at_flags, unsigned int, flags)
+{
+ struct sb_config *sc;
+ struct inode *inode;
+ struct path mountpoint;
+ struct fd f;
+ unsigned int lookup_flags, mnt_flags = 0;
+ long ret;
+
+ if ((at_flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT |
+ AT_EMPTY_PATH)) != 0)
+ return -EINVAL;
+
+ if (flags & ~(MS_RDONLY | MS_NOSUID | MS_NODEV | MS_NOEXEC |
+ MS_NOATIME | MS_NODIRATIME | MS_RELATIME | MS_STRICTATIME))
+ return -EINVAL;
+
+ if (flags & MS_RDONLY)
+ mnt_flags |= MNT_READONLY;
+ if (flags & MS_NOSUID)
+ mnt_flags |= MNT_NOSUID;
+ if (flags & MS_NODEV)
+ mnt_flags |= MNT_NODEV;
+ if (flags & MS_NOEXEC)
+ mnt_flags |= MNT_NOEXEC;
+ if (flags & MS_NODIRATIME)
+ mnt_flags |= MNT_NODIRATIME;
+
+ if (flags & MS_STRICTATIME) {
+ if (flags & MS_NOATIME)
+ return -EINVAL;
+ } else if (flags & MS_NOATIME) {
+ mnt_flags |= MNT_NOATIME;
+ } else {
+ mnt_flags |= MNT_RELATIME;
+ }
+
+ f = fdget(fs_fd);
+ if (!f.file)
+ return -EBADF;
+
+ ret = -EINVAL;
+ if (f.file->f_op != &fs_fs_fops)
+ goto err_fsfd;
+
+ sc = f.file->private_data;
+
+ ret = -EPERM;
+ if (!may_mount() ||
+ ((sc->ms_flags & MS_MANDLOCK) && !may_mandlock()))
+ goto err_fsfd;
+
+ /* Prevent further changes. */
+ inode = file_inode(f.file);
+ ret = inode_lock_killable(inode);
+ if (ret < 0)
+ goto err_fsfd;
+ ret = -EBUSY;
+ if (!sc->mounted) {
+ sc->mounted = true;
+ ret = 0;
+ }
+ inode_unlock(inode);
+ if (ret < 0)
+ goto err_fsfd;
+
+ /* Find the mountpoint. A container can be specified in dfd. */
+ lookup_flags = LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT;
+ if (at_flags & AT_SYMLINK_NOFOLLOW)
+ lookup_flags &= ~LOOKUP_FOLLOW;
+ if (at_flags & AT_NO_AUTOMOUNT)
+ lookup_flags &= ~LOOKUP_AUTOMOUNT;
+ if (at_flags & AT_EMPTY_PATH)
+ lookup_flags |= LOOKUP_EMPTY;
+ ret = user_path_at(dfd, dir_name, lookup_flags, &mountpoint);
+ if (ret < 0) {
+ sb_cfg_error(sc, "VFS: Mountpoint lookup failed");
+ goto err_fsfd;
+ }
+
+ ret = security_sb_config_mountpoint(sc, &mountpoint);
+ if (ret < 0)
+ goto err_mp;
+
+ ret = do_new_mount_sc(sc, &mountpoint, mnt_flags);
+
+err_mp:
+ path_put(&mountpoint);
+err_fsfd:
+ fdput(f);
+ return ret;
+}
+
+/*
* Return true if path is reachable from root
*
* namespace_sem or mount_lock is held
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 6238fccaf5a4..e8e473ce5ddd 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -101,6 +101,10 @@
* Equivalent of sb_kern_mount, but with a superblock configuration context.
* @sc indicates the superblock configuration context.
* @src_sb indicates the new superblock.
+ * @sb_config_mountpoint:
+ * Equivalent of sb_mount, but with an sb_config.
+ * @sc indicates the superblock configuration context.
+ * @mountpoint indicates the path on which the mount will take place.
*
* Security hooks for filesystem operations.
*
@@ -1390,6 +1394,7 @@ union security_list_options {
void (*sb_config_free)(struct sb_config *sc);
int (*sb_config_parse_option)(struct sb_config *sc, char *opt);
int (*sb_config_kern_mount)(struct sb_config *sc, struct super_block *sb);
+ int (*sb_config_mountpoint)(struct sb_config *sc, struct path *mountpoint);
int (*sb_alloc_security)(struct super_block *sb);
void (*sb_free_security)(struct super_block *sb);
@@ -1704,6 +1709,7 @@ struct security_hook_heads {
struct list_head sb_config_free;
struct list_head sb_config_parse_option;
struct list_head sb_config_kern_mount;
+ struct list_head sb_config_mountpoint;
struct list_head sb_alloc_security;
struct list_head sb_free_security;
struct list_head sb_copy_data;
diff --git a/include/linux/security.h b/include/linux/security.h
index 49a7254aa30a..f95dc555cf29 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -226,6 +226,7 @@ int security_sb_config_dup(struct sb_config *sc, struct sb_config *src_sc);
void security_sb_config_free(struct sb_config *sc);
int security_sb_config_parse_option(struct sb_config *sc, char *opt);
int security_sb_config_kern_mount(struct sb_config *sc, struct super_block *sb);
+int security_sb_config_mountpoint(struct sb_config *sc, struct path *mountpoint);
int security_sb_alloc(struct super_block *sb);
void security_sb_free(struct super_block *sb);
int security_sb_copy_data(char *orig, char *copy);
@@ -541,6 +542,11 @@ static inline int security_sb_config_kern_mount(struct sb_config *sc,
{
return 0;
}
+static inline int security_sb_config_mountpoint(struct sb_config *sc,
+ struct path *mountpoint)
+{
+ return 0;
+}
static inline int security_sb_alloc(struct super_block *sb)
{
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 91ec8802ad5d..07e4f775f24d 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -906,5 +906,7 @@ asmlinkage long sys_pkey_free(int pkey);
asmlinkage long sys_statx(int dfd, const char __user *path, unsigned flags,
unsigned mask, struct statx __user *buffer);
asmlinkage long sys_fsopen(const char *fs_name, int containerfd, unsigned int flags);
+asmlinkage long sys_fsmount(int fsfd, int dfd, const char *path, unsigned int at_flags,
+ unsigned int flags);
#endif
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index de1dc63e7e47..a0fe764bd5dd 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -261,3 +261,4 @@ cond_syscall(sys_pkey_free);
/* fd-based mount */
cond_syscall(sys_fsopen);
+cond_syscall(sys_fsmount);
diff --git a/security/security.c b/security/security.c
index fa9037186634..8b2b7e6b3112 100644
--- a/security/security.c
+++ b/security/security.c
@@ -334,6 +334,11 @@ int security_sb_config_kern_mount(struct sb_config *sc, struct super_block *sb)
return call_int_hook(sb_config_kern_mount, 0, sc, sb);
}
+int security_sb_config_mountpoint(struct sb_config *sc, struct path *mountpoint)
+{
+ return call_int_hook(sb_config_mountpoint, 0, sc, mountpoint);
+}
+
int security_sb_alloc(struct super_block *sb)
{
return call_int_hook(sb_alloc_security, 0, sb);
@@ -1691,6 +1696,8 @@ struct security_hook_heads security_hook_heads = {
LIST_HEAD_INIT(security_hook_heads.sb_config_parse_option),
.sb_config_kern_mount =
LIST_HEAD_INIT(security_hook_heads.sb_config_kern_mount),
+ .sb_config_mountpoint =
+ LIST_HEAD_INIT(security_hook_heads.sb_config_mountpoint),
.sb_alloc_security =
LIST_HEAD_INIT(security_hook_heads.sb_alloc_security),
.sb_free_security =
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 142be91888c9..e1bf18af72f5 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2990,6 +2990,18 @@ static int selinux_sb_config_kern_mount(struct sb_config *sc,
return rc;
}
+static int selinux_sb_config_mountpoint(struct sb_config *sc,
+ struct path *mountpoint)
+{
+ const struct cred *cred = current_cred();
+ int ret;
+
+ ret = path_has_perm(cred, mountpoint, FILE__MOUNTON);
+ if (ret < 0)
+ sb_cfg_error(sc, "SELinux: Mount on mountpoint not permitted");
+ return ret;
+}
+
/* inode security operations */
static int selinux_inode_alloc_security(struct inode *inode)
@@ -6300,6 +6312,7 @@ static struct security_hook_list selinux_hooks[] = {
LSM_HOOK_INIT(sb_config_free, selinux_sb_config_free),
LSM_HOOK_INIT(sb_config_parse_option, selinux_sb_config_parse_option),
LSM_HOOK_INIT(sb_config_kern_mount, selinux_sb_config_kern_mount),
+ LSM_HOOK_INIT(sb_config_mountpoint, selinux_sb_config_mountpoint),
LSM_HOOK_INIT(sb_alloc_security, selinux_sb_alloc_security),
LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security),
Hi David,
On 05/11/2017 09:59 AM, David Howells wrote:
>
> Here are a set of patches to create a superblock configuration context
> prior to setting up a new mount, populating it with the parsed
> options/binary data, creating the superblock and then effecting the mount.
>
> This allows namespaces and other information to be conveyed through the
> mount procedure. It also allows extra error information to be returned
> (so many things can go wrong during a mount that a small integer isn't
> really sufficient to convey the issue).
>
> This also allows Miklós Szeredi's idea of doing:
>
> fd = fsopen("nfs");
> write(fd, "option=val", ...);
> fsmount(fd, "/mnt");
>
> that he presented at LSF-2017 to be implemented (see the relevant patches
> in the series), to which I can add:
>
> read(fd, error_buffer, ...);
>
> to read back any error message. I didn't use netlink as that would make it
> depend on CONFIG_NET and would introduce network namespacing issues.
>
> I've implemented mount context handling for procfs and nfs.
>
> Significant changes:
>
> ver #2:
>
> (*) Removed the ->fill_super() from sb_config_operations and passed it in
> directly to functions that want to call it. NFS now calls
> nfs_fill_super() directly rather than jumping through a pointer to it
> since there's only the one option at the moment.
>
> (*) Removed ->mnt_ns and ->sb from sb_config and moved ->pid_ns into
> proc_sb_config.
>
> (*) Renamed create_super -> get_tree.
>
> (*) Renamed struct mount_context to struct sb_config and amended various
> variable names.
>
> (*) sys_fsmount() acquired AT_* flags and MS_* flags (for MNT_* flags)
> arguments.
>
> ver #1:
>
> (*) Split the sb_config stuff out into its own header.
>
> (*) Support non-context aware filesystems through a special set of
> sb_config operations.
>
> (*) Stored the created superblock and root dentry into the sb_config after
> creation rather than directly into a vfsmount. This allows some
> arguments to be removed to various NFS functions.
>
> (*) Added an explicit superblock-creation step. This allows a created
> superblock to then be mounted multiple times.
>
> (*) Added a flag to say that the sb_config is degraded and cannot have
> another go at having a superblock creation whilst getting rid of the
> one that says it's already mounted.
>
> Further developments:
>
> (*) Implement sb reconfiguration (for now it returns ENOANO).
>
> (*) Implement mount context support in more filesystems, ext4 being next
> on my list.
>
> (*) Move the walk-from-root stuff that nfs has to generic code so that you
> can do something akin to:
>
> mount /dev/sda1:/foo/bar /mnt
>
> See nfs_follow_remote_path() and mount_subtree(). This is slightly
> tricky in NFS as we have to prevent referral loops.
>
> (*) Move the pid_ns pointer from struct mount_context to struct
> proc_mount_context as I'm not sure it's necessary for anything other
> than procfs.
>
> (*) Work out how to get at the error message incurred by submounts
> encountered during nfs_follow_remote_path().
>
> Should the error message be moved to task_struct and made more
> general, perhaps retrieved with a prctl() function?
>
> (*) Clean up/consolidate the security functions. Possibly add a
> validation hook to be called at the same time as the mount context
> validate op.
>
> The patches can be found here also:
>
> http://git.kernel.org/cgit/linux/kernel/git/dhowells/linux-fs.git/log/?h=mount-context
>
> David
> ---
> David Howells (14):
> Provide a function to create a NUL-terminated string from unterminated data
> Clean up whitespace in fs/namespace.c
> VFS: Make get_mnt_ns() return the namespace
> VFS: Make get_filesystem() return the affected filesystem
> VFS: Provide empty name qstr
> VFS: Introduce a superblock configuration context
> Implement fsopen() to prepare for a mount
> Implement fsmount() to effect a pre-configured mount
> Sample program for driving fsopen/fsmount
> procfs: Move proc_fill_super() to fs/proc/root.c
> proc: Add superblock config support to procfs
> NFS: Add mount context support.
> Support legacy filesystems
> Add commands to create or update a superblock
>
>
> Documentation/filesystems/mounting.txt | 470 ++++++++
> arch/x86/entry/syscalls/syscall_32.tbl | 2
> arch/x86/entry/syscalls/syscall_64.tbl | 2
> fs/Makefile | 3
> fs/dcache.c | 8
> fs/filesystems.c | 3
> fs/fsopen.c | 302 +++++
> fs/gfs2/dir.c | 3
> fs/internal.h | 4
> fs/libfs.c | 17
> fs/mount.h | 3
> fs/namei.c | 3
> fs/namespace.c | 495 +++++++--
> fs/nfs/Makefile | 2
> fs/nfs/client.c | 74 +
> fs/nfs/getroot.c | 76 +
> fs/nfs/internal.h | 142 +--
> fs/nfs/mount.c | 1497 +++++++++++++++++++++++++++
> fs/nfs/namespace.c | 76 +
> fs/nfs/nfs3_fs.h | 2
> fs/nfs/nfs3client.c | 6
> fs/nfs/nfs3proc.c | 2
> fs/nfs/nfs4_fs.h | 4
> fs/nfs/nfs4client.c | 82 +
> fs/nfs/nfs4namespace.c | 208 ++--
> fs/nfs/nfs4proc.c | 3
> fs/nfs/nfs4super.c | 220 ++--
> fs/nfs/proc.c | 2
> fs/nfs/super.c | 1782 ++------------------------------
> fs/nsfs.c | 3
> fs/pipe.c | 3
> fs/proc/inode.c | 50 -
> fs/proc/internal.h | 6
> fs/proc/root.c | 210 +++-
> fs/sb_config.c | 524 +++++++++
> fs/super.c | 110 +-
> include/linux/dcache.h | 5
> include/linux/fs.h | 16
> include/linux/lsm_hooks.h | 47 +
> include/linux/mount.h | 4
> include/linux/nfs_xdr.h | 7
> include/linux/sb_config.h | 100 ++
> include/linux/security.h | 40 +
> include/linux/string.h | 1
> include/linux/syscalls.h | 3
> include/uapi/linux/magic.h | 1
> kernel/sys_ni.c | 4
> mm/util.c | 24
> samples/fsmount/test-fsmount.c | 79 +
> security/security.c | 45 +
> security/selinux/hooks.c | 202 +++-
> 51 files changed, 4596 insertions(+), 2381 deletions(-)
Is there any way to split the NFS patch into multiple pieces? The patch in your git tree is longer than my attention span, and I'm having a hard time keeping track of everything going on. I also suspect that this might be why it doesn't show up in my email with the rest of your patches.
Thanks,
Anna
> create mode 100644 Documentation/filesystems/mounting.txt
> create mode 100644 fs/fsopen.c
> create mode 100644 fs/nfs/mount.c
> create mode 100644 fs/sb_config.c
> create mode 100644 include/linux/sb_config.h
> create mode 100644 samples/fsmount/test-fsmount.c
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
Anna Schumaker <[email protected]> wrote:
> Is there any way to split the NFS patch into multiple pieces?
Are you okay with a patch or two that add code that is unconnected in that
patch, but connected in a later one?
David
On 05/11/2017 03:24 PM, David Howells wrote:
> Anna Schumaker <[email protected]> wrote:
>
>> Is there any way to split the NFS patch into multiple pieces?
>
> Are you okay with a patch or two that add code that is unconnected in that
> patch, but connected in a later one?
Yes, I'm okay with that. Thanks for working on this!
Anna
>
> David
>