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