aboutsummaryrefslogtreecommitdiffstats
path: root/cmd/zdb
diff options
context:
space:
mode:
authorRob N <[email protected]>2023-03-03 08:39:09 +1100
committerGitHub <[email protected]>2023-03-02 13:39:09 -0800
commit163f3d3a1fc144f1b9b358cec616d4cba4e87d67 (patch)
tree835b1d88126adf880cfc454abb55283d7ffc0130 /cmd/zdb
parent5f42d1dbf2854d0224f2f5853aab8153f78bdcc3 (diff)
zdb: add decryption support
The approach is straightforward: for dataset ops, if a key was offered, find the encryption root and the various encryption parameters, derive a wrapping key if necessary, and then unlock the encryption root. After that all the regular dataset ops will return unencrypted data, and that's kinda the whole thing. Reviewed-by: Brian Behlendorf <[email protected]> Reviewed-by: Jorgen Lundman <[email protected]> Signed-off-by: Rob Norris <[email protected]> Closes #11551 Closes #12707 Closes #14503
Diffstat (limited to 'cmd/zdb')
-rw-r--r--cmd/zdb/Makefile.am3
-rw-r--r--cmd/zdb/zdb.c174
2 files changed, 166 insertions, 11 deletions
diff --git a/cmd/zdb/Makefile.am b/cmd/zdb/Makefile.am
index b80f38b3f..c93c9c37c 100644
--- a/cmd/zdb/Makefile.am
+++ b/cmd/zdb/Makefile.am
@@ -1,4 +1,5 @@
zdb_CPPFLAGS = $(AM_CPPFLAGS) $(FORCEDEBUG_CPPFLAGS)
+zdb_CFLAGS = $(AM_CFLAGS) $(LIBCRYPTO_CFLAGS)
sbin_PROGRAMS += zdb
CPPCHECKTARGETS += zdb
@@ -12,3 +13,5 @@ zdb_LDADD = \
libzpool.la \
libzfs_core.la \
libnvpair.la
+
+zdb_LDADD += $(LIBCRYPTO_LIBS)
diff --git a/cmd/zdb/zdb.c b/cmd/zdb/zdb.c
index d239da676..58bd1da57 100644
--- a/cmd/zdb/zdb.c
+++ b/cmd/zdb/zdb.c
@@ -40,6 +40,7 @@
#include <stdlib.h>
#include <ctype.h>
#include <getopt.h>
+#include <openssl/evp.h>
#include <sys/zfs_context.h>
#include <sys/spa.h>
#include <sys/spa_impl.h>
@@ -785,16 +786,17 @@ usage(void)
"Usage:\t%s [-AbcdDFGhikLMPsvXy] [-e [-V] [-p <path> ...]] "
"[-I <inflight I/Os>]\n"
"\t\t[-o <var>=<value>]... [-t <txg>] [-U <cache>] [-x <dumpdir>]\n"
+ "\t\t[-K <key>]\n"
"\t\t[<poolname>[/<dataset | objset id>] [<object | range> ...]]\n"
- "\t%s [-AdiPv] [-e [-V] [-p <path> ...]] [-U <cache>]\n"
+ "\t%s [-AdiPv] [-e [-V] [-p <path> ...]] [-U <cache>] [-K <key>]\n"
"\t\t[<poolname>[/<dataset | objset id>] [<object | range> ...]\n"
"\t%s [-v] <bookmark>\n"
"\t%s -C [-A] [-U <cache>]\n"
"\t%s -l [-Aqu] <device>\n"
"\t%s -m [-AFLPX] [-e [-V] [-p <path> ...]] [-t <txg>] "
"[-U <cache>]\n\t\t<poolname> [<vdev> [<metaslab> ...]]\n"
- "\t%s -O <dataset> <path>\n"
- "\t%s -r <dataset> <path> <destination>\n"
+ "\t%s -O [-K <key>] <dataset> <path>\n"
+ "\t%s -r [-K <key>] <dataset> <path> <destination>\n"
"\t%s -R [-A] [-e [-V] [-p <path> ...]] [-U <cache>]\n"
"\t\t<poolname> <vdev>:<offset>:<size>[:<flags>]\n"
"\t%s -E [-A] word0:word1:...:word15\n"
@@ -879,6 +881,8 @@ usage(void)
(void) fprintf(stderr, " -I --inflight=INTEGER "
"specify the maximum number of checksumming I/Os "
"[default is 200]\n");
+ (void) fprintf(stderr, " -K --key=KEY "
+ "decryption key for encrypted dataset\n");
(void) fprintf(stderr, " -o --option=\"OPTION=INTEGER\" "
"set global variable to an unsigned 32-bit integer\n");
(void) fprintf(stderr, " -p --path==PATH "
@@ -3023,6 +3027,117 @@ verify_dd_livelist(objset_t *os)
return (0);
}
+static char *key_material = NULL;
+
+static boolean_t
+zdb_derive_key(dsl_dir_t *dd, uint8_t *key_out)
+{
+ uint64_t keyformat, salt, iters;
+ int i;
+ unsigned char c;
+
+ VERIFY0(zap_lookup(dd->dd_pool->dp_meta_objset, dd->dd_crypto_obj,
+ zfs_prop_to_name(ZFS_PROP_KEYFORMAT), sizeof (uint64_t),
+ 1, &keyformat));
+
+ switch (keyformat) {
+ case ZFS_KEYFORMAT_HEX:
+ for (i = 0; i < WRAPPING_KEY_LEN * 2; i += 2) {
+ if (!isxdigit(key_material[i]) ||
+ !isxdigit(key_material[i+1]))
+ return (B_FALSE);
+ if (sscanf(&key_material[i], "%02hhx", &c) != 1)
+ return (B_FALSE);
+ key_out[i / 2] = c;
+ }
+ break;
+
+ case ZFS_KEYFORMAT_PASSPHRASE:
+ VERIFY0(zap_lookup(dd->dd_pool->dp_meta_objset,
+ dd->dd_crypto_obj, zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT),
+ sizeof (uint64_t), 1, &salt));
+ VERIFY0(zap_lookup(dd->dd_pool->dp_meta_objset,
+ dd->dd_crypto_obj, zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS),
+ sizeof (uint64_t), 1, &iters));
+
+ if (PKCS5_PBKDF2_HMAC_SHA1(key_material, strlen(key_material),
+ ((uint8_t *)&salt), sizeof (uint64_t), iters,
+ WRAPPING_KEY_LEN, key_out) != 1)
+ return (B_FALSE);
+
+ break;
+
+ default:
+ fatal("no support for key format %u\n",
+ (unsigned int) keyformat);
+ }
+
+ return (B_TRUE);
+}
+
+static char encroot[ZFS_MAX_DATASET_NAME_LEN];
+static boolean_t key_loaded = B_FALSE;
+
+static void
+zdb_load_key(objset_t *os)
+{
+ dsl_pool_t *dp;
+ dsl_dir_t *dd, *rdd;
+ uint8_t key[WRAPPING_KEY_LEN];
+ uint64_t rddobj;
+ int err;
+
+ dp = spa_get_dsl(os->os_spa);
+ dd = os->os_dsl_dataset->ds_dir;
+
+ dsl_pool_config_enter(dp, FTAG);
+ VERIFY0(zap_lookup(dd->dd_pool->dp_meta_objset, dd->dd_crypto_obj,
+ DSL_CRYPTO_KEY_ROOT_DDOBJ, sizeof (uint64_t), 1, &rddobj));
+ VERIFY0(dsl_dir_hold_obj(dd->dd_pool, rddobj, NULL, FTAG, &rdd));
+ dsl_dir_name(rdd, encroot);
+ dsl_dir_rele(rdd, FTAG);
+
+ if (!zdb_derive_key(dd, key))
+ fatal("couldn't derive encryption key");
+
+ dsl_pool_config_exit(dp, FTAG);
+
+ ASSERT3U(dsl_dataset_get_keystatus(dd), ==, ZFS_KEYSTATUS_UNAVAILABLE);
+
+ dsl_crypto_params_t *dcp;
+ nvlist_t *crypto_args;
+
+ crypto_args = fnvlist_alloc();
+ fnvlist_add_uint8_array(crypto_args, "wkeydata",
+ (uint8_t *)key, WRAPPING_KEY_LEN);
+ VERIFY0(dsl_crypto_params_create_nvlist(DCP_CMD_NONE,
+ NULL, crypto_args, &dcp));
+ err = spa_keystore_load_wkey(encroot, dcp, B_FALSE);
+
+ dsl_crypto_params_free(dcp, (err != 0));
+ fnvlist_free(crypto_args);
+
+ if (err != 0)
+ fatal(
+ "couldn't load encryption key for %s: %s",
+ encroot, strerror(err));
+
+ ASSERT3U(dsl_dataset_get_keystatus(dd), ==, ZFS_KEYSTATUS_AVAILABLE);
+
+ printf("Unlocked encryption root: %s\n", encroot);
+ key_loaded = B_TRUE;
+}
+
+static void
+zdb_unload_key(void)
+{
+ if (!key_loaded)
+ return;
+
+ VERIFY0(spa_keystore_unload_wkey(encroot));
+ key_loaded = B_FALSE;
+}
+
static avl_tree_t idx_tree;
static avl_tree_t domain_tree;
static boolean_t fuid_table_loaded;
@@ -3037,12 +3152,36 @@ open_objset(const char *path, const void *tag, objset_t **osp)
uint64_t version = 0;
VERIFY3P(sa_os, ==, NULL);
+
/*
* We can't own an objset if it's redacted. Therefore, we do this
* dance: hold the objset, then acquire a long hold on its dataset, then
* release the pool (which is held as part of holding the objset).
*/
- err = dmu_objset_hold(path, tag, osp);
+
+ if (dump_opt['K']) {
+ /* decryption requested, try to load keys */
+ err = dmu_objset_hold(path, tag, osp);
+ if (err != 0) {
+ (void) fprintf(stderr, "failed to hold dataset "
+ "'%s': %s\n",
+ path, strerror(err));
+ return (err);
+ }
+ dsl_dataset_long_hold(dmu_objset_ds(*osp), tag);
+ dsl_pool_rele(dmu_objset_pool(*osp), tag);
+
+ /* succeeds or dies */
+ zdb_load_key(*osp);
+
+ /* release it all */
+ dsl_dataset_long_rele(dmu_objset_ds(*osp), tag);
+ dsl_dataset_rele(dmu_objset_ds(*osp), tag);
+ }
+
+ int ds_hold_flags = key_loaded ? DS_HOLD_FLAG_DECRYPT : 0;
+
+ err = dmu_objset_hold_flags(path, ds_hold_flags, tag, osp);
if (err != 0) {
(void) fprintf(stderr, "failed to hold dataset '%s': %s\n",
path, strerror(err));
@@ -3051,7 +3190,8 @@ open_objset(const char *path, const void *tag, objset_t **osp)
dsl_dataset_long_hold(dmu_objset_ds(*osp), tag);
dsl_pool_rele(dmu_objset_pool(*osp), tag);
- if (dmu_objset_type(*osp) == DMU_OST_ZFS && !(*osp)->os_encrypted) {
+ if (dmu_objset_type(*osp) == DMU_OST_ZFS &&
+ (key_loaded || !(*osp)->os_encrypted)) {
(void) zap_lookup(*osp, MASTER_NODE_OBJ, ZPL_VERSION_STR,
8, 1, &version);
if (version >= ZPL_VERSION_SA) {
@@ -3064,7 +3204,8 @@ open_objset(const char *path, const void *tag, objset_t **osp)
(void) fprintf(stderr, "sa_setup failed: %s\n",
strerror(err));
dsl_dataset_long_rele(dmu_objset_ds(*osp), tag);
- dsl_dataset_rele(dmu_objset_ds(*osp), tag);
+ dsl_dataset_rele_flags(dmu_objset_ds(*osp),
+ ds_hold_flags, tag);
*osp = NULL;
}
}
@@ -3080,9 +3221,12 @@ close_objset(objset_t *os, const void *tag)
if (os->os_sa != NULL)
sa_tear_down(os);
dsl_dataset_long_rele(dmu_objset_ds(os), tag);
- dsl_dataset_rele(dmu_objset_ds(os), tag);
+ dsl_dataset_rele_flags(dmu_objset_ds(os),
+ key_loaded ? DS_HOLD_FLAG_DECRYPT : 0, tag);
sa_attr_table = NULL;
sa_os = NULL;
+
+ zdb_unload_key();
}
static void
@@ -3464,7 +3608,7 @@ dump_object(objset_t *os, uint64_t object, int verbosity,
if (error)
fatal("dmu_object_info() failed, errno %u", error);
- if (os->os_encrypted &&
+ if (!key_loaded && os->os_encrypted &&
DMU_OT_IS_ENCRYPTED(doi.doi_bonus_type)) {
error = dnode_hold(os, object, FTAG, &dn);
if (error)
@@ -3561,7 +3705,8 @@ dump_object(objset_t *os, uint64_t object, int verbosity,
(void) printf("\t\t(bonus encrypted)\n");
}
- if (!os->os_encrypted || !DMU_OT_IS_ENCRYPTED(doi.doi_type)) {
+ if (key_loaded ||
+ (!os->os_encrypted || !DMU_OT_IS_ENCRYPTED(doi.doi_type))) {
object_viewer[ZDB_OT_TYPE(doi.doi_type)](os, object,
NULL, 0);
} else {
@@ -8516,6 +8661,7 @@ main(int argc, char **argv)
{"intent-logs", no_argument, NULL, 'i'},
{"inflight", required_argument, NULL, 'I'},
{"checkpointed-state", no_argument, NULL, 'k'},
+ {"key", required_argument, NULL, 'K'},
{"label", no_argument, NULL, 'l'},
{"disable-leak-tracking", no_argument, NULL, 'L'},
{"metaslabs", no_argument, NULL, 'm'},
@@ -8544,7 +8690,7 @@ main(int argc, char **argv)
};
while ((c = getopt_long(argc, argv,
- "AbcCdDeEFGhiI:klLmMNo:Op:PqrRsSt:uU:vVx:XYyZ",
+ "AbcCdDeEFGhiI:kK:lLmMNo:Op:PqrRsSt:uU:vVx:XYyZ",
long_options, NULL)) != -1) {
switch (c) {
case 'b':
@@ -8595,6 +8741,12 @@ main(int argc, char **argv)
usage();
}
break;
+ case 'K':
+ dump_opt[c]++;
+ key_material = strdup(optarg);
+ /* redact key material in process table */
+ while (*optarg != '\0') { *optarg++ = '*'; }
+ break;
case 'o':
error = set_global_var(optarg);
if (error != 0)
@@ -8689,7 +8841,7 @@ main(int argc, char **argv)
verbose = MAX(verbose, 1);
for (c = 0; c < 256; c++) {
- if (dump_all && strchr("AeEFklLNOPrRSXy", c) == NULL)
+ if (dump_all && strchr("AeEFkKlLNOPrRSXy", c) == NULL)
dump_opt[c] = 1;
if (dump_opt[c])
dump_opt[c] += verbose;