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 | |
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')
-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); |