libnvmf_generate_hostid() tries DMI, then device-tree, then a random UUID. DMI is the only deterministic source, and it needs root: /sys/class/dmi/id/product_uuid and /sys/firmware/dmi/entries/*/raw are mode 0400. An unprivileged caller gets EACCES and the code silently falls through to libnvme_random_uuid(). So the same machine reports a stable identifier as root and a different one on every other call as a regular user.
Why that matters needs no elaboration here: targets gate access on the host NQN, and credentials are bound to it.
The scope is narrow, since anything that opens a controller needs root anyway. It is not empty: nvme gen-hostnqn touches no device, runs fine unprivileged, and answers differently every time with nothing saying why. Library consumers see the same.
Proposal
Add /etc/machine-id as the first source:
/etc/machine-id (derived) -> DMI -> device-tree -> random
/etc/machine-id is systemd's local machine identifier: 32 lowercase hex characters, 128 bits, written at installation or first boot and constant afterwards. It has been in systemd since v20 (commit d7ccca2e, March 2011) and every major distribution ships it. Man page: https://man7.org/linux/man-pages/man5/machine-id.5.html
It is mode 0444, so it is the only deterministic source an unprivileged caller can read. It is also the only one that works everywhere. DMI comes from PC firmware, so it exists on x86 and on ARM64 servers following SystemReady, but not on POWER, s390x, or embedded ARM and RISC-V boards. The device-tree fallback does not fill that gap: PATH_UUID_IBM is /proc/device-tree/ibm,partition-uuid (tree-linux.c:36), which covers IBM POWER LPARs only. Every other machine without DMI goes straight to a random UUID, whatever its privileges.
The raw value must not be used. systemd's documentation says the machine ID "must not be exposed in untrusted environments, in particular on the network", and that a machine-tied identifier must instead be derived by hashing it under a fixed, application-specific key. A host NQN is sent to the target and is visible across the fabric. The derivation is sd_id128_get_machine_app_specific(): HMAC-SHA256 keyed by a public application id, truncated to 128 bits, with the UUID variant and version bits set.
A second problem in the same function
uuid_from_product_uuid() validates the string length only (fabrics.c:236). A machine reporting an all-zero or all-FF System UUID, which is common on virtual machines and whitebox boards, gets that as its identifier, shared with every identical machine. Two hosts, one NQN, which is worse than a random value.
This validation used to exist. PR #654 (2020), which is where the DMI-first ordering came from, checked the UUID format and screened for fake vendor UUIDs by rejecting a value where one character dominates, which catches all-zeros and all-FFs. Neither check came along when the logic moved into C. Worth restoring, along with a log line naming the source that won.
Implementation
sd_id128_get_machine_app_specific() is a public libsystemd API, but I would not link it here. libnvme builds for Windows and for systems without systemd, and nvme-cli links libsystemd only for nvme-discoverd. Lifting the implementation avoids that, with precedent: shr_parse_time() in shared/time-util.c came from systemd, and shared/ carries the same LGPL-2.1-or-later license. systemd uses its own SHA-256 and HMAC rather than OpenSSL, so lifting also avoids gating this on CONFIG_OPENSSL, which shr_hmac_sha256() sits behind today.
Open question: should machine-id outrank DMI? machine-id identifies an installation, so it survives package upgrades and distribution upgrades but changes if the machine is reimaged; DMI identifies hardware and survives both. I lean toward machine-id first anyway, because with DMI first the same command returns different identifiers as root and as a normal user, and that split is silent.
libnvmf_generate_hostid()tries DMI, then device-tree, then a random UUID. DMI is the only deterministic source, and it needs root:/sys/class/dmi/id/product_uuidand/sys/firmware/dmi/entries/*/raware mode0400. An unprivileged caller getsEACCESand the code silently falls through tolibnvme_random_uuid(). So the same machine reports a stable identifier as root and a different one on every other call as a regular user.Why that matters needs no elaboration here: targets gate access on the host NQN, and credentials are bound to it.
The scope is narrow, since anything that opens a controller needs root anyway. It is not empty:
nvme gen-hostnqntouches no device, runs fine unprivileged, and answers differently every time with nothing saying why. Library consumers see the same.Proposal
Add
/etc/machine-idas the first source:/etc/machine-idis systemd's local machine identifier: 32 lowercase hex characters, 128 bits, written at installation or first boot and constant afterwards. It has been in systemd since v20 (commitd7ccca2e, March 2011) and every major distribution ships it. Man page: https://man7.org/linux/man-pages/man5/machine-id.5.htmlIt is mode
0444, so it is the only deterministic source an unprivileged caller can read. It is also the only one that works everywhere. DMI comes from PC firmware, so it exists on x86 and on ARM64 servers following SystemReady, but not on POWER, s390x, or embedded ARM and RISC-V boards. The device-tree fallback does not fill that gap:PATH_UUID_IBMis/proc/device-tree/ibm,partition-uuid(tree-linux.c:36), which covers IBM POWER LPARs only. Every other machine without DMI goes straight to a random UUID, whatever its privileges.The raw value must not be used. systemd's documentation says the machine ID "must not be exposed in untrusted environments, in particular on the network", and that a machine-tied identifier must instead be derived by hashing it under a fixed, application-specific key. A host NQN is sent to the target and is visible across the fabric. The derivation is
sd_id128_get_machine_app_specific(): HMAC-SHA256 keyed by a public application id, truncated to 128 bits, with the UUID variant and version bits set.A second problem in the same function
uuid_from_product_uuid()validates the string length only (fabrics.c:236). A machine reporting an all-zero or all-FF System UUID, which is common on virtual machines and whitebox boards, gets that as its identifier, shared with every identical machine. Two hosts, one NQN, which is worse than a random value.This validation used to exist. PR #654 (2020), which is where the DMI-first ordering came from, checked the UUID format and screened for fake vendor UUIDs by rejecting a value where one character dominates, which catches all-zeros and all-FFs. Neither check came along when the logic moved into C. Worth restoring, along with a log line naming the source that won.
Implementation
sd_id128_get_machine_app_specific()is a public libsystemd API, but I would not link it here. libnvme builds for Windows and for systems without systemd, and nvme-cli links libsystemd only for nvme-discoverd. Lifting the implementation avoids that, with precedent:shr_parse_time()inshared/time-util.ccame from systemd, andshared/carries the same LGPL-2.1-or-later license. systemd uses its own SHA-256 and HMAC rather than OpenSSL, so lifting also avoids gating this onCONFIG_OPENSSL, whichshr_hmac_sha256()sits behind today.Open question: should machine-id outrank DMI? machine-id identifies an installation, so it survives package upgrades and distribution upgrades but changes if the machine is reimaged; DMI identifies hardware and survives both. I lean toward machine-id first anyway, because with DMI first the same command returns different identifiers as root and as a normal user, and that split is silent.