From 201d2629499512a2837e2f12c06aae05937d736b Mon Sep 17 00:00:00 2001 From: shodanshok Date: Thu, 13 Mar 2025 18:54:14 +0100 Subject: [PATCH] Add receive:append permission for limited receive Force receive (zfs receive -F) can rollback or destroy snapshots and file systems that do not exist on the sending side (see zfs-receive man page). This means an user having the receive permission can effectively delete data on receiving side, even if such user does not have explicit rollback or destroy permissions. This patch adds the receive:append permission, which only permits limited, non-forced receive. Behavior for users with full receive permission is not changed in any way. Fixes #16943 Reviewed-by: Ameer Hamza Reviewed-by: Alexander Motin Signed-off-by: Gionatan Danti Closes #17015 --- cmd/zfs/zfs_main.c | 1 + include/sys/dsl_deleg.h | 1 + man/man8/zfs-allow.8 | 3 +- module/zcommon/zfs_deleg.c | 1 + module/zfs/zfs_ioctl.c | 13 +++++- .../delegate/delegate_common.kshlib | 42 +++++++++++++++++++ .../functional/delegate/zfs_allow_010_pos.ksh | 5 ++- 7 files changed, 62 insertions(+), 4 deletions(-) diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 73ccf72d2..d8cbf8f5a 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -5292,6 +5292,7 @@ zfs_do_receive(int argc, char **argv) #define ZFS_DELEG_PERM_SHARE "share" #define ZFS_DELEG_PERM_SEND "send" #define ZFS_DELEG_PERM_RECEIVE "receive" +#define ZFS_DELEG_PERM_RECEIVE_APPEND "receive:append" #define ZFS_DELEG_PERM_ALLOW "allow" #define ZFS_DELEG_PERM_USERPROP "userprop" #define ZFS_DELEG_PERM_VSCAN "vscan" /* ??? */ diff --git a/include/sys/dsl_deleg.h b/include/sys/dsl_deleg.h index d6abac90b..0761b0745 100644 --- a/include/sys/dsl_deleg.h +++ b/include/sys/dsl_deleg.h @@ -46,6 +46,7 @@ extern "C" { #define ZFS_DELEG_PERM_SHARE "share" #define ZFS_DELEG_PERM_SEND "send" #define ZFS_DELEG_PERM_RECEIVE "receive" +#define ZFS_DELEG_PERM_RECEIVE_APPEND "receive:append" #define ZFS_DELEG_PERM_ALLOW "allow" #define ZFS_DELEG_PERM_USERPROP "userprop" #define ZFS_DELEG_PERM_VSCAN "vscan" diff --git a/man/man8/zfs-allow.8 b/man/man8/zfs-allow.8 index d26984317..3b65befda 100644 --- a/man/man8/zfs-allow.8 +++ b/man/man8/zfs-allow.8 @@ -207,7 +207,7 @@ load-key subcommand Allows loading and unloading of encryption key (see \fBzfs l change-key subcommand Allows changing an encryption key via \fBzfs change-key\fR. mount subcommand Allows mounting/umounting ZFS datasets promote subcommand Must also have the \fBmount\fR and \fBpromote\fR ability in the origin file system -receive subcommand Must also have the \fBmount\fR and \fBcreate\fR ability +receive subcommand Must also have the \fBmount\fR and \fBcreate\fR ability, required for \fBzfs receive -F\fR (see also \fBreceive:append\fR for limited, non forced receive) release subcommand Allows releasing a user hold which might destroy the snapshot rename subcommand Must also have the \fBmount\fR and \fBcreate\fR ability in the new parent rollback subcommand Must also have the \fBmount\fR ability @@ -215,6 +215,7 @@ send subcommand share subcommand Allows sharing file systems over NFS or SMB protocols snapshot subcommand Must also have the \fBmount\fR ability +receive:append other Must also have the \fBmount\fR and \fBcreate\fR ability, limited receive ability (can not do receive -F) groupquota other Allows accessing any \fBgroupquota@\fI…\fR property groupobjquota other Allows accessing any \fBgroupobjquota@\fI…\fR property groupused other Allows reading any \fBgroupused@\fI…\fR property diff --git a/module/zcommon/zfs_deleg.c b/module/zcommon/zfs_deleg.c index f977c7611..05b71a964 100644 --- a/module/zcommon/zfs_deleg.c +++ b/module/zcommon/zfs_deleg.c @@ -52,6 +52,7 @@ const zfs_deleg_perm_tab_t zfs_deleg_perm_tab[] = { {ZFS_DELEG_PERM_MOUNT}, {ZFS_DELEG_PERM_PROMOTE}, {ZFS_DELEG_PERM_RECEIVE}, + {ZFS_DELEG_PERM_RECEIVE_APPEND}, {ZFS_DELEG_PERM_RENAME}, {ZFS_DELEG_PERM_ROLLBACK}, {ZFS_DELEG_PERM_SNAPSHOT}, diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index b1b0ae544..2d1ba3c67 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -900,9 +900,18 @@ zfs_secpolicy_recv(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr) (void) innvl; int error; + /* + * zfs receive -F requires full receive permission, + * otherwise receive:append permission is enough + */ if ((error = zfs_secpolicy_write_perms(zc->zc_name, - ZFS_DELEG_PERM_RECEIVE, cr)) != 0) - return (error); + ZFS_DELEG_PERM_RECEIVE, cr)) != 0) { + if (zc->zc_guid || nvlist_exists(innvl, "force")) + return (error); + if ((error = zfs_secpolicy_write_perms(zc->zc_name, + ZFS_DELEG_PERM_RECEIVE_APPEND, cr)) != 0) + return (error); + } if ((error = zfs_secpolicy_write_perms(zc->zc_name, ZFS_DELEG_PERM_MOUNT, cr)) != 0) diff --git a/tests/zfs-tests/tests/functional/delegate/delegate_common.kshlib b/tests/zfs-tests/tests/functional/delegate/delegate_common.kshlib index 5ddb6ca2d..8e628b8e4 100644 --- a/tests/zfs-tests/tests/functional/delegate/delegate_common.kshlib +++ b/tests/zfs-tests/tests/functional/delegate/delegate_common.kshlib @@ -256,6 +256,9 @@ function check_fs_perm receive) verify_fs_receive $user $perm $fs ;; + receive:append) + verify_fs_receive_append $user $perm $fs + ;; *) common_perm $user $perm $fs ;; @@ -425,6 +428,45 @@ function verify_fs_receive return 0 } +function verify_fs_receive_append +{ + typeset user=$1 + typeset perm=$2 + typeset fs=$3 + + typeset dtst + typeset stamp=${perm}.${user}.$RANDOM + typeset newfs=$fs/newfs.$stamp + typeset bak_user=$TEST_BASE_DIR/bak.$user.$stamp + + log_must zfs create $newfs + typeset dtst="$newfs" + + typeset dtstsnap=$dtst@snap.$stamp + log_must zfs snapshot $dtstsnap + + log_must eval "zfs send $dtstsnap > $bak_user" + log_must_busy zfs destroy -rf $dtst + + log_must zfs allow $user create,mount,canmount $fs + user_run $user eval "zfs receive -o canmount=off -F $dtst < $bak_user" + log_must zfs unallow $user create,mount,canmount $fs + if datasetexists $dtstsnap ; then + return 1 + fi + + log_must zfs allow $user create,mount,canmount $fs + user_run $user eval "zfs receive -o canmount=off $dtst < $bak_user" + log_must zfs unallow $user create,mount,canmount $fs + if ! datasetexists $dtstsnap ; then + return 1 + fi + + rm -rf $bak_user + + return 0 +} + function verify_userprop { typeset user=$1 diff --git a/tests/zfs-tests/tests/functional/delegate/zfs_allow_010_pos.ksh b/tests/zfs-tests/tests/functional/delegate/zfs_allow_010_pos.ksh index 549928697..22406c72f 100755 --- a/tests/zfs-tests/tests/functional/delegate/zfs_allow_010_pos.ksh +++ b/tests/zfs-tests/tests/functional/delegate/zfs_allow_010_pos.ksh @@ -86,7 +86,8 @@ set -A perms create true false \ clone true true \ promote true true \ xattr true false \ - receive true false + receive true false \ + receive:append true false elif is_freebsd; then # Results in Results in @@ -126,6 +127,7 @@ set -A perms create true false \ rename true true \ promote true true \ receive true false \ + receive:append true false \ destroy true true else @@ -160,6 +162,7 @@ set -A perms create true false \ zoned true false \ xattr true false \ receive true false \ + receive:append true false \ destroy true true if is_global_zone; then