Watch
1
0
Fork
You've already forked souveraine
0

sessiond: gate the blank actuator on logind LockedHint

This commit is contained in:
Fimeg 2026-07-26 20:16:58 -04:00
commit 2c1ee8bc00
2 changed files with 40 additions and 3 deletions

View file

@ -55,7 +55,7 @@ use tracing::{info, warn};
/// path, so monitoring the alias yields a subscription that is silently never /// path, so monitoring the alias yields a subscription that is silently never
/// delivered. That is the failure the shell already hit /// delivered. That is the failure the shell already hit
/// (`[session-events] no session path returned`). /// (`[session-events] no session path returned`).
fn resolve_session_path() -> Result<String> { pub fn resolve_session_path() -> Result<String> {
let uid = std::fs::metadata("/proc/self") let uid = std::fs::metadata("/proc/self")
.map(|m| m.uid()) .map(|m| m.uid())
.context("reading our own uid")?; .context("reading our own uid")?;
@ -121,7 +121,7 @@ fn resolve_session_path() -> Result<String> {
/// One-shot read, so the machine starts from the truth rather than from a /// One-shot read, so the machine starts from the truth rather than from a
/// default that happens to be wrong until the first signal arrives. /// default that happens to be wrong until the first signal arrives.
fn read_locked_hint(path: &str) -> Result<bool> { pub fn read_locked_hint(path: &str) -> Result<bool> {
let out = Command::new("busctl") let out = Command::new("busctl")
.args([ .args([
"--system", "--system",

View file

@ -205,6 +205,21 @@ fn spawn_lock_hint_watcher(shared: &Arc<Shared>) {
/// Carry out one decision. Failures are recorded in the forensic trail — a /// Carry out one decision. Failures are recorded in the forensic trail — a
/// blank that did not happen is a state divergence, not a log line to lose. /// blank that did not happen is a state divergence, not a log line to lose.
/// Ask logind whether this session is locked. Fail closed: if the question
/// cannot be answered, the answer is "not locked", because the failure this
/// guards is a dark screen on an open session.
fn session_is_locked() -> bool {
match crate::sessiond::lockhint::resolve_session_path()
.and_then(|p| crate::sessiond::lockhint::read_locked_hint(&p))
{
Ok(locked) => locked,
Err(e) => {
warn!("could not read LockedHint ({e:#}); treating the session as unlocked");
false
}
}
}
fn execute(shared: &Arc<Shared>, action: Action) { fn execute(shared: &Arc<Shared>, action: Action) {
// The lock is not a shell command, so it does not go through the executor // The lock is not a shell command, so it does not go through the executor
// table below. It is the authority acting as the authority: the shell owns // table below. It is the authority acting as the authority: the shell owns
@ -265,7 +280,29 @@ fn execute(shared: &Arc<Shared>, action: Action) {
// dim is what pinned brightness at 10/255. // dim is what pinned brightness at 10/255.
Action::Dim => ("brightnessctl", vec!["set", "10"], "panel-dim"), Action::Dim => ("brightnessctl", vec!["set", "10"], "panel-dim"),
Action::Restore => unreachable!("handled above; the restore carries a value"), Action::Restore => unreachable!("handled above; the restore carries a value"),
Action::Blank => (DPMS_EXECUTOR, vec!["off"], "panel-off"), Action::Blank => {
// The invariant, enforced where it cannot be reasoned around: the
// panel does not go dark unless logind says this session is
// locked, right now. The machine's own `Locked` is a belief, and a
// belief that entered by one route (locked_ack) and can only leave
// by another (logind's falling edge) gets stuck — observed
// 2026-07-26: machine `locked`, LockedHint `no`, panel off, and
// double-tap waking straight to an unlocked screen.
//
// Checked at the actuator, not at the decision, because every path
// to a dark panel ends here and only here.
if !session_is_locked() {
warn!("refusing to blank: logind says this session is not locked");
shared.lock().device_state.record_error(
"device-state",
"panel-off",
"refused: session not locked per logind",
);
request_session_lock(shared);
return;
}
(DPMS_EXECUTOR, vec!["off"], "panel-off")
}
Action::Lock => unreachable!("handled above; the lock is not a shell command"), Action::Lock => unreachable!("handled above; the lock is not a shell command"),
}; };