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.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Jorgen Lundman <lundman@lundman.net>
Signed-off-by: Rob Norris <robn@despairlabs.com>
Closes openzfs#11551
Closes openzfs#12707
Closes openzfs#14503
  • Loading branch information
robn authored and sdimitro committed Mar 8, 2023
1 parent d696a1d commit b41fafa
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 22 deletions.
4 changes: 2 additions & 2 deletions cmd/libzoa_util/libzoa_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
#include <libzoa.h>
#endif

int start_zfs_object_agent(char *, void **);
int start_zfs_object_agent(const char *, void **);

#ifdef HAVE_LIBZOA

Expand Down Expand Up @@ -112,7 +112,7 @@ zoa_thread(void *arg)
* initialization to finish.
*/
int
start_zfs_object_agent(char *logfile, void **handle)
start_zfs_object_agent(const char *logfile, void **handle)
{
#ifdef HAVE_LIBZOA
(void) strlcpy(zoa_log_file, logfile, sizeof (zoa_log_file));
Expand Down
5 changes: 4 additions & 1 deletion cmd/zdb/Makefile.am
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Unconditionally enable debugging for zdb
zdb_CPPFLAGS = $(AM_CPPFLAGS) $(FORCEDEBUG_CPPFLAGS)
zdb_CFLAGS = $(AM_CFLAGS) $(LIBCRYPTO_CFLAGS)

sbin_PROGRAMS += zdb
CPPCHECKTARGETS += zdb
Expand All @@ -15,10 +16,12 @@ zdb_LDADD = \
lib/libzfs_core.la \
lib/libnvpair.la

zdb_LDADD += $(LIBCRYPTO_LIBS)

if BUILD_RUST
zdb_LDADD += -lzoa
zdb_LDFLAGS = -L$(abs_top_builddir)/cmd/zfs_object_agent/$(RUSTDIR)
zdb_CFLAGS = -DHAVE_LIBZOA
zdb_CFLAGS += -DHAVE_LIBZOA
endif

# include $(top_srcdir)/config/CppCheck.am
183 changes: 169 additions & 14 deletions cmd/zdb/zdb.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include <stdlib.h>
#include <ctype.h>
#include <getopt.h>
#include <openssl/evp.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/zfs_context.h>
Expand Down Expand Up @@ -175,7 +176,7 @@ char *endpoint;
char *region;
char *bucket;
char *creds_profile;
char *protocol;
const char *protocol;
#ifdef HAVE_LIBZOA
zoa_handle_t *zoa_handle;
#endif
Expand Down Expand Up @@ -800,8 +801,10 @@ usage(void)
"Usage:\t%1$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%1$s [-AdiPv] [-e [-V] [-p <path> ...]] [-U <cache>]\n"
"\t%1$s [-AdiPv] [-e [-V] [-p <path> ...]] [-U <cache>] "
"[-K <key>]\n"
"\t\t[<poolname>[/<dataset | objset id>] [<object | range> ...]\n"
#ifdef HAVE_LIBZOA
"\t%1$s [-AdiPv] [-e [-V] [-T <protocol>] -a <endpoint> "
Expand All @@ -814,8 +817,8 @@ usage(void)
"\t%1$s -l [-Aqu] <device>\n"
"\t%1$s -m [-AFLPX] [-e [-V] [-p <path> ...]] [-t <txg>] "
"[-U <cache>]\n\t\t<poolname> [<vdev> [<metaslab> ...]]\n"
"\t%1$s -O <dataset> <path>\n"
"\t%1$s -r <dataset> <path> <destination>\n"
"\t%1$s -O [-K <key>] <dataset> <path>\n"
"\t%1$s -r [-K <key>] <dataset> <path> <destination>\n"
"\t%1$s -R [-A] [-e [-V] [-p <path> ...]] [-U <cache>]\n"
"\t\t<poolname> <vdev>:<offset>:<size>[:<flags>]\n"
"\t%1$s -E [-A] word0:word1:...:word15\n"
Expand Down Expand Up @@ -899,6 +902,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 @@ -3059,6 +3064,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;
Expand All @@ -3073,12 +3189,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 @@ -3087,7 +3227,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 @@ -3100,7 +3241,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 @@ -3116,9 +3258,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 @@ -3500,7 +3645,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 @@ -3597,7 +3742,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 @@ -7437,7 +7583,9 @@ dump_leftover_checkpoint_blocks(spa_t *spa)
#define MAX_ARRAY_PRINT_SIZE 16

static int
quiet_uint8_array_printer(nvlist_prtctl_t ctl, void *unused, nvlist_t *nvl,
quiet_uint8_array_printer(nvlist_prtctl_t ctl,
void *unused __attribute__((unused)),
nvlist_t *nvl __attribute__((unused)),
const char *name, uint8_t *value, uint_t count)
{
int verbosity = dump_opt['v'];
Expand Down Expand Up @@ -8718,7 +8866,7 @@ main(int argc, char **argv)
int verbose = 0;
int error = 0;
char **searchdirs = NULL;
char *zoa_log_file = DEFAULT_ZOA_LOG;
const char *zoa_log_file = DEFAULT_ZOA_LOG;
int nsearch = 0;
char *target, *target_pool, dsname[ZFS_MAX_DATASET_NAME_LEN];
nvlist_t *policy = NULL;
Expand Down Expand Up @@ -8764,6 +8912,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 @@ -8792,7 +8941,7 @@ main(int argc, char **argv)
};

while ((c = getopt_long(argc, argv,
"a:AB:bcCdDeEf:Fg:GhiI:klLmMNo:Op:pPqrRsSt:T:uU:vVx:XYyZz:",
"a:AB:bcCdDeEf:Fg:GhiI:kK:lLmMNo:Op:pPqrRsSt:T:uU:vVx:XYyZz:",
long_options, NULL)) != -1) {
switch (c) {
case 'b':
Expand Down Expand Up @@ -8843,6 +8992,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 @@ -8985,7 +9140,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
2 changes: 1 addition & 1 deletion include/libzoa_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
extern "C" {
#endif

int start_zfs_object_agent(char *, void**);
int start_zfs_object_agent(const char *, void**);

#ifdef __cplusplus
}
Expand Down
Loading

0 comments on commit b41fafa

Please sign in to comment.