From 3f531697d38d909de24f8470c3ee6f23bf77e45e Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Tue, 31 Mar 2026 09:27:27 +0200 Subject: [PATCH 1/4] build: resort libnvme linker scripts Sort alphabetically the symbols. Signed-off-by: Daniel Wagner --- libnvme/src/libnvme.ld | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/libnvme/src/libnvme.ld b/libnvme/src/libnvme.ld index 6d5d7a0f9b..41132df603 100644 --- a/libnvme/src/libnvme.ld +++ b/libnvme/src/libnvme.ld @@ -6,7 +6,6 @@ LIBNVME_3 { nvme_close; nvme_create_ctrl; nvme_create_global_ctx; - nvme_ctrl_match_config; nvme_ctrl_first_ns; nvme_ctrl_first_path; nvme_ctrl_get_config; @@ -17,11 +16,10 @@ LIBNVME_3 { nvme_ctrl_get_subsystem; nvme_ctrl_get_transport_handle; nvme_ctrl_identify; + nvme_ctrl_match_config; nvme_ctrl_next_ns; nvme_ctrl_next_path; nvme_ctrl_release_transport_handle; - nvme_reset_ctrl; - nvme_filter_ctrls; nvme_describe_key_serial; nvme_disconnect_ctrl; nvme_dump_config; @@ -29,14 +27,23 @@ LIBNVME_3 { nvme_errno_to_string; nvme_export_tls_key; nvme_export_tls_key_versioned; + nvme_filter_ctrls; + nvme_filter_namespace; + nvme_filter_paths; + nvme_filter_subsys; + nvme_find_uuid; nvme_first_host; nvme_first_subsystem; nvme_free_ctrl; nvme_free_global_ctx; nvme_free_host; + nvme_free_nbft; nvme_free_ns; nvme_free_subsystem; nvme_gen_dhchap_key; + nvme_generate_hostid; + nvme_generate_hostnqn; + nvme_generate_hostnqn_from_hostid; nvme_generate_tls_key_identity; nvme_generate_tls_key_identity_compat; nvme_get_ana_log_atomic; @@ -48,6 +55,7 @@ LIBNVME_3 { nvme_get_ctrl_telemetry; nvme_get_directive_receive_length; nvme_get_feature_length; + nvme_get_host; nvme_get_host_telemetry; nvme_get_log; nvme_get_logging_level; @@ -57,21 +65,16 @@ LIBNVME_3 { nvme_get_nsid; nvme_get_path_attr; nvme_get_subsys_attr; + nvme_get_subsystem; nvme_get_telemetry_log; nvme_get_telemetry_max; nvme_get_uuid_list; nvme_get_version; - nvme_get_host; nvme_host_get_global_ctx; nvme_host_get_ids; nvme_host_is_pdc_enabled; nvme_host_release_fds; nvme_host_set_pdc_enabled; - nvme_read_hostid; - nvme_generate_hostid; - nvme_read_hostnqn; - nvme_generate_hostnqn; - nvme_generate_hostnqn_from_hostid; nvme_import_tls_key; nvme_import_tls_key_versioned; nvme_init_ctrl; @@ -111,11 +114,8 @@ LIBNVME_3 { nvme_mi_status_to_string; nvme_mi_submit_entry; nvme_mi_submit_exit; - nvme_filter_namespace; nvme_namespace_first_path; nvme_namespace_next_path; - nvme_free_nbft; - nvme_read_nbft; nvme_next_host; nvme_next_subsystem; nvme_ns_compare; @@ -132,7 +132,6 @@ LIBNVME_3 { nvme_ns_get_uuid; nvme_ns_identify; nvme_ns_read; - nvme_rescan_ns; nvme_ns_verify; nvme_ns_write; nvme_ns_write_uncorrectable; @@ -142,11 +141,17 @@ LIBNVME_3 { nvme_path_get_ctrl; nvme_path_get_ns; nvme_path_get_queue_depth; - nvme_filter_paths; + nvme_random_uuid; nvme_read_config; + nvme_read_hostid; + nvme_read_hostnqn; nvme_read_key; + nvme_read_nbft; nvme_refresh_topology; nvme_rescan_ctrl; + nvme_rescan_ns; + nvme_reset_ctrl; + nvme_reset_subsystem; nvme_revoke_tls_key; nvme_scan_ctrl; nvme_scan_ctrl_namespace_paths; @@ -169,16 +174,13 @@ LIBNVME_3 { nvme_strerror; nvme_submit_admin_passthru; nvme_submit_io_passthru; - nvme_filter_subsys; nvme_subsystem_first_ctrl; nvme_subsystem_first_ns; - nvme_get_subsystem; nvme_subsystem_get_host; nvme_subsystem_lookup_namespace; nvme_subsystem_next_ctrl; nvme_subsystem_next_ns; nvme_subsystem_release_fds; - nvme_reset_subsystem; nvme_transport_handle_get_fd; nvme_transport_handle_get_name; nvme_transport_handle_is_blkdev; @@ -190,9 +192,7 @@ LIBNVME_3 { nvme_transport_handle_set_submit_exit; nvme_unlink_ctrl; nvme_update_key; - nvme_find_uuid; nvme_uuid_from_string; - nvme_random_uuid; nvme_uuid_to_string; local: *; From 7dbd3dd24c5988a83d083b1b4ccddd872add803a Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Tue, 31 Mar 2026 08:51:06 +0200 Subject: [PATCH 2/4] linux: add nvme_create_raw_secret Move helper function to create the raw secret from the input string to the library. This allows to use a common function to consistently create the raw secret in nvme-cli and libnvme. For testing purposes it is also helpful to generate a secret based from a pin. Use the pin as seed and create the raw secret with a simple algorithm: function create_key(seed, key_len): output = [] counter = 0 while length(output) < key_len: hash = HASH(seed || encode(counter)) output += hash counter += 1 return output[0:key_len] Signed-off-by: Daniel Wagner --- libnvme/src/libnvme.ld | 1 + libnvme/src/nvme/linux.c | 136 ++++++++++++++++++++++++++++++++++++++- libnvme/src/nvme/linux.h | 18 ++++++ nvme.c | 79 ++--------------------- 4 files changed, 160 insertions(+), 74 deletions(-) diff --git a/libnvme/src/libnvme.ld b/libnvme/src/libnvme.ld index 41132df603..2a34886af2 100644 --- a/libnvme/src/libnvme.ld +++ b/libnvme/src/libnvme.ld @@ -6,6 +6,7 @@ LIBNVME_3 { nvme_close; nvme_create_ctrl; nvme_create_global_ctx; + nvme_create_raw_secret; nvme_ctrl_first_ns; nvme_ctrl_first_path; nvme_ctrl_get_config; diff --git a/libnvme/src/nvme/linux.c b/libnvme/src/nvme/linux.c index fd2271a56b..8189ce814a 100644 --- a/libnvme/src/nvme/linux.c +++ b/libnvme/src/nvme/linux.c @@ -19,6 +19,9 @@ #include #include +#if HAVE_SYS_RANDOM +#include +#endif #include #ifndef _GNU_SOURCE @@ -163,6 +166,14 @@ __public int nvme_gen_dhchap_key(struct nvme_global_ctx *ctx, return 0; } +__public int nvme_create_raw_secret(struct nvme_global_ctx *ctx, + const char *secret, size_t key_len, unsigned char **raw_secret) +{ + nvme_msg(ctx, LOG_ERR, "NVMe TLS 2.0 is not supported; " + "recompile with OpenSSL support.\n"); + return -ENOTSUP; +} + static int derive_retained_key(struct nvme_global_ctx *ctx, int hmac, const char *hostnqn, unsigned char *generated, unsigned char *retained, size_t key_len) @@ -745,7 +756,130 @@ static int derive_psk_digest(struct nvme_global_ctx *ctx, return strlen(digest); } -#endif /* !CONFIG_OPENSSL */ + +static ssize_t getrandom_bytes(void *buf, size_t buflen) +{ + ssize_t result; +#if HAVE_SYS_RANDOM + result = getrandom(buf, buflen, GRND_NONBLOCK); +#else + _cleanup_fd_ int fd = -1; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return -errno; + result = read(fd, buf, buflen); +#endif + if (result < 0) + return -errno; + return result; +} + +static ssize_t getswordfish(struct nvme_global_ctx *ctx, + const char *seed, void *buf, size_t buflen) +{ + unsigned char hash[EVP_MAX_MD_SIZE]; + EVP_MD_CTX *md_ctx; + size_t copied = 0; + + md_ctx = EVP_MD_CTX_new(); + if (!md_ctx) + return -ENOMEM; + + while (copied < buflen) { + unsigned int counter = 0; + unsigned int hash_len; + size_t to_copy; + + if (EVP_DigestInit_ex(md_ctx, EVP_sha256(), NULL) != 1) + goto err; + + EVP_DigestUpdate(md_ctx, seed, strlen(seed)); + EVP_DigestUpdate(md_ctx, &counter, sizeof(counter)); + + if (EVP_DigestFinal_ex(md_ctx, hash, &hash_len) != 1) + goto err; + + to_copy = buflen - copied; + if (to_copy > hash_len) + to_copy = hash_len; + + memcpy((unsigned char *)buf + copied, hash, to_copy); + copied += to_copy; + counter++; + } + + EVP_MD_CTX_free(md_ctx); + return buflen; + +err: + EVP_MD_CTX_free(md_ctx); + return -EIO; +} + +__public int nvme_create_raw_secret(struct nvme_global_ctx *ctx, + const char *secret, size_t key_len, unsigned char **raw_secret) +{ + _cleanup_free_ unsigned char *buf = NULL; + int secret_len = 0, i, err; + unsigned int c; + + if (key_len != 32 && key_len != 48 && key_len != 64) { + nvme_msg(ctx, LOG_ERR, "Invalid key length %ld", key_len); + return -EINVAL; + } + + buf = malloc(key_len); + if (!buf) + return -ENOMEM; + + if (!secret) { + err = getrandom_bytes(buf, key_len); + if (err < 0) + return err; + + goto out; + } + + if (strlen(secret) < 4) { + nvme_msg(ctx, LOG_ERR, "Input secret too short\n"); + return -EINVAL; + } + + if (!strncmp(secret, "pin:", 4)) { + err = getswordfish(ctx, &secret[4], buf, key_len); + if (err < 0) + return err; + + goto out; + } + + for (i = 0; i < strlen(secret); i += 2) { + if (sscanf(&secret[i], "%02x", &c) != 1) { + nvme_msg(ctx, LOG_ERR, + "Invalid secret '%s'", secret); + return -EINVAL; + } + if (i >= key_len * 2) { + nvme_msg(ctx, LOG_ERR, + "Skipping excess secret bytes\n"); + break; + } + buf[secret_len++] = (unsigned char)c; + } + if (secret_len != key_len) { + nvme_msg(ctx, LOG_ERR, + "Invalid key length (%d bytes)\n", secret_len); + return -EINVAL; + } + +out: + *raw_secret = buf; + buf = NULL; + return 0; +} + +#endif /* CONFIG_OPENSSL */ static int gen_tls_identity(const char *hostnqn, const char *subsysnqn, int version, int cipher, char *digest, diff --git a/libnvme/src/nvme/linux.h b/libnvme/src/nvme/linux.h index 2816d4bef8..91320352dc 100644 --- a/libnvme/src/nvme/linux.h +++ b/libnvme/src/nvme/linux.h @@ -103,6 +103,24 @@ int nvme_lookup_key(struct nvme_global_ctx *ctx, const char *type, */ int nvme_set_keyring(struct nvme_global_ctx *ctx, long keyring_id); +/** + * nvme_create_raw_secret - Generate a raw secret buffer from input data + * @ctx: struct nvme_global_ctx object + * @secret: Input secret data + * @key_len: The length of the raw_secret in bytes + * @raw_secret: Return buffer with the generated raw secret + * + * Transforms the provided @secret into a raw secret buffer suitable for + * use with NVMe key management operations. + * + * The generated raw secret can subsequently be passed to nvme_read_key() + * or nvme_update_key(). + * + * Return: 0 on success, or a negative error code on failure. + */ +int nvme_create_raw_secret(struct nvme_global_ctx *ctx, + const char *secret, size_t key_len, unsigned char **raw_secret); + /** * nvme_read_key() - Read key raw data * @ctx: struct nvme_global_ctx object diff --git a/nvme.c b/nvme.c index 8a458d8155..59bd078432 100644 --- a/nvme.c +++ b/nvme.c @@ -46,9 +46,6 @@ #include #include -#if HAVE_SYS_RANDOM - #include -#endif #include @@ -311,24 +308,6 @@ static OPT_VALS(feature_name) = { VAL_END() }; -static ssize_t getrandom_bytes(void *buf, size_t buflen) -{ - ssize_t result; -#if HAVE_SYS_RANDOM - result = getrandom(buf, buflen, GRND_NONBLOCK); -#else - _cleanup_fd_ int fd = -1; - - fd = open("/dev/urandom", O_RDONLY); - if (fd < 0) - return -errno; - result = read(fd, buf, buflen); -#endif - if (result < 0) - return -errno; - return result; -} - static int check_arg_dev(int argc, char **argv) { if (optind >= argc) { @@ -9641,32 +9620,9 @@ static int gen_dhchap_key(int argc, char **argv, struct command *acmd, struct pl cfg.key_len = 32; } - if (cfg.key_len != 32 && cfg.key_len != 48 && cfg.key_len != 64) { - nvme_show_error("Invalid key length %u", cfg.key_len); - return -EINVAL; - } - raw_secret = malloc(cfg.key_len); - if (!raw_secret) - return -ENOMEM; - if (!cfg.secret) { - if (getrandom_bytes(raw_secret, cfg.key_len) < 0) - return -errno; - } else { - int secret_len = 0, i; - unsigned int c; - - for (i = 0; i < strlen(cfg.secret); i += 2) { - if (sscanf(&cfg.secret[i], "%02x", &c) != 1) { - nvme_show_error("Invalid secret '%s'", cfg.secret); - return -EINVAL; - } - raw_secret[secret_len++] = (unsigned char)c; - } - if (secret_len != cfg.key_len) { - nvme_show_error("Invalid key length (%d bytes)", secret_len); - return -EINVAL; - } - } + err = nvme_create_raw_secret(ctx, cfg.secret, cfg.key_len, &raw_secret); + if (err) + return err; if (!cfg.nqn) { cfg.nqn = hnqn = nvme_read_hostnqn(); @@ -9949,32 +9905,9 @@ static int gen_tls_key(int argc, char **argv, struct command *acmd, struct plugi return -ENOMEM; } - raw_secret = malloc(key_len + 4); - if (!raw_secret) - return -ENOMEM; - if (!cfg.secret) { - if (getrandom_bytes(raw_secret, key_len) < 0) - return -errno; - } else { - int secret_len = 0, i; - unsigned int c; - - for (i = 0; i < strlen(cfg.secret); i += 2) { - if (sscanf(&cfg.secret[i], "%02x", &c) != 1) { - nvme_show_error("Invalid secret '%s'", cfg.secret); - return -EINVAL; - } - if (i >= key_len * 2) { - fprintf(stderr, "Skipping excess secret bytes\n"); - break; - } - raw_secret[secret_len++] = (unsigned char)c; - } - if (secret_len != key_len) { - nvme_show_error("Invalid key length (%d bytes)", secret_len); - return -EINVAL; - } - } + err = nvme_create_raw_secret(ctx, cfg.secret, key_len, &raw_secret); + if (err) + return err; err = nvme_export_tls_key(ctx, raw_secret, key_len, &encoded_key); if (err) { From d1efe0767d6eb840d2ca3aeed69d41eec3dd8b42 Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Tue, 31 Mar 2026 10:49:40 +0200 Subject: [PATCH 3/4] fabris: add nvmf_context_free Introduce function to release all resource associated to a fabrics context. This allows makes the opaque data structure more useful inside the library. Signed-off-by: Daniel Wagner --- fabrics.c | 10 +++++----- libnvme/src/libnvmf.ld | 1 + libnvme/src/nvme/fabrics.c | 5 +++++ libnvme/src/nvme/fabrics.h | 11 +++++++++++ util/cleanup.h | 6 ++++++ 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/fabrics.c b/fabrics.c index fb681d5a38..9016fb81d0 100644 --- a/fabrics.c +++ b/fabrics.c @@ -389,7 +389,7 @@ static int create_common_context(struct nvme_global_ctx *ctx, return 0; err: - free(fctx); + nvmf_context_free(fctx); return err; } @@ -425,7 +425,7 @@ static int create_discovery_context(struct nvme_global_ctx *ctx, return 0; err: - free(fctx); + nvmf_context_free(fctx); return err; } @@ -480,7 +480,7 @@ int fabrics_discovery(const char *desc, int argc, char **argv, bool connect) char *context = NULL; nvme_print_flags_t flags; _cleanup_nvme_global_ctx_ struct nvme_global_ctx *ctx = NULL; - _cleanup_free_ struct nvmf_context *fctx = NULL; + _cleanup_nvmf_context_ struct nvmf_context *fctx = NULL; int ret; struct nvme_fabrics_config cfg; struct nvmf_args fa = { .subsysnqn = NVME_DISC_SUBSYS_NAME }; @@ -592,7 +592,7 @@ int fabrics_connect(const char *desc, int argc, char **argv) char *config_file = NULL; char *context = NULL; _cleanup_nvme_global_ctx_ struct nvme_global_ctx *ctx = NULL; - _cleanup_free_ struct nvmf_context *fctx = NULL; + _cleanup_nvmf_context_ struct nvmf_context *fctx = NULL; _cleanup_nvme_ctrl_ nvme_ctrl_t c = NULL; int ret; nvme_print_flags_t flags; @@ -931,7 +931,7 @@ int fabrics_config(const char *desc, int argc, char **argv) } if (modify_config) { - _cleanup_free_ struct nvmf_context *fctx = NULL; + _cleanup_nvmf_context_ struct nvmf_context *fctx = NULL; if (!fa.subsysnqn) { fprintf(stderr, diff --git a/libnvme/src/libnvmf.ld b/libnvme/src/libnvmf.ld index 806490b064..6ee8e719d2 100644 --- a/libnvme/src/libnvmf.ld +++ b/libnvme/src/libnvmf.ld @@ -10,6 +10,7 @@ LIBNVMF_3 { nvmf_connect_config_json; nvmf_connect_ctrl; nvmf_context_create; + nvmf_context_free; nvmf_context_set_connection; nvmf_context_set_crypto; nvmf_context_set_device; diff --git a/libnvme/src/nvme/fabrics.c b/libnvme/src/nvme/fabrics.c index 129aa134d3..364923abaa 100644 --- a/libnvme/src/nvme/fabrics.c +++ b/libnvme/src/nvme/fabrics.c @@ -214,6 +214,11 @@ __public int nvmf_context_create(struct nvme_global_ctx *ctx, return 0; } +__public void nvmf_context_free(struct nvmf_context *fctx) +{ + free(fctx); +} + __public int nvmf_context_set_discovery_cbs(struct nvmf_context *fctx, void (*discovery_log)(struct nvmf_context *fctx, bool connect, diff --git a/libnvme/src/nvme/fabrics.h b/libnvme/src/nvme/fabrics.h index 5a441ec2a0..bba27ab036 100644 --- a/libnvme/src/nvme/fabrics.h +++ b/libnvme/src/nvme/fabrics.h @@ -381,6 +381,17 @@ int nvmf_context_create(struct nvme_global_ctx *ctx, const char *trsvcid, void *user_data), void *user_data, struct nvmf_context **fctxp); +/** + * nvmf_context_free() - Free a fabrics context + * @fctx: Fabrics context to free + * + * Releases all resources associated with @fctx. The context must have + * been previously created with nvmf_context_create(). + * + * After this call, @fctx must not be used. + */ +void nvmf_context_free(struct nvmf_context *fctx); + /** * nvmf_context_set_discovery_cbs() - Set discovery callbacks for context * @fctx: Fabrics context diff --git a/util/cleanup.h b/util/cleanup.h index 6ef3e87673..95cba332f5 100644 --- a/util/cleanup.h +++ b/util/cleanup.h @@ -52,6 +52,12 @@ static inline void free_uri(struct nvme_fabrics_uri **uri) nvmf_free_uri(*uri); } #define _cleanup_uri_ __cleanup__(free_uri) + +static inline void cleanup_nvmf_context(struct nvmf_context **fctx) +{ + nvmf_context_free(*fctx); +} +#define _cleanup_nvmf_context_ __cleanup__(cleanup_nvmf_context) #endif static inline DEFINE_CLEANUP_FUNC(cleanup_file, FILE *, fclose) From 5d8b815f8a9e9cdede4d240e93844e7b72ab1210 Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Tue, 31 Mar 2026 10:55:45 +0200 Subject: [PATCH 4/4] fabrics: allow tls key to be a pin For testing purpose it is very useful to have proper created secret keys based on a pin. Thus extend nvmf_context_set_crypto to transform the pin secret into a proper key. Signed-off-by: Daniel Wagner --- libnvme/src/nvme/fabrics.c | 30 +++++++++++++++++++++++++++++- libnvme/src/nvme/private.h | 4 +++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/libnvme/src/nvme/fabrics.c b/libnvme/src/nvme/fabrics.c index 364923abaa..eb04eeba5f 100644 --- a/libnvme/src/nvme/fabrics.c +++ b/libnvme/src/nvme/fabrics.c @@ -204,6 +204,8 @@ __public int nvmf_context_create(struct nvme_global_ctx *ctx, if (!fctx) return -ENOMEM; + fctx->ctx = ctx; + fctx->decide_retry = decide_retry; fctx->connected = connected; fctx->already_connected = already_connected; @@ -216,6 +218,7 @@ __public int nvmf_context_create(struct nvme_global_ctx *ctx, __public void nvmf_context_free(struct nvmf_context *fctx) { + free(fctx->tls_key); free(fctx); } @@ -285,12 +288,37 @@ __public int nvmf_context_set_crypto(struct nvmf_context *fctx, const char *keyring, const char *tls_key, const char *tls_key_identity) { + int err; + fctx->hostkey = hostkey; fctx->ctrlkey = ctrlkey; fctx->keyring = keyring; - fctx->tls_key = tls_key; fctx->tls_key_identity = tls_key_identity; + if (!tls_key) + return 0; + + if (!strncmp(tls_key, "pin:", 4)) { + _cleanup_free_ unsigned char *raw_secret = NULL; + _cleanup_free_ char *encoded_key = NULL; + int key_len = 32; + + err = nvme_create_raw_secret(fctx->ctx, tls_key, + key_len, &raw_secret); + if (err) + return err; + + err = nvme_export_tls_key(fctx->ctx, raw_secret, + key_len, &encoded_key); + if (err) + return err; + + fctx->tls_key = encoded_key; + encoded_key = NULL; + return 0; + } + + fctx->tls_key = strdup(tls_key); return 0; } diff --git a/libnvme/src/nvme/private.h b/libnvme/src/nvme/private.h index 1f92efbc19..c3651fb958 100644 --- a/libnvme/src/nvme/private.h +++ b/libnvme/src/nvme/private.h @@ -307,6 +307,8 @@ struct nvme_global_ctx { }; struct nvmf_context { + struct nvme_global_ctx *ctx; + /* common callbacks */ bool (*decide_retry)(struct nvmf_context *fctx, int err, void *user_data); @@ -354,7 +356,7 @@ struct nvmf_context { const char *hostkey; const char *ctrlkey; const char *keyring; - const char *tls_key; + char *tls_key; const char *tls_key_identity; void *user_data;