session-lock: fix potential race

Currently the session lock client has no 100% safe way to know when it
is safe to suspend after requesting that the session be locked.

For a suspend to be safe the compositor must have either blanked or
rendered a lock surface on all outputs before suspending. This is
because the current framebuffer on suspend appears to be saved and
displayed again after suspend, at least on my Linux system.

If a new "locked" frame for all outputs is not rendered before suspend,
an "unlocked" frame or frames will likely be briefly displayed on resume
before the lock surfaces are rendered or the screen is blanked.

To fix this, wait until a lock surface has been rendered on all outputs,
or if that times out until all outputs have been blanked, before sending
the locked event to the client.

Resolving this race on the compositor side without protocol changes
is the most effective way to avoid this potential information leak,
regardless of which session lock client is used.
This commit is contained in:
Isaac Freund
2022-12-22 22:27:17 +01:00
parent 5d4c2f2fbd
commit 8f8d94aa45
5 changed files with 148 additions and 28 deletions

View File

@ -63,7 +63,13 @@ pub fn renderOutput(output: *Output) void {
server.renderer.begin(@intCast(u32, output.wlr_output.width), @intCast(u32, output.wlr_output.height));
if (server.lock_manager.locked) {
// In order to avoid flashing a blank black screen as the session is locked
// continue to render the unlocked session until either a lock surface is
// created or waiting for lock surfaces times out.
if (server.lock_manager.state == .locked or
(server.lock_manager.state == .waiting_for_lock_surfaces and output.lock_surface != null) or
server.lock_manager.state == .waiting_for_blank)
{
server.renderer.clear(&[_]f32{ 0, 0, 0, 1 }); // solid black
// TODO: this isn't frame-perfect if the output mode is changed. We
@ -87,10 +93,19 @@ pub fn renderOutput(output: *Output) void {
output.wlr_output.renderSoftwareCursors(null);
server.renderer.end();
output.wlr_output.commit() catch
output.wlr_output.commit() catch {
log.err("output commit failed for {s}", .{output.wlr_output.name});
return;
};
output.lock_render_state = if (output.lock_surface != null) .lock_surface else .blanked;
if (server.lock_manager.state != .locked) {
server.lock_manager.maybeLock();
}
return;
}
output.lock_render_state = .unlocked;
// Find the first visible fullscreen view in the stack if there is one
var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, renderFilter);