Skip to content

A laptop woken at the passphrase prompt powers off at 3% instead of running flat - #645

Open
DevomB wants to merge 2 commits into
mainfrom
sealed-battery-off
Open

DevomB wants to merge 2 commits into
mainfrom
sealed-battery-off

Conversation

@DevomB

@DevomB DevomB commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

The battery's ACPI driver wakes an s2idle machine when the charge falls to the firmware's alarm level or goes critical. In v6.18's drivers/acpi/battery.c, acpi_battery_update() calls acpi_pm_wakeup_event(), and the device is a wakeup source from the moment it is added. A laptop asleep in a bag then wakes to the state passphrase prompt with its lid still shut.

At that prompt the power service can't keep its 3% rule:

  • It may wait on the suspended state partition, as any program started then can.
  • If it doesn't wait, its shutdown asks kryptikd to stop each zone, then runs the system's shutdown, which syncs, stops kryptikd and unmounts. None of that can finish with the partition suspended.

kryptikd already answers the screen lock while the partition is suspended, reading sysfs alone. With this change it also looks at the battery:

  • When: every 30 s, from the end of each sleep (woken or refused) until the passphrase is given. A daemon started again at the prompt looks from its start.
  • The rule: power.sh's. The fullest of the machine's own batteries (not a mouse's, scope Device; one not present is ignored) at or under 3%, and running down: one says it discharges, or none says anything and no adapter is online.
  • The action: seen twice running, it powers off with reboot(2). The zones are not stopped first:
    • they are frozen, and the partition and their volumes have no key;
    • everything was written out before the seal;
    • a sync would wait on the partition for good.
  • The count carries across a sleep, so a machine put back to sleep soon after each wake still powers off. It resets once the partition is back.
  • A refused power-off is logged once and not retried.

The power service gains sealed(), read with shell builtins alone. While it holds, the service leaves the power-off to kryptikd.

Tests:

  • critical_in is held to every case in tools/tests/power.sh that decides power.sh's critical=, so the two readers agree.
  • The twice-running count has its own test.
  • tools/tests/power.sh runs sealed() against stand-in device-mapper trees, and checks that the loop's power-off waits on it.
  • No guest check yet. The suspend suite would need test_power loaded, the battery set to 2% with builtins while sealed, and "Power down" expected after the wake.

docs/design/laptop.md gets a paragraph, "The battery while the state partition waits".

New attack surface: none. The daemon reads /sys/class/power_supply and calls reboot(2), as root, which it already is. No new input from zones or the session reaches it.

…unning flat

The battery's ACPI driver wakes an s2idle machine when the charge falls to
the firmware's alarm level or goes critical (drivers/acpi/battery.c,
acpi_battery_update). The laptop then waits at the state passphrase
prompt, lid still shut, and the power service waits on the suspended
state partition, as every program started then does, so its 3% power-off
never comes and the laptop runs flat awake.

kryptikd already answers the lock while the partition is suspended, from
sysfs alone. From each sleep's end until the passphrase is given (or from
its start, for a daemon started again at the prompt) it now looks at the
battery every 30 s, by power.sh's rule: the fullest of the machine's own
batteries at or under 3% and running down. Seen twice running, it powers
off with reboot(2). The zones are not stopped first: they are frozen, the
partition and their volumes have no key, everything was written out
before the seal, and a sync would wait on the partition for good.

critical_in is held to tools/tests/power.sh's cases in a unit test, and
the twice-running count to its own.
… and the count carries across a sleep

The suspend suite's guest runs programs at the passphrase prompt, so the
power service's loop is not sure to stall there. Where it does not, its
shutdown would ask kryptikd to stop each zone and then run the system's
shutdown, which syncs, stops kryptikd and unmounts: none of that can
finish with the state partition suspended, and kryptikd's own power-off
would die with it. sealed() reads /sys/block with builtins alone, and
while it holds the service leaves the power-off to kryptikd.

From 93's read: kryptikd's count of critical looks reset at each sleep's
end, so a machine put back to sleep within 30 s of each wake would never
see two in one wake. It now resets only once the partition is back.

tools/tests/power.sh runs sealed() against stand-in device-mapper trees,
and checks that the loop's power-off waits on it.
@DevomB
DevomB marked this pull request as ready for review October 23, 2026 12:29
DevomB added a commit that referenced this pull request Oct 23, 2026
…se after a wake powers the machine off, with the state sealed

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant