Commit 4260f7c7516f4c209cf0ca34fda99cc9a0847772
Committed by
Chris Mason
1 parent
531cb13f1e
Exists in
master
and in
7 other branches
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>
Showing 3 changed files with 116 additions and 5 deletions Side-by-side Diff
fs/btrfs/ctree.h
... | ... | @@ -1234,6 +1234,7 @@ |
1234 | 1234 | #define BTRFS_MOUNT_FORCE_COMPRESS (1 << 11) |
1235 | 1235 | #define BTRFS_MOUNT_SPACE_CACHE (1 << 12) |
1236 | 1236 | #define BTRFS_MOUNT_CLEAR_CACHE (1 << 13) |
1237 | +#define BTRFS_MOUNT_USER_SUBVOL_RM_ALLOWED (1 << 14) | |
1237 | 1238 | |
1238 | 1239 | #define btrfs_clear_opt(o, opt) ((o) &= ~BTRFS_MOUNT_##opt) |
1239 | 1240 | #define btrfs_set_opt(o, opt) ((o) |= BTRFS_MOUNT_##opt) |
fs/btrfs/ioctl.c
... | ... | @@ -409,6 +409,76 @@ |
409 | 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 | 482 | /* copy of may_create in fs/namei.c() */ |
413 | 483 | static inline int btrfs_may_create(struct inode *dir, struct dentry *child) |
414 | 484 | { |
... | ... | @@ -1274,9 +1344,6 @@ |
1274 | 1344 | int ret; |
1275 | 1345 | int err = 0; |
1276 | 1346 | |
1277 | - if (!capable(CAP_SYS_ADMIN)) | |
1278 | - return -EPERM; | |
1279 | - | |
1280 | 1347 | vol_args = memdup_user(arg, sizeof(*vol_args)); |
1281 | 1348 | if (IS_ERR(vol_args)) |
1282 | 1349 | return PTR_ERR(vol_args); |
1283 | 1350 | |
... | ... | @@ -1306,12 +1373,50 @@ |
1306 | 1373 | } |
1307 | 1374 | |
1308 | 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 | 1416 | if (inode->i_ino != BTRFS_FIRST_FREE_OBJECTID) { |
1310 | 1417 | err = -EINVAL; |
1311 | 1418 | goto out_dput; |
1312 | 1419 | } |
1313 | - | |
1314 | - dest = BTRFS_I(inode)->root; | |
1315 | 1420 | |
1316 | 1421 | mutex_lock(&inode->i_mutex); |
1317 | 1422 | err = d_invalidate(dentry); |
fs/btrfs/super.c
... | ... | @@ -71,6 +71,7 @@ |
71 | 71 | Opt_nossd, Opt_ssd_spread, Opt_thread_pool, Opt_noacl, Opt_compress, |
72 | 72 | Opt_compress_force, Opt_notreelog, Opt_ratio, Opt_flushoncommit, |
73 | 73 | Opt_discard, Opt_space_cache, Opt_clear_cache, Opt_err, |
74 | + Opt_user_subvol_rm_allowed, | |
74 | 75 | }; |
75 | 76 | |
76 | 77 | static match_table_t tokens = { |
... | ... | @@ -96,6 +97,7 @@ |
96 | 97 | {Opt_discard, "discard"}, |
97 | 98 | {Opt_space_cache, "space_cache"}, |
98 | 99 | {Opt_clear_cache, "clear_cache"}, |
100 | + {Opt_user_subvol_rm_allowed, "user_subvol_rm_allowed"}, | |
99 | 101 | {Opt_err, NULL}, |
100 | 102 | }; |
101 | 103 | |
... | ... | @@ -245,6 +247,9 @@ |
245 | 247 | case Opt_clear_cache: |
246 | 248 | printk(KERN_INFO "btrfs: force clearing of disk cache\n"); |
247 | 249 | btrfs_set_opt(info->mount_opt, CLEAR_CACHE); |
250 | + break; | |
251 | + case Opt_user_subvol_rm_allowed: | |
252 | + btrfs_set_opt(info->mount_opt, USER_SUBVOL_RM_ALLOWED); | |
248 | 253 | break; |
249 | 254 | case Opt_err: |
250 | 255 | printk(KERN_INFO "btrfs: unrecognized mount option " |