From 7291fba1dfe34531883e4c56e3f26dd942f5c9f5 Mon Sep 17 00:00:00 2001 From: Gionatan Danti Date: Fri, 31 Jan 2025 22:07:30 +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 https://github.com/openzfs/zfs/issues/16943 Signed-off-by: Gionatan Danti --- 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 73ccf72d263c..d8cbf8f5aa4c 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 d6abac90bbcc..0761b0745065 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 d26984317c2e..3b65befda832 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 f977c761147d..05b71a9643a2 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 b1b0ae54460b..2d1ba3c67ed4 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 5ddb6ca2ddc8..8e628b8e4382 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 549928697edd..22406c72f82a 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