Repository navigation
nvme: drop root privileges through the privsep helper - #4182
Conversation
|
I am not totally happy with this approach but given that we retrofitting privilege separation, it's no surprise that there are exception necessary at this point. Ideally we could drop the root rights right at the beginning without parsing anything. But this is not possible right now. So I think it's a step towards this goal. Thus let's get the device commands working and figure out how to handle the fabrics related one in a different step. @martin-belanger do you agree with this approach do you think this is a good idea? I am still not sure if the whole privsep is going to work as hoped. I'd say we mark this as experiment and if it turns out shitty, we can remove it again. |
There was a problem hiding this comment.
🟡 Changes recommended
Helper integration introduces functional regressions and an argument-based path that unintentionally retains root privileges.
5 open findings
Handle helper-backed devices in format rescan and reset guards · New Restrict privilege-separation bypass to the device argument · New Keep registry update and delete commands privileged · New Forward no-ioctl-probing setting to the helper · New Skip helper tests under root or run them unprivileged · New
What changed in this PR
Connects nvme-cli to the existing privilege-separation helper, allowing eligible commands to drop root privileges before parsing configuration and options. This remains Linux-only and disabled by default.
Changes:
- Adds early command selection, privilege dropping and helper-backed device access.
- Marks commands that must retain root privileges.
- Adds command-selection checks and helper-backed CLI tests.
| File | Description |
|---|---|
| tests/unit/c/test-plugin.c | Tests command-selection consistency. |
| tests/unit/c/meson.build | Links privilege-separation support into tests. |
| tests/cli/nvme_privsep_test.py | Tests helper startup and device routing. |
| tests/cli/meson.build | Registers helper-backed CLI tests. |
| src/privsep.h | Declares privilege-separation interfaces and stubs. |
| src/privsep.c | Implements helper startup and privilege dropping. |
| src/plugin.h | Adds command privilege metadata and lookup interface. |
| src/plugin.c | Adds early lookup and execution checks. |
| src/nvme.c | Initializes privilege separation before configuration loading. |
| src/nvme-cmds-fabrics.c | Attaches contexts and preserves fabrics privileges. |
| src/nvme-cmds-enumerate.c | Preserves enumeration privileges. |
| src/nvme-cmds-deprecated.c | Preserves deprecated key-command privileges. |
| src/meson.build | Conditionally builds privilege-separation support. |
| src/global-ctx.c | Attaches shared contexts to the helper. |
| plugins/zns/zns.c | Preserves listing privileges. |
| plugins/wdc/wdc-nvme.c | Marks privileged telemetry commands. |
| plugins/utils/utils.c | Exempts metadata dumping from privilege dropping. |
| plugins/solidigm/solidigm-nvme.c | Exempts version reporting. |
| plugins/seagate/seagate-nvme.c | Exempts version reporting. |
| plugins/sandisk/sandisk-nvme.c | Marks privileged telemetry commands. |
| plugins/registry/registry-nvme.c | Preserves registry-list privileges. |
| plugins/netapp/netapp-nvme.c | Preserves enumeration privileges. |
| plugins/nbft/nbft-plugin.c | Preserves NBFT-reading privileges. |
| plugins/micron/micron-nvme.c | Marks privileged diagnostics and version commands. |
| plugins/keys/keys-plugin.c | Preserves key-management privileges. |
| plugins/huawei/huawei-nvme.c | Preserves listing privileges. |
| plugins/exclusion/exclusion-nvme.c | Preserves exclusion-management privileges. |
| plugins/config/config-nvme.c | Preserves configuration-management privileges. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
I agree with this approach. Getting the device commands working first makes sense, and so does marking it as an experiment. For the fabrics commands, here is an idea for the later step. Limits:
This is only a thought, |
|
That is also an option, that the fabrics command are just forwarding the request to discocverd. This might be an option for a production system. I think we still want the feature to be able to write directly to the fabrics device for development work. FWIW, the current version of the privsep helper is able to handle the fabrics command (writing to the fabrics device). What is missing are all the registry features. |
There was a problem hiding this comment.
🔵 Needs a closer look
The credential-changing lifecycle and new privilege boundary require final human security review.
7 open issues
Restrict privilege-separation bypass to the device argument Handle helper-backed devices in format rescan and reset guards Report unreadable configuration errors after privilege drop · New Gate id-ctrl helper tests on the id plugin · New Skip helper tests under root or run them unprivileged Forward no-ioctl-probing setting to the helper Keep registry update and delete commands privileged
Review effort: Balanced
libnvme_get_nsid(), libnvme_reset_subsystem() and libnvme_rescan_ns() return a negative error code. Only for a direct handle errno happens to hold the same value; a privsep handle gets the error code from the helper and leaves errno alone. Check the return value instead. With a privsep handle, get-feature, set-feature and fdp set-events failed on a controller device instead of falling back to all namespaces, and ns get-id and format could report success after a failure. Signed-off-by: Daniel Wagner <dwagner@suse.de>
A handle opened through the privsep helper drives a local device with the ioctl interface, only in the helper. Report it as direct, so that callers like nvme format still rescan the namespaces, update the block size or reset the controller after a format. Signed-off-by: Daniel Wagner <dwagner@suse.de>
libnvme_set_ioctl_probing() only changed the context of the client; the helper still probed for the 64 bit passthru ioctls, e.g. for nvme --no-ioctl-probing. Send the setting with OPEN, in the reserved word now called options, and open the handle in a helper context without probing. The helper rejects unknown options. Signed-off-by: Daniel Wagner <dwagner@suse.de>
With privilege separation, nvme-cli drops its root privileges and opens the devices through the libnvme helper. The helper forwards the device commands only, so the commands which do anything else must keep root for now: the commands without a device argument (fabrics, enumeration, keys, exclusions, configuration, registry, NBFT) and the device commands which also access the PCI config space, the kernel log or root owned paths (Micron vs-pcie-stats, clear-pcie-correctable-errors and vs-internal-log, WDC and Sandisk reason-identifier and vs-telemetry-controller-option, registry update and delete). Add an explicit needs_root flag to struct command and set it for them. The list is what needs to be addressed before nvme-cli can drop the privileges for all commands. Nothing uses the flag yet. Signed-off-by: Daniel Wagner <dwagner@suse.de>
nvme-cli failed without a message when nvme-cli.conf exists but can't be read. Report the file name and the error. A missing file is still not an error. The file holds no secrets and must be readable by every user, as nvme-cli is going to read it after dropping its root privileges. Say so in nvme-cli.conf(5). Signed-off-by: Daniel Wagner <dwagner@suse.de>
When root runs nvme-cli on behalf of a user and the command does not need root, start the libnvme privsep helper, drop the privileges and open the devices through the helper. The decision runs first in main(), on the command plugin_lookup() found, before the configuration file or the options of the command are parsed, so nvme-cli.conf is read as the target user. The privileges are dropped to SUDO_UID/SUDO_GID or to NVME_PRIVSEP_UID/NVME_PRIVSEP_GID. The environment is trusted only when nvme-cli runs as root, and setuid is not supported: a setuid nvme-cli keeps running as it was started. Without any of the variables root keeps its privileges, which leaves scripts and services unchanged. The supplementary groups become the target group only; looking up the ones of the target user would run NSS as root. If the helper cannot be started or the privileges cannot be dropped, nvme-cli fails instead of running privileged. Commands with an MI device (mctp:<net>,<eid>) keep root, as MI uses the MCTP sockets directly. A command which needs root does not run once the privileges are dropped, should a deprecated command forward to one. The code is the same without privilege separation support, except that it never starts a helper. For the tests, NVME_PRIVSEP_TEST_HELPER names a helper which an unprivileged nvme-cli uses as well; it is ignored as root and in secure-execution mode. Signed-off-by: Daniel Wagner <dwagner@suse.de>
Add a test for the privilege separation of nvme-cli: device commands start the helper and the helper opens the devices, while commands which keep root, MI devices, help and version don't. The test needs NVME_PRIVSEP_TEST_HELPER and is skipped when run as root, which ignores it. In privsep builds, also run the mock CLI tests (print, pif-sts, init-pi-tags, dry-run, sel, host-metadata, discovery-logs, sed) a second time with the commands going through the helper. Signed-off-by: Daniel Wagner <dwagner@suse.de>
There was a problem hiding this comment.
🔵 Needs a closer look
The credential transition and privileged-helper trust boundary warrant final human security review.
1 open issue
6 resolved since last review
Restrict privilege-separation bypass to the device argument Handle helper-backed devices in format rescan and reset guards Gate id-ctrl helper tests on the id plugin Report unreadable configuration errors after privilege drop Skip helper tests under root or run them unprivileged Forward no-ioctl-probing setting to the helper
Review effort: Balanced


