diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index c340e01d016..a2f5876867a 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -776,6 +776,6 @@ Tools using the Varlink protocol (such as `varlinkctl`) or sd-bus (such as `systemd-tpm2-clear`: * `SYSTEMD_TPM2_ALLOW_CLEAR` – takes a boolean. Overrides the effect of the - `systemd.factory_reset=` kernel command line option: if set to false, + `systemd.tpm2_allow_clear=` kernel command line option: if set to false, requesting a TPM clearing is skipped, and the command immediately exits successfully. diff --git a/docs/FACTORY_RESET.md b/docs/FACTORY_RESET.md index bb0e3063b11..5a1765e2c6e 100644 --- a/docs/FACTORY_RESET.md +++ b/docs/FACTORY_RESET.md @@ -11,25 +11,69 @@ In various scenarios it is important to be able to reset operating systems back into a "factory state", i.e. where all state, user data and configuration is reset so that it resembles the system state when it was originally shipped. -systemd natively supports a concept of factory reset, that can both act as a -specific implementation for UEFI based systems, as well as a series of hook -points and a template for implementations on other systems. - +systemd natively supports a concept of factory reset, through a series of +generic hook points that can be integrated with a factory reset mechanism. Factory reset always takes place during early boot, i.e. from a well-defined -"clean" state. Factory reset operations may be requested from one boot to be +"clean" state. Factory reset operations are requested from one boot to be executed on the next. -Specifically, the following concepts are available: +The mechanism works as follows: -* The `factory-reset.target` unit may be used to request a factory reset - operation and trigger a reboot in order to execute it. It by default executes - three services: `systemd-factory-reset-request.service`, - `systemd-tpm2-clear.service` and `systemd-factory-reset-reboot.service`. +* The `factory-reset.target` unit is used to request a factory reset operation + and trigger a reboot in order to execute it. Services invoked via this target + prepare the system such that a factory reset will be requested on the next + boot. Once these services are done, + [`systemd-factory-reset-reboot.service`](https://www.freedesktop.org/software/systemd/man/latest/systemd-factory-reset-reboot.service.html) + is started, which triggers the reboot. + +* On the next boot, `systemd-factory-reset-generator` checks whether or not a + factory reset was requested. A factory reset may be requested via a kernel + command line option (`systemd.factory_reset=1`) or via the UEFI variable + `FactoryResetRequest` (see below). If either condition is met, + `factory-reset-now.target` is added to the boot transaction. + +* `factory-reset-now.target` will be started at boot whenever a factory reset is + requested. Services ordered before this target do the work of factory resetting + the system. Once these services are done, + [`systemd-factory-reset-complete.service`](https://www.freedesktop.org/software/systemd/man/latest/systemd-factory-reset-complete.service.html) + marks the factory reset operation as completed. The boot process may then + continue. + +* The + [`systemd-factory-reset`](https://www.freedesktop.org/software/systemd/man/latest/systemd-factory-reset.html) + tool can be used to query the current state of the factory request mechanism, + i.e. whether a factory reset is currently being executed, or if one has been + requested for the next boot. It also provides the + `/run/systemd/io.systemd.FactoryReset` Varlink service for the same purpose. + Early boot services that wish to participate in factory reset should use this + service to determine whether the system is currently being reset. + +* Not all systems will support factory reset. It's possible that there's nothing + listening for the factory reset request, and nothing happens before + `factory-reset-now.target` is reached. To avoid this situation, factory reset + should only be requested if the `/run/systemd/factory-reset-supported` stamp + file exists. Early boot services that participate in factory reset can create + this file if they determine that they have a meaningful amount of work to do. + +* The + [`systemd-logind.service(8)`](https://www.freedesktop.org/software/systemd/man/latest/systemd-logind.service.html) + unit supports automatically binding factory reset to special keypresses + (typically long presses). See the + [`logind.conf(5)`](https://www.freedesktop.org/software/systemd/man/latest/logind.conf.html) + man page. + +## Implementation for UEFI systems + +systemd also provides an implementation of this mechanism for UEFI-based systems. +This implementation can act completely standalone for distributions that rely on +tools like `systemd-repart`, but it can also be extended to meet other needs. + +The UEFI support works as follows: * The [`systemd-factory-reset-request.service`](https://www.freedesktop.org/software/systemd/man/latest/systemd-factory-reset-request.service.html) - unit is typically invoked via `factory-reset.target`. It requests a factory - reset operation for the next boot by setting the `FactoryResetRequest` EFI + unit is invoked via `factory-reset.target`. It requests a factory reset + operation for the next boot by setting the `FactoryResetRequest` EFI variable. The EFI variable contains information about the requesting OS, so that multi-boot scenarios are somewhat covered. @@ -41,79 +85,36 @@ Specifically, the following concepts are available: invalidating all prior keys associated with the security chip and generating a new seed key. -* The - [`systemd-factory-reset-reboot.service`](https://www.freedesktop.org/software/systemd/man/latest/systemd-factory-reset-reboot.service.html) - unit automatically reboots the system as part of `factory-reset.target`. It - is ordered after `systemd-tpm2-clear.service` and - `systemd-factory-reset-request.service` in order to initiate the reboot that - is supposed to execute the factory reset operations. - -* The `factory-reset-now.target` unit is started at boot whenever a factory - reset is requested for the boot. A factory reset may be requested via a - kernel command line option (`systemd.factory_reset=1`) or via the UEFI - variable `FactoryResetRequest` (see above). The - `systemd-factory-reset-generator` unit generator checks both these conditions - and adds `factory-reset-now.target` to the boot transaction, already in the - initial RAM disk (initrd). - -* The - [`systemd-factory-reset-complete.service`](https://www.freedesktop.org/software/systemd/man/latest/systemd-factory-reset-complete.service.html) - unit is invoked after `factory-reset-now.target` and marks the factory reset - operation as complete. The boot process then may continue. - * The [`systemd-repart`](https://www.freedesktop.org/software/systemd/man/latest/systemd-repart.html) - tool can take the factory reset logic into account. Either on explicit - request via the `--factory-reset=` logic, or automatically derived from the - aforementioned kernel command line switch and EFI variable. When invoked for - factory reset it will securely erase all partitions marked for that via the + tool is one of the early-boot services that do the work of factory resetting + the system. In normal operation, it starts on every boot. When invoked during + a factory reset, it will erase all partitions marked for that via the `FactoryReset=` setting in its partition definition files. Once that is - complete it will execute the usual setup operation, i.e. format new - partitions again. - -* The - [`systemd-logind.service(8)`](https://www.freedesktop.org/software/systemd/man/latest/systemd-logind.service.html) - unit supports automatically binding factory reset to special keypresses - (typically long presses), see the - [`logind.conf(5)`](https://www.freedesktop.org/software/systemd/man/latest/logind.conf.html) - man page. - -* The - [`systemd-factory-reset`](https://www.freedesktop.org/software/systemd/man/latest/systemd-factory-reset.html) - tool can be used to query the current state of the factory request mechanism, - i.e. whether a factory reset is currently being executed, or if one has been - requested for the next boot. - -* The `/run/systemd/io.systemd.FactoryReset` Varlink service provides two IPC - APIs for working with factory reset: it permits querying whether the local - system supports requesting a factory reset by starting - `factory-reset.target`. This may be used by UIs to hide or show in the UI an - interface to request a factory reset. The Varlink IPC service also reports - the current factory reset state, much like the `systemd-factory-reset` tool - mentioned above. This may be used by various early boot services that - potentially intent to reset system state during a factory reset operation. - -## Exposure in the UI - -If a graphical UI shall expose a factory reset operation it should first check -if requesting a factory reset is supported at all via the Varlink service -mentioned above. Once a factory reset shall be executed it shall ask for -activation of the `factory-reset.target` unit. - -Alternatively, `systemd-logind.service`'s hotkey support may be used, for -example to request factory reset if the reboot button is pressed for a long -time. + complete, it will resume its usual setup operation, i.e. reformatting the + empty partition with a file system. If any partition definitions have + `FactoryReset=` enabled, `systemd-repart` will create the + `/run/systemd/factory-reset-supported` stamp file. ## Support for non-UEFI Systems -The above is a relatively bespoke solution for EFI systems. It uses EFI -variables as stateful memory to request the factory reset on the next boot. +On non-EFI systems, the `FactoryResetRequest` EFI variable cannot be used to +communicate the factory reset request to the next boot. Instead, a service that +somehow stores the request should be plugged into `factory-reset.target`. At +boot, the request should then be fed back into the booted kernel via the +`systemd.factory_reset=1` kernel command line option. -On non-EFI systems, a different mechanism should be devised. A service -requesting the factory request can then be plugged into -`factory-reset.target`. At boot the request should then be fed back to the -booted kernel via the `systemd.factory_reset=1` kernel command line option, in -order to execute the reset operation. +## Exposure in the UI + +If a graphical UI shall expose a factory reset operation, it should first check +if requesting a factory reset is supported at all. This can be achieved by +checking whether `/run/systemd/factory-reset-supported` exists. Once the end-user +triggers a factory reset, the UI can start the process by asking systemd to +activate the `factory-reset.target` unit. + +Alternatively, `systemd-logind.service`'s hotkey support may be used. For +example, it can be configured to request factory reset if the reboot button is +pressed for a long time. ## Support for Resetting other Resources than Partitions + TPM @@ -122,16 +123,34 @@ partitions (via `systemd-repart`, see above) and reset the TPM (via `systemd-tpm2-clear.service`, see above). In some cases other resources shall be reset/erased too. To support that, -define your own service and plug it into `factory-reset-now.target`, ensuring -it is ordered before that. +define your own service and plug it into `factory-reset-now.target`. Ensure that +your service is ordered before the target! + +If your service should be enough to enable factory reset support, it should be +create `/run/systemd/factory-reset-supported` on every boot. Order the service +before `factory-reset.target`. You can use the Varlink API to determine at +runtime whether or not your service needs to perform the factory reset. ## Factory Reset via Boot Menu -Factory reset can also be requested via the boot menu. A simple factory reset -(that does not touch the TPM) at boot can be requested via a boot menu item -containing the `systemd.factory_reset=1` kernel command line option. A more -comprehensive factory reset operation (that also erases the TPM) can be -requested by booting with `rd.systemd.unit=factory-reset.target`. Note that the -latter will require one reboot (required since that's how TPM resets work), -while the former will reset state and continue running without an additional -reboot. +Factory reset can also be requested via the boot menu, by booting with certain +kernel command line arguments. The specifics vary by distribution, but here +are some pointers: + +The most potable solution would be to boot with +`rd.systemd.unit=factory-reset.target` set. This will execute the entire factory +reset process from within the initrd, including a reboot. To preserve the state +of the TPM, you can pass `systemd.tpm2_allow_clear=false`. + +Depending on the way your distribution uses the factory reset integration points, +a simpler case may be possible. Booting with `systemd.factory_reset=1` will +bypass `factory-reset.target` entirely, and skip a reboot. However, bear in mind +that this may have unintended consequences: some firmware or hardware may not be +completely reset this way, including the TPM. + +Note that the portable solution requires that distributions include their entire +factory reset integration in the initrd. If that is undesirable, alternatives +do exist. For instance, image-based distributions that separate `/usr` from the +rootfs can use something like `root=tmpfs systemd.unit=factory-reset.target` to +trigger the factory reset from the real `/usr`. + diff --git a/src/factory-reset/factory-reset-tool.c b/src/factory-reset/factory-reset-tool.c index e5eaed2efaa..ba428dd8ed8 100644 --- a/src/factory-reset/factory-reset-tool.c +++ b/src/factory-reset/factory-reset-tool.c @@ -142,6 +142,9 @@ static int verb_request(int argc, char *argv[], void *userdata) { return 0; } + if (f == FACTORY_RESET_UNSUPPORTED || access("/run/systemd/factory-reset-supported", F_OK) < 0) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Factory reset is unsupported, refusing to request it."); if (!is_efi_boot()) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not an EFI boot, requesting factory reset via EFI variable not supported."); @@ -316,18 +319,6 @@ static int vl_method_get_factory_reset_mode(sd_varlink *link, sd_json_variant *p return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("mode", factory_reset_mode_to_string(f))); } -static int vl_method_can_request_factory_reset(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - int r; - - assert(parameters); - - r = sd_varlink_dispatch(link, parameters, /* table= */ NULL, /* userdata= */ NULL); - if (r != 0) - return r; - - return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("supported", is_efi_boot())); -} - static int varlink_service(void) { int r; @@ -344,8 +335,7 @@ static int varlink_service(void) { r = sd_varlink_server_bind_method_many( varlink_server, - "io.systemd.FactoryReset.GetFactoryResetMode", vl_method_get_factory_reset_mode, - "io.systemd.FactoryReset.CanRequestFactoryReset", vl_method_can_request_factory_reset); + "io.systemd.FactoryReset.GetFactoryResetMode", vl_method_get_factory_reset_mode); if (r < 0) return log_error_errno(r, "Failed to bind Varlink methods: %m"); diff --git a/src/repart/repart.c b/src/repart/repart.c index 9827d032b5c..4b75b918d1d 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -140,6 +140,7 @@ static char *arg_image = NULL; static char **arg_definitions = NULL; static bool arg_discard = true; static bool arg_can_factory_reset = false; +static bool arg_stamp_factory_reset = false; static int arg_factory_reset = -1; static sd_id128_t arg_seed = SD_ID128_NULL; static bool arg_randomize = false; @@ -8054,6 +8055,7 @@ static int parse_argv(int argc, char *argv[], X509 **ret_certificate, EVP_PKEY * ARG_DISCARD, ARG_FACTORY_RESET, ARG_CAN_FACTORY_RESET, + ARG_STAMP_FACTORY_RESET, ARG_ROOT, ARG_IMAGE, ARG_IMAGE_POLICY, @@ -8100,6 +8102,7 @@ static int parse_argv(int argc, char *argv[], X509 **ret_certificate, EVP_PKEY * { "discard", required_argument, NULL, ARG_DISCARD }, { "factory-reset", required_argument, NULL, ARG_FACTORY_RESET }, { "can-factory-reset", no_argument, NULL, ARG_CAN_FACTORY_RESET }, + { "stamp-factory-reset", no_argument, NULL, ARG_STAMP_FACTORY_RESET }, { "root", required_argument, NULL, ARG_ROOT }, { "image", required_argument, NULL, ARG_IMAGE }, { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, @@ -8202,6 +8205,10 @@ static int parse_argv(int argc, char *argv[], X509 **ret_certificate, EVP_PKEY * arg_can_factory_reset = true; break; + case ARG_STAMP_FACTORY_RESET: + arg_stamp_factory_reset = true; + break; + case ARG_ROOT: r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root); if (r < 0) @@ -9262,14 +9269,15 @@ static int run(int argc, char *argv[]) { return r; context->from_scratch = r > 0; /* Starting from scratch */ - if (arg_can_factory_reset) { - r = context_can_factory_reset(context); + r = context_can_factory_reset(context); + if (r < 0) + return r; + if (arg_can_factory_reset) + return r == 0 ? EXIT_FAILURE : 0; + if (arg_stamp_factory_reset && r > 0) { + r = touch("/run/systemd/factory-reset-supported"); if (r < 0) - return r; - if (r == 0) - return EXIT_FAILURE; - - return 0; + return log_error_errno(r, "Failed to create /run/systemd/factory-reset-supported file: %m"); } r = context_factory_reset(context); diff --git a/units/factory-reset.target b/units/factory-reset.target index 68d505c877b..f9a777db99b 100644 --- a/units/factory-reset.target +++ b/units/factory-reset.target @@ -8,7 +8,8 @@ # (at your option) any later version. [Unit] -Description=Factory Reset Initiation +Description=Initiate Factory Reset Documentation=man:systemd.special(7) Wants=systemd-factory-reset-reboot.service Before=systemd-factory-reset-reboot.service +AssertPathExists=/run/systemd/factory-reset-supported diff --git a/units/systemd-repart.service b/units/systemd-repart.service index 85a2a9b8718..9c9a8aba2c1 100644 --- a/units/systemd-repart.service +++ b/units/systemd-repart.service @@ -22,14 +22,14 @@ ConditionDirectoryNotEmpty=|/sysusr/usr/local/lib/repart.d DefaultDependencies=no Wants=modprobe@loop.service modprobe@dm_mod.service After=initrd-usr-fs.target modprobe@loop.service modprobe@dm_mod.service systemd-tpm2-setup-early.service -Before=initrd-root-fs.target factory-reset-now.target +Before=initrd-root-fs.target factory-reset-now.target factory-reset.target Conflicts=shutdown.target initrd-switch-root.target Before=shutdown.target initrd-switch-root.target [Service] Type=oneshot RemainAfterExit=yes -ExecStart=systemd-repart --dry-run=no +ExecStart=systemd-repart --dry-run=no --stamp-factory-reset # The tool returns 76 if it can't find the root block device SuccessExitStatus=76