CVE-2022-26530: Insufficient Information (NVD-CWE-noinfo)
value: swaylock before 1.6 allows attackers to trigger a crash and achieve unlocked access to a Wayland compositor.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 9.1
Published: 2022-04-03T23:15Z
Repository: https://github.com/swaywm/swaylock
Language: C
Files changed: include/swaylock.h, main.c, meson.build
Commit date: 2022-02-02 15:30:45 +0100
Commit message: Add support for ext-session-lock-v1

This is a new protocol to lock the session [1]. It should be more
reliable than layer-shell + input-inhibitor.

[1]: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/131

Diff: diff --git a/include/swaylock.h b/include/swaylock.h
index 3993fe5..30f9117 100644
--- a/include/swaylock.h
+++ b/include/swaylock.h
@@ -87,10 +87,12 @@ struct swaylock_state {
 	struct swaylock_xkb xkb;
 	enum auth_state auth_state;
 	int failed_attempts;
 	bool run_display;
 	struct zxdg_output_manager_v1 *zxdg_output_manager;
+	struct ext_session_lock_manager_v1 *ext_session_lock_manager_v1;
+	struct ext_session_lock_v1 *ext_session_lock_v1;
 };
 
 struct swaylock_surface {
 	cairo_surface_t *image;
 	struct swaylock_state *state;
@@ -99,10 +101,11 @@ struct swaylock_surface {
 	struct zxdg_output_v1 *xdg_output;
 	struct wl_surface *surface;
 	struct wl_surface *child; // surface made into subsurface
 	struct wl_subsurface *subsurface;
 	struct zwlr_layer_surface_v1 *layer_surface;
+	struct ext_session_lock_surface_v1 *ext_session_lock_surface_v1;
 	struct pool_buffer buffers[2];
 	struct pool_buffer indicator_buffers[2];
 	struct pool_buffer *current_buffer;
 	bool frame_pending, dirty;
 	uint32_t width, height;
diff --git a/main.c b/main.c
index 057eb87..3c01bfb 100644
--- a/main.c
+++ b/main.c
@@ -24,10 +24,11 @@
 #include "seat.h"
 #include "swaylock.h"
 #include "wlr-input-inhibitor-unstable-v1-client-protocol.h"
 #include "wlr-layer-shell-unstable-v1-client-protocol.h"
 #include "xdg-output-unstable-v1-client-protocol.h"
+#include "ext-session-lock-v1-client-protocol.h"
 
 static uint32_t parse_color(const char *color) {
 	if (color[0] == '#') {
 		++color;
 	}
@@ -95,10 +96,13 @@ static void daemonize(void) {
 static void destroy_surface(struct swaylock_surface *surface) {
 	wl_list_remove(&surface->link);
 	if (surface->layer_surface != NULL) {
 		zwlr_layer_surface_v1_destroy(surface->layer_surface);
 	}
+	if (surface->ext_session_lock_surface_v1 != NULL) {
+		ext_session_lock_surface_v1_destroy(surface->ext_session_lock_surface_v1);
+	}
 	if (surface->surface != NULL) {
 		wl_surface_destroy(surface->surface);
 	}
 	destroy_buffer(&surface->buffers[0]);
 	destroy_buffer(&surface->buffers[1]);
@@ -107,10 +111,11 @@ static void destroy_surface(struct swaylock_surface *surface) {
 	wl_output_destroy(surface->output);
 	free(surface);
 }
 
 static const struct zwlr_layer_surface_v1_listener layer_surface_listener;
+static const struct ext_session_lock_surface_v1_listener ext_session_lock_surface_v1_listener;
 
 static cairo_surface_t *select_image(struct swaylock_state *state,
 		struct swaylock_surface *surface);
 
 static bool surface_is_opaque(struct swaylock_surface *surface) {
@@ -118,11 +123,11 @@ static bool surface_is_opaque(struct swaylock_surface *surface) {
 		return cairo_surface_get_content(surface->image) == CAIRO_CONTENT_COLOR;
 	}
 	return (surface->state->args.colors.background & 0xff) == 0xff;
 }
 
-static void create_layer_surface(struct swaylock_surface *surface) {
+static void create_surface(struct swaylock_surface *surface) {
 	struct swaylock_state *state = surface->state;
 
 	surface->image = select_image(state, surface);
 
 	surface->surface = wl_compositor_create_surface(state->compositor);
@@ -132,26 +137,32 @@ static void create_layer_surface(struct swaylock_surface *surface) {
 	assert(surface->child);
 	surface->subsurface = wl_subcompositor_get_subsurface(state->subcompositor, surface->child, surface->surface);
 	assert(surface->subsurface);
 	wl_subsurface_set_sync(surface->subsurface);
 
-	surface->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
-			state->layer_shell, surface->surface, surface->output,
-			ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "lockscreen");
-	assert(surface->layer_surface);
-
-	zwlr_layer_surface_v1_set_size(surface->layer_surface, 0, 0);
-	zwlr_layer_surface_v1_set_anchor(surface->layer_surface,
-			ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
-			ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
-			ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
-			ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
-	zwlr_layer_surface_v1_set_exclusive_zone(surface->layer_surface, -1);
-	zwlr_layer_surface_v1_set_keyboard_interactivity(
-			surface->layer_surface, true);
-	zwlr_layer_surface_v1_add_listener(surface->layer_surface,
-			&layer_surface_listener, surface);
+	if (state->ext_session_lock_v1) {
+		surface->ext_session_lock_surface_v1 = ext_session_lock_v1_get_lock_surface(
+				state->ext_session_lock_v1, surface->surface, surface->output);
+		ext_session_lock_surface_v1_add_listener(surface->ext_session_lock_surface_v1,
+				&ext_session_lock_surface_v1_listener, surface);
+	} else {
+		surface->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
+				state->layer_shell, surface->surface, surface->output,
+				ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "lockscreen");
+
+		zwlr_layer_surface_v1_set_size(surface->layer_surface, 0, 0);
+		zwlr_layer_surface_v1_set_anchor(surface->layer_surface,
+				ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
+				ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
+				ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
+				ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
+		zwlr_layer_surface_v1_set_exclusive_zone(surface->layer_surface, -1);
+		zwlr_layer_surface_v1_set_keyboard_interactivity(
+				surface->layer_surface, true);
+		zwlr_layer_surface_v1_add_listener(surface->layer_surface,
+				&layer_surface_listener, surface);
+	}
 
 	if (surface_is_opaque(surface) &&
 			surface->state->args.mode != BACKGROUND_MODE_CENTER &&
 			surface->state->args.mode != BACKGROUND_MODE_FIT) {
 		struct wl_region *region =
@@ -159,11 +170,13 @@ static void create_layer_surface(struct swaylock_surface *surface) {
 		wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX);
 		wl_surface_set_opaque_region(surface->surface, region);
 		wl_region_destroy(region);
 	}
 
-	wl_surface_commit(surface->surface);
+	if (!state->ext_session_lock_v1) {
+		wl_surface_commit(surface->surface);
+	}
 }
 
 static void layer_surface_configure(void *data,
 		struct zwlr_layer_surface_v1 *layer_surface,
 		uint32_t serial, uint32_t width, uint32_t height) {
@@ -186,10 +199,27 @@ static void layer_surface_closed(void *data,
 static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
 	.configure = layer_surface_configure,
 	.closed = layer_surface_closed,
 };
 
+static void ext_session_lock_surface_v1_handle_configure(void *data,
+		struct ext_session_lock_surface_v1 *lock_surface, uint32_t serial,
+		uint32_t width, uint32_t height) {
+	struct swaylock_surface *surface = data;
+	surface->width = width;
+	surface->height = height;
+	surface->indicator_width = 0;
+	surface->indicator_height = 0;
+	ext_session_lock_surface_v1_ack_configure(lock_surface, serial);
+	render_frame_background(surface);
+	render_frame(surface);
+}
+
+static const struct ext_session_lock_surface_v1_listener ext_session_lock_surface_v1_listener = {
+	.configure = ext_session_lock_surface_v1_handle_configure,
+};
+
 static const struct wl_callback_listener surface_frame_listener;
 
 static void surface_frame_handle_done(void *data, struct wl_callback *callback,
 		uint32_t time) {
 	struct swaylock_surface *surface = data;
@@ -300,10 +330,25 @@ struct zxdg_output_v1_listener _xdg_output_listener = {
 	.done = handle_xdg_output_done,
 	.name = handle_xdg_output_name,
 	.description = handle_xdg_output_description,
 };
 
+static void ext_session_lock_v1_handle_locked(void *data, struct ext_session_lock_v1 *lock) {
+	// Who cares
+}
+
+static void ext_session_lock_v1_handle_finished(void *data, struct ext_session_lock_v1 *lock) {
+	swaylock_log(LOG_ERROR, "Failed to lock session -- "
+			"is another lockscreen running?");
+	exit(2);
+}
+
+static const struct ext_session_lock_v1_listener ext_session_lock_v1_listener = {
+	.locked = ext_session_lock_v1_handle_locked,
+	.finished = ext_session_lock_v1_handle_finished,
+};
+
 static void handle_global(void *data, struct wl_registry *registry,
 		uint32_t name, const char *interface, uint32_t version) {
 	struct swaylock_state *state = data;
 	if (strcmp(interface, wl_compositor_interface.name) == 0) {
 		state->compositor = wl_registry_bind(registry, name,
@@ -339,13 +384,16 @@ static void handle_global(void *data, struct wl_registry *registry,
 		surface->output_global_name = name;
 		wl_output_add_listener(surface->output, &_wl_output_listener, surface);
 		wl_list_insert(&state->surfaces, &surface->link);
 
 		if (state->run_display) {
-			create_layer_surface(surface);
+			create_surface(surface);
 			wl_display_roundtrip(state->display);
 		}
+	} else if (strcmp(interface, ext_session_lock_manager_v1_interface.name) == 0) {
+		state->ext_session_lock_manager_v1 = wl_registry_bind(registry, name,
+				&ext_session_lock_manager_v1_interface, 1);
 	}
 }
 
 static void handle_global_remove(void *data, struct wl_registry *registry,
 		uint32_t name) {
@@ -1180,24 +1228,38 @@ int main(int argc, char **argv) {
 	}
 
 	struct wl_registry *registry = wl_display_get_registry(state.display);
 	wl_registry_add_listener(registry, &registry_listener, &state);
 	wl_display_roundtrip(state.display);
-	assert(state.compositor && state.layer_shell && state.shm);
-	if (!state.input_inhibit_manager) {
-		free(state.args.font);
-		swaylock_log(LOG_ERROR, "Compositor does not support the input "
-				"inhibitor protocol, refusing to run insecurely");
+
+	if (!state.compositor || !state.shm) {
+		swaylock_log(LOG_ERROR, "Missing wl_compositor or wl_shm");
+		return 1;
+	}
+
+	if (state.ext_session_lock_manager_v1) {
+		swaylock_log(LOG_DEBUG, "Using ext-session-lock-v1");
+		state.ext_session_lock_v1 = ext_session_lock_manager_v1_lock(state.ext_session_lock_manager_v1);
+		ext_session_lock_v1_add_listener(state.ext_session_lock_v1,
+				&ext_session_lock_v1_listener, &state);
+	} else if (state.layer_shell && state.input_inhibit_manager) {
+		swaylock_log(LOG_DEBUG, "Using wlr-layer-shell + wlr-input-inhibitor");
+		zwlr_input_inhibit_manager_v1_get_inhibitor(state.input_inhibit_manager);
+	} else {
+		swaylock_log(LOG_ERROR, "Missing ext-session-lock-v1, wlr-layer-shell "
+				"and wlr-input-inhibitor");
 		return 1;
 	}
 
-	zwlr_input_inhibit_manager_v1_get_inhibitor(state.input_inhibit_manager);
 	if (wl_display_roundtrip(state.display) == -1) {
 		free(state.args.font);
-		swaylock_log(LOG_ERROR, "Exiting - failed to inhibit input:"
-				" is another lockscreen already running?");
-		return 2;
+		if (state.input_inhibit_manager) {
+			swaylock_log(LOG_ERROR, "Exiting - failed to inhibit input:"
+					" is another lockscreen already running?");
+			return 2;
+		}
+		return 1;
 	}
 
 	if (state.zxdg_output_manager) {
 		struct swaylock_surface *surface;
 		wl_list_for_each(surface, &state.surfaces, link) {
@@ -1212,11 +1274,11 @@ int main(int argc, char **argv) {
 				"manager, images assigned to named outputs will not work");
 	}
 
 	struct swaylock_surface *surface;
 	wl_list_for_each(surface, &state.surfaces, link) {
-		create_layer_surface(surface);
+		create_surface(surface);
 	}
 
 	if (state.args.daemonize) {
 		wl_display_roundtrip(state.display);
 		daemonize();
@@ -1235,8 +1297,13 @@ int main(int argc, char **argv) {
 			break;
 		}
 		loop_poll(state.eventloop);
 	}
 
+	if (state.ext_session_lock_v1) {
+		ext_session_lock_v1_unlock_and_destroy(state.ext_session_lock_v1);
+		wl_display_flush(state.display);
+	}
+
 	free(state.args.font);
 	return 0;
 }
diff --git a/meson.build b/meson.build
index fc1f61c..3b349c7 100644
--- a/meson.build
+++ b/meson.build
@@ -34,11 +34,11 @@ add_project_arguments(
 if is_freebsd
 	add_project_arguments('-D_C11_SOURCE', language: 'c')
 endif
 
 wayland_client = dependency('wayland-client')
-wayland_protos = dependency('wayland-protocols', version: '>=1.14', fallback: 'wayland-protocols')
+wayland_protos = dependency('wayland-protocols', version: '>=1.25', fallback: 'wayland-protocols')
 xkbcommon      = dependency('xkbcommon')
 cairo          = dependency('cairo')
 gdk_pixbuf     = dependency('gdk-pixbuf-2.0', required: get_option('gdk-pixbuf'))
 bash_comp      = dependency('bash-completion', required: false)
 fish_comp      = dependency('fish', required: false)
@@ -84,10 +84,11 @@ client_protos_src = []
 client_protos_headers = []
 
 client_protocols = [
 	[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
 	[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
+	[wl_protocol_dir, 'staging/ext-session-lock/ext-session-lock-v1.xml'],
 	['wlr-layer-shell-unstable-v1.xml'],
 	['wlr-input-inhibitor-unstable-v1.xml'],
 ]
 
 foreach p : client_protocols

Vulnerable code: static void create_layer_surface(struct swaylock_surface *surface) {
	surface->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
			state->layer_shell, surface->surface, surface->output,
			ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "lockscreen");
	assert(surface->layer_surface);

	zwlr_layer_surface_v1_set_size(surface->layer_surface, 0, 0);
	zwlr_layer_surface_v1_set_anchor(surface->layer_surface,
			ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
			ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
			ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
			ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
	zwlr_layer_surface_v1_set_exclusive_zone(surface->layer_surface, -1);
	zwlr_layer_surface_v1_set_keyboard_interactivity(
			surface->layer_surface, true);
	zwlr_layer_surface_v1_add_listener(surface->layer_surface,
			&layer_surface_listener, surface);
	wl_surface_commit(surface->surface);
			create_layer_surface(surface);
	assert(state.compositor && state.layer_shell && state.shm);
	if (!state.input_inhibit_manager) {
		free(state.args.font);
		swaylock_log(LOG_ERROR, "Compositor does not support the input "
				"inhibitor protocol, refusing to run insecurely");
	zwlr_input_inhibit_manager_v1_get_inhibitor(state.input_inhibit_manager);
		swaylock_log(LOG_ERROR, "Exiting - failed to inhibit input:"
				" is another lockscreen already running?");
		return 2;
		create_layer_surface(surface);
wayland_protos = dependency('wayland-protocols', version: '>=1.14', fallback: 'wayland-protocols')
Fixed code: 	struct ext_session_lock_manager_v1 *ext_session_lock_manager_v1;
	struct ext_session_lock_v1 *ext_session_lock_v1;
	struct ext_session_lock_surface_v1 *ext_session_lock_surface_v1;
#include "ext-session-lock-v1-client-protocol.h"
	if (surface->ext_session_lock_surface_v1 != NULL) {
		ext_session_lock_surface_v1_destroy(surface->ext_session_lock_surface_v1);
	}
static const struct ext_session_lock_surface_v1_listener ext_session_lock_surface_v1_listener;
static void create_surface(struct swaylock_surface *surface) {
	if (state->ext_session_lock_v1) {
		surface->ext_session_lock_surface_v1 = ext_session_lock_v1_get_lock_surface(
				state->ext_session_lock_v1, surface->surface, surface->output);
		ext_session_lock_surface_v1_add_listener(surface->ext_session_lock_surface_v1,
				&ext_session_lock_surface_v1_listener, surface);
	} else {
		surface->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
				state->layer_shell, surface->surface, surface->output,
				ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "lockscreen");

		zwlr_layer_surface_v1_set_size(surface->layer_surface, 0, 0);
		zwlr_layer_surface_v1_set_anchor(surface->layer_surface,
				ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
				ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
				ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
				ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
		zwlr_layer_surface_v1_set_exclusive_zone(surface->layer_surface, -1);
		zwlr_layer_surface_v1_set_keyboard_interactivity(
				surface->layer_surface, true);
		zwlr_layer_surface_v1_add_listener(surface->layer_surface,
				&layer_surface_listener, surface);
	}
	if (!state->ext_session_lock_v1) {
		wl_surface_commit(surface->surface);
	}
static void ext_session_lock_surface_v1_handle_configure(void *data,
		struct ext_session_lock_surface_v1 *lock_surface, uint32_t serial,
		uint32_t width, uint32_t height) {
	struct swaylock_surface *surface = data;
	surface->width = width;
	surface->height = height;
	surface->indicator_width = 0;
	surface->indicator_height = 0;
	ext_session_lock_surface_v1_ack_configure(lock_surface, serial);
	render_frame_background(surface);
	render_frame(surface);
}

static const struct ext_session_lock_surface_v1_listener ext_session_lock_surface_v1_listener = {
	.configure = ext_session_lock_surface_v1_handle_configure,
};

static void ext_session_lock_v1_handle_locked(void *data, struct ext_session_lock_v1 *lock) {
	// Who cares
}

static void ext_session_lock_v1_handle_finished(void *data, struct ext_session_lock_v1 *lock) {
	swaylock_log(LOG_ERROR, "Failed to lock session -- "
			"is another lockscreen running?");
	exit(2);
}

static const struct ext_session_lock_v1_listener ext_session_lock_v1_listener = {
	.locked = ext_session_lock_v1_handle_locked,
	.finished = ext_session_lock_v1_handle_finished,
};

			create_surface(surface);
	} else if (strcmp(interface, ext_session_lock_manager_v1_interface.name) == 0) {
		state->ext_session_lock_manager_v1 = wl_registry_bind(registry, name,
				&ext_session_lock_manager_v1_interface, 1);

	if (!state.compositor || !state.shm) {
		swaylock_log(LOG_ERROR, "Missing wl_compositor or wl_shm");
		return 1;
	}

	if (state.ext_session_lock_manager_v1) {
		swaylock_log(LOG_DEBUG, "Using ext-session-lock-v1");
		state.ext_session_lock_v1 = ext_session_lock_manager_v1_lock(state.ext_session_lock_manager_v1);
		ext_session_lock_v1_add_listener(state.ext_session_lock_v1,
				&ext_session_lock_v1_listener, &state);
	} else if (state.layer_shell && state.input_inhibit_manager) {
		swaylock_log(LOG_DEBUG, "Using wlr-layer-shell + wlr-input-inhibitor");
		zwlr_input_inhibit_manager_v1_get_inhibitor(state.input_inhibit_manager);
	} else {
		swaylock_log(LOG_ERROR, "Missing ext-session-lock-v1, wlr-layer-shell "
				"and wlr-input-inhibitor");
		if (state.input_inhibit_manager) {
			swaylock_log(LOG_ERROR, "Exiting - failed to inhibit input:"
					" is another lockscreen already running?");
			return 2;
		}
		return 1;
		create_surface(surface);
	if (state.ext_session_lock_v1) {
		ext_session_lock_v1_unlock_and_destroy(state.ext_session_lock_v1);
		wl_display_flush(state.display);
	}

wayland_protos = dependency('wayland-protocols', version: '>=1.25', fallback: 'wayland-protocols')
	[wl_protocol_dir, 'staging/ext-session-lock/ext-session-lock-v1.xml'],