Fourth step of privilege separation (#3879): nvme-cli uses the helper. When root runs nvme-cli on behalf of a user and the command does not need root, nvme-cli starts the helper, drops its privileges and opens the devices through the helper. Rebased onto master with #4185.
Still behind the
privsepmeson option (disabled by default, Linux only). The code is the same with the option disabled, except that it never starts a helper. setuid is not supported: a setuid nvme-cli keeps running as it was started and never trusts the environment.Fix found by the privsep test runs: the callers of
libnvme_get_nsid(),libnvme_reset_subsystem()andlibnvme_rescan_ns()checkederrnoinstead of the return value. A privsep handle leaveserrnoalone, soget-feature,set-featureandfdp set-eventsfailed on a controller device, andns get-idandformatcould report success after a failure.Prep:
libnvme_transport_handle_is_direct(), sonvme formatstill rescans the namespaces, updates the block size and resets the controllerlibnvme_set_ioctl_probing()reaches the helper: OPEN carries it in the former reserved word, the helper opens the handle in a context without probing and rejects unknown optionsWhich commands drop: an explicit
needs_rootflag instruct commandmarks the commands which keep root: the commands without a device argument (fabrics, enumeration, keys, exclusions, configuration, registry, NBFT, and the deprecated key commands) and the device commands which also access PCI config space, the kernel log or root owned paths (Micronvs-pcie-stats,clear-pcie-correctable-errors,vs-internal-log, WDC and Sandiskreason-identifier,vs-telemetry-controller-option,registry updateanddelete). The list is what has to be addressed before all commands can drop. Commands with an MI device (mctp:<net>,<eid>in their arguments) keep root, as MI uses the MCTP sockets directly.When: first in
main(), on the commandplugin_lookup()found (#4185), before the configuration file or the options of the command are parsed. Aneeds_rootcommand does not run once the privileges are dropped, should a deprecated command forward to one.To whom:
SUDO_UID/SUDO_GID, elseNVME_PRIVSEP_UID/NVME_PRIVSEP_GID, only when nvme-cli runs as root. Without them root keeps its privileges, so scripts and services are unchanged. The supplementary groups become the target group only (no NSS as root). If the helper cannot be started or the privileges cannot be dropped, nvme-cli fails instead of running privileged.For the tests,
NVME_PRIVSEP_TEST_HELPERnames a helper which an unprivileged nvme-cli uses as well; it is ignored as root and in secure-execution mode.Testing:
nvme-cli - privsep, needs theidplugin): device commands start the helper and the helper opens the device, commands which keep root, MI devices, help and version don't, amctp:file name does not keep root; skipped when run as rootnvme-cli - <name>-privsep)--werror), every commit on its ownNVME_PRIVSEP_UID,SUDO_UID, missing helper,needs_rootcommands, MI, setuid nvme keeps root and ignores the environment, nvme-cli.conf read after the drop with the command line winning