Skip to content

Commit

Permalink
zdb: add decryption support
Browse files Browse the repository at this point in the history
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.

Signed-off-by: Rob Norris <robn@despairlabs.com>
  • Loading branch information
robn committed Feb 17, 2023
1 parent 57cfae4 commit 16b8e15
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 11 deletions.
3 changes: 3 additions & 0 deletions cmd/zdb/Makefile.am
@@ -1,4 +1,5 @@
zdb_CPPFLAGS = $(AM_CPPFLAGS) $(FORCEDEBUG_CPPFLAGS)
zdb_CFLAGS = $(LIBCRYPTO_CFLAGS)

sbin_PROGRAMS += zdb
CPPCHECKTARGETS += zdb
Expand All @@ -12,3 +13,5 @@ zdb_LDADD = \
libzpool.la \
libzfs_core.la \
libnvpair.la

zdb_LDADD += $(LIBCRYPTO_LIBS)
173 changes: 162 additions & 11 deletions cmd/zdb/zdb.c
Expand Up @@ -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>
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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 "
Expand Down Expand Up @@ -3023,6 +3027,116 @@ 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;

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], "%02x", &c) != 1)
return (B_FALSE);
key_out[i / 2] = c;
}
break;

case ZFS_KEYFORMAT_PASSPHRASE:
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);
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 %d\n", 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);
zap_lookup(dd->dd_pool->dp_meta_objset, dd->dd_crypto_obj,
DSL_CRYPTO_KEY_ROOT_DDOBJ, sizeof (uint64_t), 1, &rddobj);
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);
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;

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;
Expand All @@ -3037,12 +3151,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));
Expand All @@ -3051,7 +3189,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) {
Expand All @@ -3064,7 +3203,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;
}
}
Expand All @@ -3080,9 +3220,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
Expand Down Expand Up @@ -3464,7 +3607,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)
Expand Down Expand Up @@ -3561,7 +3704,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 {
Expand Down Expand Up @@ -8516,6 +8660,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'},
Expand Down Expand Up @@ -8544,7 +8689,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':
Expand Down Expand Up @@ -8595,6 +8740,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)
Expand Down Expand Up @@ -8689,7 +8840,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;
Expand Down
22 changes: 22 additions & 0 deletions man/man8/zdb.8
Expand Up @@ -30,12 +30,14 @@
.Op Fl t Ar txg
.Op Fl U Ar cache
.Op Fl x Ar dumpdir
.Op Fl K Ar key
.Op Ar poolname Ns Op / Ns Ar dataset Ns | Ns Ar objset-ID
.Op Ar object Ns | Ns Ar range Ns
.Nm
.Op Fl AdiPv
.Op Fl e Oo Fl V Oc Oo Fl p Ar path Oc Ns
.Op Fl U Ar cache
.Op Fl K Ar key
.Ar poolname Ns Op Ar / Ns Ar dataset Ns | Ns Ar objset-ID
.Op Ar object Ns | Ns Ar range Ns
.Nm
Expand All @@ -59,9 +61,11 @@
.Ar poolname Op Ar vdev Oo Ar metaslab Oc Ns
.Nm
.Fl O
.Op Fl K Ar key
.Ar dataset path
.Nm
.Fl r
.Op Fl K Ar key
.Ar dataset path destination
.Nm
.Fl R
Expand Down Expand Up @@ -412,6 +416,24 @@ The default value is 200.
This option affects the performance of the
.Fl c
option.
.It Fl K , -key Ns = Ns Ar key
Decryption key needed to access an encrypted dataset.
This will cause
.Nm
to attempt to unlock the dataset using the encryption root, key format and other
encryption parameters on the given dataset.
.Nm
can still inspect pool and dataset structures on encrypted datasets without
unlocking them, but will not be able to access file names and attributes and
object contents. \fBWARNING:\fP The raw decryption key and any decrypted data
will be in user memory while
.Nm
is running.
Other user programs may be able to extract it by inspecting
.Nm
as it runs.
Exercise extreme caution when using this option in shared or uncontrolled
environments.
.It Fl o , -option Ns = Ns Ar var Ns = Ns Ar value Ns
Set the given global libzpool variable to the provided value.
The value must be an unsigned 32-bit integer.
Expand Down

0 comments on commit 16b8e15

Please sign in to comment.