[PATCH v4 4/5] fuse: Support privileged xattrs only with a mount option

From: Seth Forshee
Date: Tue Oct 14 2014 - 10:26:26 EST


Allowing unprivileged users to provide arbitrary xattrs via fuse
mounts bypasses the normal restrictions on setting xattrs. Such
mounts should be restricted to reading and writing xattrs in the
user.* namespace.

It's difficult though to tell whether a mount is being performed
on behalf of an unprivileged user since fuse mounts are ususally
done via a suid root helper. Thus a new mount option,
privileged_xattrs, is added to indicated that xattrs from other
namespaces are allowed. This option can only be supplied by
system-wide root; supplying the option as an unprivileged user
will cause the mount to fail.

Cc: Eric W. Biederman <ebiederm@xxxxxxxxxxxx>
Cc: Serge H. Hallyn <serge.hallyn@xxxxxxxxxx>
Signed-off-by: Seth Forshee <seth.forshee@xxxxxxxxxxxxx>
---
fs/fuse/dir.c | 9 +++++++++
fs/fuse/fuse_i.h | 5 +++++
fs/fuse/inode.c | 37 ++++++++++++++++++++++++-------------
3 files changed, 38 insertions(+), 13 deletions(-)

diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index e3123bfbc711..1bb378aa9175 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -13,6 +13,7 @@
#include <linux/sched.h>
#include <linux/namei.h>
#include <linux/slab.h>
+#include <linux/xattr.h>

static bool fuse_use_readdirplus(struct inode *dir, struct dir_context *ctx)
{
@@ -1882,6 +1883,10 @@ static int fuse_setxattr(struct dentry *entry, const char *name,
if (fc->no_setxattr)
return -EOPNOTSUPP;

+ if (!(fc->flags & FUSE_PRIVILEGED_XATTRS) &&
+ strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) != 0)
+ return -EOPNOTSUPP;
+
req = fuse_get_req_nopages(fc);
if (IS_ERR(req))
return PTR_ERR(req);
@@ -1925,6 +1930,10 @@ static ssize_t fuse_getxattr(struct dentry *entry, const char *name,
if (fc->no_getxattr)
return -EOPNOTSUPP;

+ if (!(fc->flags & FUSE_PRIVILEGED_XATTRS) &&
+ strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) != 0)
+ return -EOPNOTSUPP;
+
req = fuse_get_req_nopages(fc);
if (IS_ERR(req))
return PTR_ERR(req);
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 81187ba04e4a..3ea4b4db9a79 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -46,6 +46,11 @@
doing the mount will be allowed to access the filesystem */
#define FUSE_ALLOW_OTHER (1 << 1)

+/** If the FUSE_PRIV_XATTRS flag is given, then xattrs outside the
+ user.* namespace are allowed. This option is only allowed for
+ system root. */
+#define FUSE_PRIVILEGED_XATTRS (1 << 2)
+
/** Number of page pointers embedded in fuse_req */
#define FUSE_REQ_INLINE_PAGES 1

diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index b88b5a780228..5e00a6a76049 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -493,6 +493,7 @@ enum {
OPT_ALLOW_OTHER,
OPT_MAX_READ,
OPT_BLKSIZE,
+ OPT_PRIVILEGED_XATTRS,
OPT_ERR
};

@@ -505,6 +506,7 @@ static const match_table_t tokens = {
{OPT_ALLOW_OTHER, "allow_other"},
{OPT_MAX_READ, "max_read=%u"},
{OPT_BLKSIZE, "blksize=%u"},
+ {OPT_PRIVILEGED_XATTRS, "privileged_xattrs"},
{OPT_ERR, NULL}
};

@@ -540,35 +542,35 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d, int is_bdev)
switch (token) {
case OPT_FD:
if (match_int(&args[0], &value))
- return 0;
+ return -EINVAL;
d->fd = value;
d->fd_present = 1;
break;

case OPT_ROOTMODE:
if (match_octal(&args[0], &value))
- return 0;
+ return -EINVAL;
if (!fuse_valid_type(value))
- return 0;
+ return -EINVAL;
d->rootmode = value;
d->rootmode_present = 1;
break;

case OPT_USER_ID:
if (fuse_match_uint(&args[0], &uv))
- return 0;
+ return -EINVAL;
d->user_id = make_kuid(current_user_ns(), uv);
if (!uid_valid(d->user_id))
- return 0;
+ return -EINVAL;
d->user_id_present = 1;
break;

case OPT_GROUP_ID:
if (fuse_match_uint(&args[0], &uv))
- return 0;
+ return -EINVAL;
d->group_id = make_kgid(current_user_ns(), uv);
if (!gid_valid(d->group_id))
- return 0;
+ return -EINVAL;
d->group_id_present = 1;
break;

@@ -582,26 +584,32 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d, int is_bdev)

case OPT_MAX_READ:
if (match_int(&args[0], &value))
- return 0;
+ return -EINVAL;
d->max_read = value;
break;

case OPT_BLKSIZE:
if (!is_bdev || match_int(&args[0], &value))
- return 0;
+ return -EINVAL;
d->blksize = value;
break;

+ case OPT_PRIVILEGED_XATTRS:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ d->flags |= FUSE_PRIVILEGED_XATTRS;
+ break;
+
default:
- return 0;
+ return -EINVAL;
}
}

if (!d->fd_present || !d->rootmode_present ||
!d->user_id_present || !d->group_id_present)
- return 0;
+ return -EINVAL;

- return 1;
+ return 0;
}

static int fuse_show_options(struct seq_file *m, struct dentry *root)
@@ -617,6 +625,8 @@ static int fuse_show_options(struct seq_file *m, struct dentry *root)
seq_puts(m, ",default_permissions");
if (fc->flags & FUSE_ALLOW_OTHER)
seq_puts(m, ",allow_other");
+ if (fc->flags & FUSE_PRIVILEGED_XATTRS)
+ seq_puts(m, ",privileged_xattrs");
if (fc->max_read != ~0)
seq_printf(m, ",max_read=%u", fc->max_read);
if (sb->s_bdev && sb->s_blocksize != FUSE_DEFAULT_BLKSIZE)
@@ -1058,7 +1068,8 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent)

sb->s_flags &= ~(MS_NOSEC | MS_I_VERSION);

- if (!parse_fuse_opt(data, &d, is_bdev))
+ err = parse_fuse_opt(data, &d, is_bdev);
+ if (err)
goto err;

if (is_bdev) {
--
2.1.0

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/