diff options
| author | Sage Weil <sage@newdream.net> | 2010-10-29 15:46:43 -0400 |
|---|---|---|
| committer | Chris Mason <chris.mason@oracle.com> | 2010-10-29 21:42:10 -0400 |
| commit | 4260f7c7516f4c209cf0ca34fda99cc9a0847772 (patch) | |
| tree | 7feb5ab81d074b17a7c5b12bcc019c2e1bb010e2 /fs/btrfs | |
| parent | 531cb13f1e417c060b54f979e1659ecd69bea650 (diff) | |
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>
Diffstat (limited to 'fs/btrfs')
| -rw-r--r-- | fs/btrfs/ctree.h | 1 | ||||
| -rw-r--r-- | fs/btrfs/ioctl.c | 115 | ||||
| -rw-r--r-- | fs/btrfs/super.c | 5 |
3 files changed, 116 insertions, 5 deletions
diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h index e5d66b13c175..8db9234f6b41 100644 --- a/fs/btrfs/ctree.h +++ b/fs/btrfs/ctree.h | |||
| @@ -1234,6 +1234,7 @@ struct btrfs_root { | |||
| 1234 | #define BTRFS_MOUNT_FORCE_COMPRESS (1 << 11) | 1234 | #define BTRFS_MOUNT_FORCE_COMPRESS (1 << 11) |
| 1235 | #define BTRFS_MOUNT_SPACE_CACHE (1 << 12) | 1235 | #define BTRFS_MOUNT_SPACE_CACHE (1 << 12) |
| 1236 | #define BTRFS_MOUNT_CLEAR_CACHE (1 << 13) | 1236 | #define BTRFS_MOUNT_CLEAR_CACHE (1 << 13) |
| 1237 | #define BTRFS_MOUNT_USER_SUBVOL_RM_ALLOWED (1 << 14) | ||
| 1237 | 1238 | ||
| 1238 | #define btrfs_clear_opt(o, opt) ((o) &= ~BTRFS_MOUNT_##opt) | 1239 | #define btrfs_clear_opt(o, opt) ((o) &= ~BTRFS_MOUNT_##opt) |
| 1239 | #define btrfs_set_opt(o, opt) ((o) |= BTRFS_MOUNT_##opt) | 1240 | #define btrfs_set_opt(o, opt) ((o) |= BTRFS_MOUNT_##opt) |
diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index fdd88f2f1ece..463d91b4dd3a 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c | |||
| @@ -409,6 +409,76 @@ fail: | |||
| 409 | return ret; | 409 | return ret; |
| 410 | } | 410 | } |
| 411 | 411 | ||
| 412 | /* copy of check_sticky in fs/namei.c() | ||
| 413 | * It's inline, so penalty for filesystems that don't use sticky bit is | ||
| 414 | * minimal. | ||
| 415 | */ | ||
| 416 | static inline int btrfs_check_sticky(struct inode *dir, struct inode *inode) | ||
| 417 | { | ||
| 418 | uid_t fsuid = current_fsuid(); | ||
| 419 | |||
| 420 | if (!(dir->i_mode & S_ISVTX)) | ||
| 421 | return 0; | ||
| 422 | if (inode->i_uid == fsuid) | ||
| 423 | return 0; | ||
| 424 | if (dir->i_uid == fsuid) | ||
| 425 | return 0; | ||
| 426 | return !capable(CAP_FOWNER); | ||
| 427 | } | ||
| 428 | |||
| 429 | /* copy of may_delete in fs/namei.c() | ||
| 430 | * Check whether we can remove a link victim from directory dir, check | ||
| 431 | * whether the type of victim is right. | ||
| 432 | * 1. We can't do it if dir is read-only (done in permission()) | ||
| 433 | * 2. We should have write and exec permissions on dir | ||
| 434 | * 3. We can't remove anything from append-only dir | ||
| 435 | * 4. We can't do anything with immutable dir (done in permission()) | ||
| 436 | * 5. If the sticky bit on dir is set we should either | ||
| 437 | * a. be owner of dir, or | ||
| 438 | * b. be owner of victim, or | ||
| 439 | * c. have CAP_FOWNER capability | ||
| 440 | * 6. If the victim is append-only or immutable we can't do antyhing with | ||
| 441 | * links pointing to it. | ||
| 442 | * 7. If we were asked to remove a directory and victim isn't one - ENOTDIR. | ||
| 443 | * 8. If we were asked to remove a non-directory and victim isn't one - EISDIR. | ||
| 444 | * 9. We can't remove a root or mountpoint. | ||
| 445 | * 10. We don't allow removal of NFS sillyrenamed files; it's handled by | ||
| 446 | * nfs_async_unlink(). | ||
| 447 | */ | ||
| 448 | |||
| 449 | static int btrfs_may_delete(struct inode *dir,struct dentry *victim,int isdir) | ||
| 450 | { | ||
| 451 | int error; | ||
| 452 | |||
| 453 | if (!victim->d_inode) | ||
| 454 | return -ENOENT; | ||
| 455 | |||
| 456 | BUG_ON(victim->d_parent->d_inode != dir); | ||
| 457 | audit_inode_child(victim, dir); | ||
| 458 | |||
| 459 | error = inode_permission(dir, MAY_WRITE | MAY_EXEC); | ||
| 460 | if (error) | ||
| 461 | return error; | ||
| 462 | if (IS_APPEND(dir)) | ||
| 463 | return -EPERM; | ||
| 464 | if (btrfs_check_sticky(dir, victim->d_inode)|| | ||
| 465 | IS_APPEND(victim->d_inode)|| | ||
| 466 | IS_IMMUTABLE(victim->d_inode) || IS_SWAPFILE(victim->d_inode)) | ||
| 467 | return -EPERM; | ||
| 468 | if (isdir) { | ||
| 469 | if (!S_ISDIR(victim->d_inode->i_mode)) | ||
| 470 | return -ENOTDIR; | ||
| 471 | if (IS_ROOT(victim)) | ||
| 472 | return -EBUSY; | ||
| 473 | } else if (S_ISDIR(victim->d_inode->i_mode)) | ||
| 474 | return -EISDIR; | ||
| 475 | if (IS_DEADDIR(dir)) | ||
| 476 | return -ENOENT; | ||
| 477 | if (victim->d_flags & DCACHE_NFSFS_RENAMED) | ||
| 478 | return -EBUSY; | ||
| 479 | return 0; | ||
| 480 | } | ||
| 481 | |||
| 412 | /* copy of may_create in fs/namei.c() */ | 482 | /* copy of may_create in fs/namei.c() */ |
| 413 | static inline int btrfs_may_create(struct inode *dir, struct dentry *child) | 483 | static inline int btrfs_may_create(struct inode *dir, struct dentry *child) |
| 414 | { | 484 | { |
| @@ -1274,9 +1344,6 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file, | |||
| 1274 | int ret; | 1344 | int ret; |
| 1275 | int err = 0; | 1345 | int err = 0; |
| 1276 | 1346 | ||
| 1277 | if (!capable(CAP_SYS_ADMIN)) | ||
| 1278 | return -EPERM; | ||
| 1279 | |||
| 1280 | vol_args = memdup_user(arg, sizeof(*vol_args)); | 1347 | vol_args = memdup_user(arg, sizeof(*vol_args)); |
| 1281 | if (IS_ERR(vol_args)) | 1348 | if (IS_ERR(vol_args)) |
| 1282 | return PTR_ERR(vol_args); | 1349 | return PTR_ERR(vol_args); |
| @@ -1306,13 +1373,51 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file, | |||
| 1306 | } | 1373 | } |
| 1307 | 1374 | ||
| 1308 | inode = dentry->d_inode; | 1375 | inode = dentry->d_inode; |
| 1376 | dest = BTRFS_I(inode)->root; | ||
| 1377 | if (!capable(CAP_SYS_ADMIN)){ | ||
| 1378 | /* | ||
| 1379 | * Regular user. Only allow this with a special mount | ||
| 1380 | * option, when the user has write+exec access to the | ||
| 1381 | * subvol root, and when rmdir(2) would have been | ||
| 1382 | * allowed. | ||
| 1383 | * | ||
| 1384 | * Note that this is _not_ check that the subvol is | ||
| 1385 | * empty or doesn't contain data that we wouldn't | ||
| 1386 | * otherwise be able to delete. | ||
| 1387 | * | ||
| 1388 | * Users who want to delete empty subvols should try | ||
| 1389 | * rmdir(2). | ||
| 1390 | */ | ||
| 1391 | err = -EPERM; | ||
| 1392 | if (!btrfs_test_opt(root, USER_SUBVOL_RM_ALLOWED)) | ||
| 1393 | goto out_dput; | ||
| 1394 | |||
| 1395 | /* | ||
| 1396 | * Do not allow deletion if the parent dir is the same | ||
| 1397 | * as the dir to be deleted. That means the ioctl | ||
| 1398 | * must be called on the dentry referencing the root | ||
| 1399 | * of the subvol, not a random directory contained | ||
| 1400 | * within it. | ||
| 1401 | */ | ||
| 1402 | err = -EINVAL; | ||
| 1403 | if (root == dest) | ||
| 1404 | goto out_dput; | ||
| 1405 | |||
| 1406 | err = inode_permission(inode, MAY_WRITE | MAY_EXEC); | ||
| 1407 | if (err) | ||
| 1408 | goto out_dput; | ||
| 1409 | |||
| 1410 | /* check if subvolume may be deleted by a non-root user */ | ||
| 1411 | err = btrfs_may_delete(dir, dentry, 1); | ||
| 1412 | if (err) | ||
| 1413 | goto out_dput; | ||
| 1414 | } | ||
| 1415 | |||
| 1309 | if (inode->i_ino != BTRFS_FIRST_FREE_OBJECTID) { | 1416 | if (inode->i_ino != BTRFS_FIRST_FREE_OBJECTID) { |
| 1310 | err = -EINVAL; | 1417 | err = -EINVAL; |
| 1311 | goto out_dput; | 1418 | goto out_dput; |
| 1312 | } | 1419 | } |
| 1313 | 1420 | ||
| 1314 | dest = BTRFS_I(inode)->root; | ||
| 1315 | |||
| 1316 | mutex_lock(&inode->i_mutex); | 1421 | mutex_lock(&inode->i_mutex); |
| 1317 | err = d_invalidate(dentry); | 1422 | err = d_invalidate(dentry); |
| 1318 | if (err) | 1423 | if (err) |
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c index 0002e6d1a16f..718b10de2049 100644 --- a/fs/btrfs/super.c +++ b/fs/btrfs/super.c | |||
| @@ -71,6 +71,7 @@ enum { | |||
| 71 | Opt_nossd, Opt_ssd_spread, Opt_thread_pool, Opt_noacl, Opt_compress, | 71 | Opt_nossd, Opt_ssd_spread, Opt_thread_pool, Opt_noacl, Opt_compress, |
| 72 | Opt_compress_force, Opt_notreelog, Opt_ratio, Opt_flushoncommit, | 72 | Opt_compress_force, Opt_notreelog, Opt_ratio, Opt_flushoncommit, |
| 73 | Opt_discard, Opt_space_cache, Opt_clear_cache, Opt_err, | 73 | Opt_discard, Opt_space_cache, Opt_clear_cache, Opt_err, |
| 74 | Opt_user_subvol_rm_allowed, | ||
| 74 | }; | 75 | }; |
| 75 | 76 | ||
| 76 | static match_table_t tokens = { | 77 | static match_table_t tokens = { |
| @@ -96,6 +97,7 @@ static match_table_t tokens = { | |||
| 96 | {Opt_discard, "discard"}, | 97 | {Opt_discard, "discard"}, |
| 97 | {Opt_space_cache, "space_cache"}, | 98 | {Opt_space_cache, "space_cache"}, |
| 98 | {Opt_clear_cache, "clear_cache"}, | 99 | {Opt_clear_cache, "clear_cache"}, |
| 100 | {Opt_user_subvol_rm_allowed, "user_subvol_rm_allowed"}, | ||
| 99 | {Opt_err, NULL}, | 101 | {Opt_err, NULL}, |
| 100 | }; | 102 | }; |
| 101 | 103 | ||
| @@ -246,6 +248,9 @@ int btrfs_parse_options(struct btrfs_root *root, char *options) | |||
| 246 | printk(KERN_INFO "btrfs: force clearing of disk cache\n"); | 248 | printk(KERN_INFO "btrfs: force clearing of disk cache\n"); |
| 247 | btrfs_set_opt(info->mount_opt, CLEAR_CACHE); | 249 | btrfs_set_opt(info->mount_opt, CLEAR_CACHE); |
| 248 | break; | 250 | break; |
| 251 | case Opt_user_subvol_rm_allowed: | ||
| 252 | btrfs_set_opt(info->mount_opt, USER_SUBVOL_RM_ALLOWED); | ||
| 253 | break; | ||
| 249 | case Opt_err: | 254 | case Opt_err: |
| 250 | printk(KERN_INFO "btrfs: unrecognized mount option " | 255 | printk(KERN_INFO "btrfs: unrecognized mount option " |
| 251 | "'%s'\n", p); | 256 | "'%s'\n", p); |
