feat: close the chord that let a teacher hibernate the room
Ctrl+Alt+Delete opened Plasma's session screen and a teacher hibernated a classroom computer from it. A hibernated room cannot be recovered by anybody standing in front of it, which on a Sunday morning is everybody. logout=false was already set and did not stop it. That restriction governs Plasma's own actions — what plasmashell draws in a menu or an applet. The chord is a KGlobalAccel shortcut registered by ksmserver and it asks D-Bus for the greeter without consulting a Plasma action at all. A restriction on a menu cannot see a key. Three layers, because each one alone has a way past it. The key. kglobalshortcutsrc unbinds every ksmserver session action and PowerDevil's Sleep, Hibernate, PowerDown and PowerOff, each entry marked immutable. It is deliberately not in the xdg directory with the other settings: kglobalacceld opens that file as a SimpleConfig, so it reads one file in the teacher's home and no system directory at all. Measured — with it in the cascade, kreadconfig6 reported the chord unbound while the shortcut server went on answering Ctrl+Alt+Del. classroom-desktop-reset copies the package's copy into place before the shortcut server starts, the same way the panel is copied out of the package at every login. The policy. A polkit rule refuses login1.suspend* and .hibernate* for the classroom user and for nobody else. This is the layer that matters: a key can be unbound and anything reaching logind can still ask. Refusing here rather than masking sleep.target keeps a technician's own account working, and is also what tells Plasma to stop drawing the button — CanSuspend and CanHibernate come back no. Hybrid sleep and suspend-then-hibernate have no action of their own in systemd 257; both are authorised through hibernate, so denying it denies all three. The greeter. Its D-Bus activation is shadowed with /bin/false, the same mechanism already used for the old notification daemon. Layer one removes the caller, this removes the callee. Measured in the lab, from the package, after a reboot: the escape reproduced first with the shadow removed — the greeter really does start — and then refused, exited 1, with it back. The chord reports nothing bound. systemctl suspend as the classroom user is denied. The maintenance chord and the display chord both still answer, by key code, after the shortcut file is replaced wholesale. A root process may still suspend, hibernate, reboot and power off. 182 checks pass across a logout and back. Screen locking stays disabled, the power button still does nothing, and reboot and shut down are not newly denied to anybody. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8db5e8dd9f
commit
23dff0b7b4
@@ -48,6 +48,60 @@ notification_owner() {
|
||||
[[ -n "$pid" ]] && in_lab "cat /proc/$pid/comm"
|
||||
}
|
||||
|
||||
# How many keys KGlobalAccel has bound to an action, asked of the running
|
||||
# shortcut server rather than read out of a file. This is the question
|
||||
# Ctrl+Alt+Delete actually turns on: a file can say anything, and
|
||||
# kglobalacceld is what answers a key press.
|
||||
shortcut_of() {
|
||||
docker exec -u classroom -e XDG_RUNTIME_DIR=/run/user/1000 -e HOME=/home/classroom \
|
||||
-e DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus "$NAME" \
|
||||
busctl --user --no-pager call org.kde.kglobalaccel /kglobalaccel \
|
||||
org.kde.KGlobalAccel shortcut as 4 "$1" "$2" "" "" 2>/dev/null
|
||||
}
|
||||
|
||||
# How many keys are bound. "ai 0" is an empty array: nothing answers.
|
||||
shortcut_keys() { shortcut_of "$@" | awk '{print $2}'; }
|
||||
|
||||
# And which key, as Qt's combined key code, for the chords that have to
|
||||
# keep working.
|
||||
shortcut_key() { shortcut_of "$@" | awk '{print $3}'; }
|
||||
|
||||
# polkit's own verdict on a running process, which is the exact question
|
||||
# a teacher's session asks when something offers to suspend the machine.
|
||||
#
|
||||
# allowed it would go ahead
|
||||
# challenge it would ask for an administrator's password, which no
|
||||
# teacher has — this is what reboot and shut down already do
|
||||
# denied polkit refuses outright, which is the classroom rule
|
||||
#
|
||||
# Asked about a pid rather than by running pkcheck as somebody, because
|
||||
# the subject polkit judges is the calling process and its session.
|
||||
polkit_verdict() {
|
||||
in_lab "/usr/bin/pkcheck --action-id $2 --process $1 >/dev/null 2>&1;
|
||||
case \$? in 0) echo allowed ;; 1) echo denied ;; *) echo challenge ;; esac"
|
||||
}
|
||||
|
||||
teacher_session() { in_lab 'pgrep -x plasmashell | head -1'; }
|
||||
|
||||
# What logind tells the classroom user it may do, which is what Plasma
|
||||
# reads before deciding whether to draw a button.
|
||||
#
|
||||
# yes / challenge the operation is on offer
|
||||
# no policy refuses it
|
||||
# na this machine cannot do it at all
|
||||
#
|
||||
# A teacher must never see the first two. The lab container cannot
|
||||
# hibernate whatever the policy says, so it answers "na" there and "no" on
|
||||
# a real machine; both mean refused, and the polkit checks above are what
|
||||
# actually prove the rule.
|
||||
logind_offers() {
|
||||
case "$(in_lab "runuser -u classroom -- busctl --no-pager call org.freedesktop.login1 \
|
||||
/org/freedesktop/login1 org.freedesktop.login1.Manager $1" | cut -d'"' -f2)" in
|
||||
yes|challenge) echo offered ;;
|
||||
*) echo refused ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# logind answers for the running configuration, not for a file on disk.
|
||||
logind_property() {
|
||||
in_lab "busctl get-property org.freedesktop.login1 /org/freedesktop/login1 \
|
||||
@@ -145,6 +199,64 @@ check "logind ignores a long press" ignore "$(logind_property HandlePowerKeyLong
|
||||
check "logind has no idle action" ignore "$(logind_property IdleAction)"
|
||||
check "logind ignores the lid" ignore "$(logind_property HandleLidSwitch)"
|
||||
|
||||
section "Ctrl+Alt+Delete, and the sleep states"
|
||||
# The chord a teacher used to hibernate a room.
|
||||
check "Ctrl+Alt+Delete is bound to nothing" 0 "$(shortcut_keys ksmserver 'Log Out')"
|
||||
for action in 'Log Out Without Confirmation' LogOut 'Shut Down' Reboot \
|
||||
'Reboot Without Confirmation' 'Halt Without Confirmation'; do
|
||||
check "ksmserver/$action unbound" 0 "$(shortcut_keys ksmserver "$action")"
|
||||
done
|
||||
for action in Sleep Hibernate PowerDown PowerOff; do
|
||||
check "powerdevil/$action unbound" 0 "$(shortcut_keys org_kde_powerdevil "$action")"
|
||||
done
|
||||
# And the room's own chords are not collateral damage. The shortcut file
|
||||
# is replaced wholesale at login, so this is the check that says the
|
||||
# maintenance route survived it. 503316555 is Meta+Ctrl+Alt+Shift+K.
|
||||
check "the maintenance chord still answers" 503316555 \
|
||||
"$(shortcut_key ekklesia-admin-terminal.desktop _launch)"
|
||||
check "the display chord still answers" 503316548 \
|
||||
"$(shortcut_key ekklesia-display-settings.desktop _launch)"
|
||||
# The greeter itself, with nothing to start it.
|
||||
check "the session screen cannot be activated" 1 \
|
||||
"$(in_lab 'grep -c "^Exec=/bin/false" /usr/local/share/dbus-1/services/org.kde.LogoutPrompt.service 2>/dev/null || true')"
|
||||
# The policy layer, asked of the teacher's own running session.
|
||||
#
|
||||
# Hybrid sleep and suspend-then-hibernate have no polkit action of their
|
||||
# own in systemd 257: logind authorises both through the hibernate action,
|
||||
# so denying hibernate denies all three. The rule names the other two
|
||||
# spellings anyway, in case a future systemd splits them out.
|
||||
TEACHER="$(teacher_session)"
|
||||
for action in suspend suspend-multiple-sessions suspend-ignore-inhibit \
|
||||
hibernate hibernate-multiple-sessions hibernate-ignore-inhibit; do
|
||||
check "the teacher's session may not $action" denied \
|
||||
"$(polkit_verdict "$TEACHER" "org.freedesktop.login1.$action")"
|
||||
done
|
||||
# What the desktop is told, which is why no button is drawn for it.
|
||||
check "logind offers the classroom no suspend" refused "$(logind_offers CanSuspend)"
|
||||
check "logind offers the classroom no hibernate" refused "$(logind_offers CanHibernate)"
|
||||
check "logind offers the classroom no hybrid sleep" refused \
|
||||
"$(logind_offers CanHybridSleep)"
|
||||
check "logind offers the classroom no suspend-then-hibernate" refused \
|
||||
"$(logind_offers CanSuspendThenHibernate)"
|
||||
|
||||
# AND THE MACHINE IS NOT CRIPPLED FOR THE PERSON WHO MAINTAINS IT.
|
||||
#
|
||||
# The rule is scoped to the classroom user by name, so every other account
|
||||
# has to be able to do all of it. pid 1 stands in for a root process.
|
||||
for action in suspend hibernate reboot power-off; do
|
||||
check "a root process may still $action" allowed \
|
||||
"$(polkit_verdict 1 "org.freedesktop.login1.$action")"
|
||||
done
|
||||
# And the two the classroom user was deliberately NOT denied: ending up at
|
||||
# an administrator's password prompt is the existing behaviour, and it is
|
||||
# not this release's business to change it.
|
||||
for action in reboot power-off; do
|
||||
check "the classroom is not newly denied $action" challenge \
|
||||
"$(polkit_verdict "$TEACHER" "org.freedesktop.login1.$action")"
|
||||
done
|
||||
check "sleep is not masked machine-wide" 0 \
|
||||
"$(in_lab 'systemctl is-enabled sleep.target suspend.target hibernate.target 2>/dev/null | grep -c masked || true')"
|
||||
|
||||
section "lockdown still in force"
|
||||
for key in run_command shell_access logout; do
|
||||
check "$key" false "$(session_config --file kdeglobals --group 'KDE Action Restrictions' --key "$key")"
|
||||
|
||||
Reference in New Issue
Block a user