Btrfs: allow subvol deletion by unprivileged user with -o user_subvol_rm_allowed
Add a mount option user_subvol_rm_allowed that allows users to delete a (potentially non-empty!) subvol when they would otherwise we allowed to do an rmdir(2). We duplicate the may_delete() checks from the core VFS code to implement identical security checks (minus the directory size check). We additionally require that the user has write+exec permission on the subvol root inode. Signed-off-by: Sage Weil <sage@newdream.net> Signed-off-by: Chris Mason <chris.mason@oracle.com>
This commit is contained in:
parent
531cb13f1e
commit
4260f7c751
3 changed files with 116 additions and 5 deletions
|
@ -1234,6 +1234,7 @@ struct btrfs_root {
|
||||||
#define BTRFS_MOUNT_FORCE_COMPRESS (1 << 11)
|
#define BTRFS_MOUNT_FORCE_COMPRESS (1 << 11)
|
||||||
#define BTRFS_MOUNT_SPACE_CACHE (1 << 12)
|
#define BTRFS_MOUNT_SPACE_CACHE (1 << 12)
|
||||||
#define BTRFS_MOUNT_CLEAR_CACHE (1 << 13)
|
#define BTRFS_MOUNT_CLEAR_CACHE (1 << 13)
|
||||||
|
#define BTRFS_MOUNT_USER_SUBVOL_RM_ALLOWED (1 << 14)
|
||||||
|
|
||||||
#define btrfs_clear_opt(o, opt) ((o) &= ~BTRFS_MOUNT_##opt)
|
#define btrfs_clear_opt(o, opt) ((o) &= ~BTRFS_MOUNT_##opt)
|
||||||
#define btrfs_set_opt(o, opt) ((o) |= BTRFS_MOUNT_##opt)
|
#define btrfs_set_opt(o, opt) ((o) |= BTRFS_MOUNT_##opt)
|
||||||
|
|
115
fs/btrfs/ioctl.c
115
fs/btrfs/ioctl.c
|
@ -409,6 +409,76 @@ fail:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* copy of check_sticky in fs/namei.c()
|
||||||
|
* It's inline, so penalty for filesystems that don't use sticky bit is
|
||||||
|
* minimal.
|
||||||
|
*/
|
||||||
|
static inline int btrfs_check_sticky(struct inode *dir, struct inode *inode)
|
||||||
|
{
|
||||||
|
uid_t fsuid = current_fsuid();
|
||||||
|
|
||||||
|
if (!(dir->i_mode & S_ISVTX))
|
||||||
|
return 0;
|
||||||
|
if (inode->i_uid == fsuid)
|
||||||
|
return 0;
|
||||||
|
if (dir->i_uid == fsuid)
|
||||||
|
return 0;
|
||||||
|
return !capable(CAP_FOWNER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* copy of may_delete in fs/namei.c()
|
||||||
|
* Check whether we can remove a link victim from directory dir, check
|
||||||
|
* whether the type of victim is right.
|
||||||
|
* 1. We can't do it if dir is read-only (done in permission())
|
||||||
|
* 2. We should have write and exec permissions on dir
|
||||||
|
* 3. We can't remove anything from append-only dir
|
||||||
|
* 4. We can't do anything with immutable dir (done in permission())
|
||||||
|
* 5. If the sticky bit on dir is set we should either
|
||||||
|
* a. be owner of dir, or
|
||||||
|
* b. be owner of victim, or
|
||||||
|
* c. have CAP_FOWNER capability
|
||||||
|
* 6. If the victim is append-only or immutable we can't do antyhing with
|
||||||
|
* links pointing to it.
|
||||||
|
* 7. If we were asked to remove a directory and victim isn't one - ENOTDIR.
|
||||||
|
* 8. If we were asked to remove a non-directory and victim isn't one - EISDIR.
|
||||||
|
* 9. We can't remove a root or mountpoint.
|
||||||
|
* 10. We don't allow removal of NFS sillyrenamed files; it's handled by
|
||||||
|
* nfs_async_unlink().
|
||||||
|
*/
|
||||||
|
|
||||||
|
static int btrfs_may_delete(struct inode *dir,struct dentry *victim,int isdir)
|
||||||
|
{
|
||||||
|
int error;
|
||||||
|
|
||||||
|
if (!victim->d_inode)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
BUG_ON(victim->d_parent->d_inode != dir);
|
||||||
|
audit_inode_child(victim, dir);
|
||||||
|
|
||||||
|
error = inode_permission(dir, MAY_WRITE | MAY_EXEC);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
if (IS_APPEND(dir))
|
||||||
|
return -EPERM;
|
||||||
|
if (btrfs_check_sticky(dir, victim->d_inode)||
|
||||||
|
IS_APPEND(victim->d_inode)||
|
||||||
|
IS_IMMUTABLE(victim->d_inode) || IS_SWAPFILE(victim->d_inode))
|
||||||
|
return -EPERM;
|
||||||
|
if (isdir) {
|
||||||
|
if (!S_ISDIR(victim->d_inode->i_mode))
|
||||||
|
return -ENOTDIR;
|
||||||
|
if (IS_ROOT(victim))
|
||||||
|
return -EBUSY;
|
||||||
|
} else if (S_ISDIR(victim->d_inode->i_mode))
|
||||||
|
return -EISDIR;
|
||||||
|
if (IS_DEADDIR(dir))
|
||||||
|
return -ENOENT;
|
||||||
|
if (victim->d_flags & DCACHE_NFSFS_RENAMED)
|
||||||
|
return -EBUSY;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* copy of may_create in fs/namei.c() */
|
/* copy of may_create in fs/namei.c() */
|
||||||
static inline int btrfs_may_create(struct inode *dir, struct dentry *child)
|
static inline int btrfs_may_create(struct inode *dir, struct dentry *child)
|
||||||
{
|
{
|
||||||
|
@ -1274,9 +1344,6 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
|
||||||
int ret;
|
int ret;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
|
||||||
if (!capable(CAP_SYS_ADMIN))
|
|
||||||
return -EPERM;
|
|
||||||
|
|
||||||
vol_args = memdup_user(arg, sizeof(*vol_args));
|
vol_args = memdup_user(arg, sizeof(*vol_args));
|
||||||
if (IS_ERR(vol_args))
|
if (IS_ERR(vol_args))
|
||||||
return PTR_ERR(vol_args);
|
return PTR_ERR(vol_args);
|
||||||
|
@ -1306,13 +1373,51 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
|
||||||
}
|
}
|
||||||
|
|
||||||
inode = dentry->d_inode;
|
inode = dentry->d_inode;
|
||||||
|
dest = BTRFS_I(inode)->root;
|
||||||
|
if (!capable(CAP_SYS_ADMIN)){
|
||||||
|
/*
|
||||||
|
* Regular user. Only allow this with a special mount
|
||||||
|
* option, when the user has write+exec access to the
|
||||||
|
* subvol root, and when rmdir(2) would have been
|
||||||
|
* allowed.
|
||||||
|
*
|
||||||
|
* Note that this is _not_ check that the subvol is
|
||||||
|
* empty or doesn't contain data that we wouldn't
|
||||||
|
* otherwise be able to delete.
|
||||||
|
*
|
||||||
|
* Users who want to delete empty subvols should try
|
||||||
|
* rmdir(2).
|
||||||
|
*/
|
||||||
|
err = -EPERM;
|
||||||
|
if (!btrfs_test_opt(root, USER_SUBVOL_RM_ALLOWED))
|
||||||
|
goto out_dput;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Do not allow deletion if the parent dir is the same
|
||||||
|
* as the dir to be deleted. That means the ioctl
|
||||||
|
* must be called on the dentry referencing the root
|
||||||
|
* of the subvol, not a random directory contained
|
||||||
|
* within it.
|
||||||
|
*/
|
||||||
|
err = -EINVAL;
|
||||||
|
if (root == dest)
|
||||||
|
goto out_dput;
|
||||||
|
|
||||||
|
err = inode_permission(inode, MAY_WRITE | MAY_EXEC);
|
||||||
|
if (err)
|
||||||
|
goto out_dput;
|
||||||
|
|
||||||
|
/* check if subvolume may be deleted by a non-root user */
|
||||||
|
err = btrfs_may_delete(dir, dentry, 1);
|
||||||
|
if (err)
|
||||||
|
goto out_dput;
|
||||||
|
}
|
||||||
|
|
||||||
if (inode->i_ino != BTRFS_FIRST_FREE_OBJECTID) {
|
if (inode->i_ino != BTRFS_FIRST_FREE_OBJECTID) {
|
||||||
err = -EINVAL;
|
err = -EINVAL;
|
||||||
goto out_dput;
|
goto out_dput;
|
||||||
}
|
}
|
||||||
|
|
||||||
dest = BTRFS_I(inode)->root;
|
|
||||||
|
|
||||||
mutex_lock(&inode->i_mutex);
|
mutex_lock(&inode->i_mutex);
|
||||||
err = d_invalidate(dentry);
|
err = d_invalidate(dentry);
|
||||||
if (err)
|
if (err)
|
||||||
|
|
|
@ -71,6 +71,7 @@ enum {
|
||||||
Opt_nossd, Opt_ssd_spread, Opt_thread_pool, Opt_noacl, Opt_compress,
|
Opt_nossd, Opt_ssd_spread, Opt_thread_pool, Opt_noacl, Opt_compress,
|
||||||
Opt_compress_force, Opt_notreelog, Opt_ratio, Opt_flushoncommit,
|
Opt_compress_force, Opt_notreelog, Opt_ratio, Opt_flushoncommit,
|
||||||
Opt_discard, Opt_space_cache, Opt_clear_cache, Opt_err,
|
Opt_discard, Opt_space_cache, Opt_clear_cache, Opt_err,
|
||||||
|
Opt_user_subvol_rm_allowed,
|
||||||
};
|
};
|
||||||
|
|
||||||
static match_table_t tokens = {
|
static match_table_t tokens = {
|
||||||
|
@ -96,6 +97,7 @@ static match_table_t tokens = {
|
||||||
{Opt_discard, "discard"},
|
{Opt_discard, "discard"},
|
||||||
{Opt_space_cache, "space_cache"},
|
{Opt_space_cache, "space_cache"},
|
||||||
{Opt_clear_cache, "clear_cache"},
|
{Opt_clear_cache, "clear_cache"},
|
||||||
|
{Opt_user_subvol_rm_allowed, "user_subvol_rm_allowed"},
|
||||||
{Opt_err, NULL},
|
{Opt_err, NULL},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -246,6 +248,9 @@ int btrfs_parse_options(struct btrfs_root *root, char *options)
|
||||||
printk(KERN_INFO "btrfs: force clearing of disk cache\n");
|
printk(KERN_INFO "btrfs: force clearing of disk cache\n");
|
||||||
btrfs_set_opt(info->mount_opt, CLEAR_CACHE);
|
btrfs_set_opt(info->mount_opt, CLEAR_CACHE);
|
||||||
break;
|
break;
|
||||||
|
case Opt_user_subvol_rm_allowed:
|
||||||
|
btrfs_set_opt(info->mount_opt, USER_SUBVOL_RM_ALLOWED);
|
||||||
|
break;
|
||||||
case Opt_err:
|
case Opt_err:
|
||||||
printk(KERN_INFO "btrfs: unrecognized mount option "
|
printk(KERN_INFO "btrfs: unrecognized mount option "
|
||||||
"'%s'\n", p);
|
"'%s'\n", p);
|
||||||
|
|
Loading…
Reference in a new issue