From 765d1f0644658d772addeb9b2dd5039ac43177ad Mon Sep 17 00:00:00 2001 From: Tom Caputi Date: Fri, 28 Jun 2019 15:38:37 -0400 Subject: [PATCH] Add 'zfs umount -u' for encrypted datasets This patch adds the ability for the user to unload keys for datasets as they are being unmounted. This is analogous to 'zfs mount -l'. Reviewed-by: Brian Behlendorf Reviewed-by: Alek Pinchuk Signed-off-by: Tom Caputi Closes: #8917 Closes: #8952 --- cmd/zfs/zfs_main.c | 13 +-- lib/libzfs/libzfs_mount.c | 28 ++++++- man/man8/zfs.8 | 13 +-- tests/runfiles/linux.run | 2 +- .../cli_root/zfs_unmount/Makefile.am | 3 +- .../zfs_unmount/zfs_unmount_unload_keys.ksh | 79 +++++++++++++++++++ 6 files changed, 125 insertions(+), 13 deletions(-) create mode 100755 tests/zfs-tests/tests/functional/cli_root/zfs_unmount/zfs_unmount_unload_keys.ksh diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 30942e1f0..bddf25c2d 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -315,7 +315,7 @@ get_usage(zfs_help_t idx) return (gettext("\tsnapshot [-r] [-o property=value] ... " "@ ...\n")); case HELP_UNMOUNT: - return (gettext("\tunmount [-f] " + return (gettext("\tunmount [-fu] " "<-a | filesystem|mountpoint>\n")); case HELP_UNSHARE: return (gettext("\tunshare " @@ -7015,13 +7015,16 @@ unshare_unmount(int op, int argc, char **argv) char sharesmb[ZFS_MAXPROPLEN]; /* check options */ - while ((c = getopt(argc, argv, op == OP_SHARE ? ":a" : "af")) != -1) { + while ((c = getopt(argc, argv, op == OP_SHARE ? ":a" : "afu")) != -1) { switch (c) { case 'a': do_all = 1; break; case 'f': - flags = MS_FORCE; + flags |= MS_FORCE; + break; + case 'u': + flags |= MS_CRYPT; break; case ':': (void) fprintf(stderr, gettext("missing argument for " @@ -7281,8 +7284,8 @@ unshare_unmount(int op, int argc, char **argv) } /* - * zfs unmount -a - * zfs unmount filesystem + * zfs unmount [-fu] -a + * zfs unmount [-fu] filesystem * * Unmount all filesystems, or a specific ZFS filesystem. */ diff --git a/lib/libzfs/libzfs_mount.c b/lib/libzfs/libzfs_mount.c index 39ca2be05..7497a7223 100644 --- a/lib/libzfs/libzfs_mount.c +++ b/lib/libzfs/libzfs_mount.c @@ -668,6 +668,7 @@ zfs_unmount(zfs_handle_t *zhp, const char *mountpoint, int flags) libzfs_handle_t *hdl = zhp->zfs_hdl; struct mnttab entry; char *mntpt = NULL; + boolean_t encroot, unmounted = B_FALSE; /* check to see if we need to unmount the filesystem */ if (mountpoint != NULL || ((zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM) && @@ -696,8 +697,33 @@ zfs_unmount(zfs_handle_t *zhp, const char *mountpoint, int flags) (void) zfs_shareall(zhp); return (-1); } + libzfs_mnttab_remove(hdl, zhp->zfs_name); free(mntpt); + unmounted = B_TRUE; + } + + /* + * If the MS_CRYPT flag is provided we must ensure we attempt to + * unload the dataset's key regardless of whether we did any work + * to unmount it. We only do this for encryption roots. + */ + if ((flags & MS_CRYPT) != 0 && + zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF) { + zfs_refresh_properties(zhp); + + if (zfs_crypto_get_encryption_root(zhp, &encroot, NULL) != 0 && + unmounted) { + (void) zfs_mount(zhp, NULL, 0); + return (-1); + } + + if (encroot && zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS) == + ZFS_KEYSTATUS_AVAILABLE && + zfs_crypto_unload_key(zhp) != 0) { + (void) zfs_mount(zhp, NULL, 0); + return (-1); + } } return (0); @@ -715,7 +741,7 @@ zfs_unmountall(zfs_handle_t *zhp, int flags) int ret; clp = changelist_gather(zhp, ZFS_PROP_MOUNTPOINT, - CL_GATHER_ITER_MOUNTED, 0); + CL_GATHER_ITER_MOUNTED, flags); if (clp == NULL) return (-1); diff --git a/man/man8/zfs.8 b/man/man8/zfs.8 index 0a23466b7..7058ec503 100644 --- a/man/man8/zfs.8 +++ b/man/man8/zfs.8 @@ -182,7 +182,7 @@ .Fl a | Ar filesystem .Nm .Cm unmount -.Op Fl f +.Op Fl fu .Fl a | Ar filesystem Ns | Ns Ar mountpoint .Nm .Cm share @@ -3462,7 +3462,7 @@ Attempt to force mounting of all filesystems, even those that couldn't normally .It Xo .Nm .Cm unmount -.Op Fl f +.Op Fl fu .Fl a | Ar filesystem Ns | Ns Ar mountpoint .Xc Unmounts currently mounted ZFS file systems. @@ -3470,13 +3470,16 @@ Unmounts currently mounted ZFS file systems. .It Fl a Unmount all available ZFS file systems. Invoked automatically as part of the shutdown process. +.It Fl f +Forcefully unmount the file system, even if it is currently in use. +.El +.It Fl u +Unload keys for any encryption roots unmounted by this command. +.El .It Ar filesystem Ns | Ns Ar mountpoint Unmount the specified filesystem. The command can also be given a path to a ZFS file system mount point on the system. -.It Fl f -Forcefully unmount the file system, even if it is currently in use. -.El .It Xo .Nm .Cm share diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run index 4a9c06065..e5dd044ac 100644 --- a/tests/runfiles/linux.run +++ b/tests/runfiles/linux.run @@ -275,7 +275,7 @@ tags = ['functional', 'cli_root', 'zfs_unload-key'] tests = ['zfs_unmount_001_pos', 'zfs_unmount_002_pos', 'zfs_unmount_003_pos', 'zfs_unmount_004_pos', 'zfs_unmount_005_pos', 'zfs_unmount_006_pos', 'zfs_unmount_007_neg', 'zfs_unmount_008_neg', 'zfs_unmount_009_pos', - 'zfs_unmount_all_001_pos', 'zfs_unmount_nested'] + 'zfs_unmount_all_001_pos', 'zfs_unmount_nested', 'zfs_unmount_unload_keys'] tags = ['functional', 'cli_root', 'zfs_unmount'] [tests/functional/cli_root/zfs_unshare] diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_unmount/Makefile.am b/tests/zfs-tests/tests/functional/cli_root/zfs_unmount/Makefile.am index 34cbb17ae..6507b094d 100644 --- a/tests/zfs-tests/tests/functional/cli_root/zfs_unmount/Makefile.am +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_unmount/Makefile.am @@ -12,7 +12,8 @@ dist_pkgdata_SCRIPTS = \ zfs_unmount_008_neg.ksh \ zfs_unmount_009_pos.ksh \ zfs_unmount_all_001_pos.ksh \ - zfs_unmount_nested.ksh + zfs_unmount_nested.ksh \ + zfs_unmount_unload_keys.ksh dist_pkgdata_DATA = \ zfs_unmount.cfg \ diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_unmount/zfs_unmount_unload_keys.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_unmount/zfs_unmount_unload_keys.ksh new file mode 100755 index 000000000..d6d0a7e9a --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_unmount/zfs_unmount_unload_keys.ksh @@ -0,0 +1,79 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2017 Datto, Inc. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/cli_root/zfs_unmount/zfs_unmount.kshlib +. $STF_SUITE/tests/functional/cli_root/zfs_load-key/zfs_load-key_common.kshlib + +# +# DESCRIPTION: +# "zfs unmount -u" should allow the user to unload their encryption +# keys while unmounting one or more datasets +# +# STRATEGY: +# 1. Create a hierarchy of encrypted datasets +# 2. Test that 'zfs unmount -u' unloads keys as it unmounts a dataset +# 3. Test that 'zfs unmount -u' unloads keys as it unmounts multiple datasets +# 4. Test that 'zfs unmount -u' returns an error if the key is still in +# use by a clone. +# + +verify_runnable "both" + +function cleanup +{ + datasetexists $TESTPOOL/$TESTFS2 && \ + log_must zfs destroy -r $TESTPOOL/$TESTFS2 + datasetexists $TESTPOOL/$TESTFS2/newroot && \ + log_must zfs destroy -r $TESTPOOL/$TESTFS2/newroot + datasetexists $TESTPOOL/$TESTFS2/child && \ + log_must zfs destroy -r $TESTPOOL/$TESTFS2/child + +} +log_onexit cleanup + +log_assert "'zfs unmount -u' should unload keys for datasets as they are unmounted" +log_must eval "echo 'password' | zfs create -o encryption=on -o keyformat=passphrase $TESTPOOL/$TESTFS2" +log_must eval "echo 'password' | zfs create -o encryption=on -o keyformat=passphrase $TESTPOOL/$TESTFS2/newroot" +log_must zfs create $TESTPOOL/$TESTFS2/child + +log_must zfs umount -u $TESTPOOL/$TESTFS2/newroot +log_must key_unavailable $TESTPOOL/$TESTFS2/newroot +log_must eval "echo 'password' | zfs mount -l $TESTPOOL/$TESTFS2/newroot" + +log_must zfs umount -u $TESTPOOL/$TESTFS2 +log_must key_unavailable $TESTPOOL/$TESTFS2 +log_must key_unavailable $TESTPOOL/$TESTFS2/newroot +log_must key_unavailable $TESTPOOL/$TESTFS2/child +log_must eval "echo 'password' | zfs mount -l $TESTPOOL/$TESTFS2/newroot" + +log_must zfs snap $TESTPOOL/$TESTFS2/newroot@1 +log_must zfs clone $TESTPOOL/$TESTFS2/newroot@1 $TESTPOOL/$TESTFS2/clone +log_mustnot zfs umount -u $TESTPOOL/$TESTFS2/newroot +log_must key_available $TESTPOOL/$TESTFS2/newroot +log_must mounted $TESTPOOL/$TESTFS2/newroot + +log_pass "'zfs unmount -u' unloads keys for datasets as they are unmounted"