From 65d3ba64f91306b4e767d0170283b5132a54ef8a Mon Sep 17 00:00:00 2001 From: nevaforget Date: Thu, 26 Mar 2026 12:34:19 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20Audit-Findings=20=E2=80=94=20Login-Threa?= =?UTF-8?q?d,=20Traversable-Assets,=20Session-Exec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Login-IPC in Background-Thread ausgelagert, UI friert nicht mehr ein bei langsamem PAM/greetd (PERF-2/BUG-4) - importlib.resources korrekt via as_file()/read_text() statt Path(str()) — funktioniert in ZIP-Wheels (BUG-1) - is_absolute()-Check für Session-Exec entfernt, greetd löst PATH selbst auf — relative Executables wie 'sway' blockieren nicht mehr (LOGIC-1) - ValueError (json.JSONDecodeError) im except abgefangen (BUG-2) --- src/moongreet/greeter.py | 74 ++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/src/moongreet/greeter.py b/src/moongreet/greeter.py index 47a4868..bf40621 100644 --- a/src/moongreet/greeter.py +++ b/src/moongreet/greeter.py @@ -6,7 +6,8 @@ import shlex import socket import stat import subprocess -from importlib.resources import files +import threading +from importlib.resources import as_file, files from pathlib import Path import gi @@ -72,7 +73,9 @@ class GreeterWindow(Gtk.ApplicationWindow): # Background wallpaper (blurred and darkened) bg_path = self._config.background if not bg_path or not bg_path.exists(): - bg_path = Path(str(DEFAULT_WALLPAPER_PATH)) + # Extract package wallpaper to a real filesystem path (works in ZIP wheels too) + self._wallpaper_ctx = as_file(DEFAULT_WALLPAPER_PATH) + bg_path = self._wallpaper_ctx.__enter__() if bg_path.exists(): background = Gtk.Picture() background.set_filename(str(bg_path)) @@ -249,10 +252,10 @@ class GreeterWindow(Gtk.ApplicationWindow): ) if avatar_path and avatar_path.exists(): self._set_avatar_from_file(avatar_path, user.username) - elif DEFAULT_AVATAR_PATH.exists(): - self._set_default_avatar() else: - self._avatar_image.set_from_icon_name("avatar-default-symbolic") + # Default avatar — _set_default_avatar uses Traversable.read_text() + # which works in ZIP wheels too, no exists() check needed + self._set_default_avatar() # Apply user's GTK theme if available self._apply_user_theme(user) @@ -379,6 +382,11 @@ class GreeterWindow(Gtk.ApplicationWindow): pass self._greetd_sock = None + def _set_login_sensitive(self, sensitive: bool) -> None: + """Enable or disable login controls during authentication.""" + self._password_entry.set_sensitive(sensitive) + self._session_dropdown.set_sensitive(sensitive) + def _attempt_login(self, user: User, password: str, session: Session) -> None: """Attempt to authenticate and start a session via greetd IPC.""" sock_path = os.environ.get("GREETD_SOCK") @@ -389,6 +397,17 @@ class GreeterWindow(Gtk.ApplicationWindow): if not self._validate_greetd_sock(sock_path): return + # Disable UI while authenticating — the IPC runs in a background thread + self._set_login_sensitive(False) + thread = threading.Thread( + target=self._login_worker, + args=(user, password, session, sock_path), + daemon=True, + ) + thread.start() + + def _login_worker(self, user: User, password: str, session: Session, sock_path: str) -> None: + """Run greetd IPC in a background thread to avoid blocking the GTK main loop.""" try: sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.settimeout(10.0) @@ -399,8 +418,7 @@ class GreeterWindow(Gtk.ApplicationWindow): response = create_session(sock, user.username) if response.get("type") == "error": - self._show_greetd_error(response, self._strings.auth_failed) - self._close_greetd_sock() + GLib.idle_add(self._on_login_error, response, self._strings.auth_failed) return # Step 2: Send password if auth message received @@ -409,47 +427,59 @@ class GreeterWindow(Gtk.ApplicationWindow): if response.get("type") == "error": self._failed_attempts[user.username] = self._failed_attempts.get(user.username, 0) + 1 - self._show_greetd_error(response, self._strings.wrong_password) warning = faillock_warning(self._failed_attempts[user.username], self._strings) - if warning: - current = self._error_label.get_text() - self._error_label.set_text(f"{current}\n{warning}") - self._close_greetd_sock() + GLib.idle_add(self._on_login_auth_error, response, warning) return if response.get("type") == "auth_message": # Multi-stage auth (e.g. TOTP) is not supported cancel_session(sock) - self._close_greetd_sock() - self._show_error(self._strings.multi_stage_unsupported) + GLib.idle_add(self._on_login_error, None, self._strings.multi_stage_unsupported) return # Step 3: Start session if response.get("type") == "success": cmd = shlex.split(session.exec_cmd) - if not cmd or not Path(cmd[0]).is_absolute(): - self._show_error(self._strings.invalid_session_command) + if not cmd: cancel_session(sock) - self._close_greetd_sock() + GLib.idle_add(self._on_login_error, None, self._strings.invalid_session_command) return response = start_session(sock, cmd) if response.get("type") == "success": self._save_last_user(user.username) self._close_greetd_sock() - self.get_application().quit() + GLib.idle_add(self.get_application().quit) return else: - self._show_greetd_error(response, self._strings.session_start_failed) + GLib.idle_add(self._on_login_error, response, self._strings.session_start_failed) self._close_greetd_sock() except ConnectionError as e: self._close_greetd_sock() - self._show_error(self._strings.connection_error.format(error=e)) - except OSError as e: + GLib.idle_add(self._on_login_error, None, self._strings.connection_error.format(error=e)) + except (OSError, ValueError) as e: self._close_greetd_sock() - self._show_error(self._strings.socket_error.format(error=e)) + GLib.idle_add(self._on_login_error, None, self._strings.socket_error.format(error=e)) + + def _on_login_error(self, response: dict | None, message: str) -> None: + """Handle login error on the GTK main thread.""" + if response: + self._show_greetd_error(response, message) + else: + self._show_error(message) + self._close_greetd_sock() + self._set_login_sensitive(True) + + def _on_login_auth_error(self, response: dict, warning: str | None) -> None: + """Handle authentication failure with optional faillock warning on the GTK main thread.""" + self._show_greetd_error(response, self._strings.wrong_password) + if warning: + current = self._error_label.get_text() + self._error_label.set_text(f"{current}\n{warning}") + self._close_greetd_sock() + self._set_login_sensitive(True) def _cancel_pending_session(self) -> None: """Cancel any in-progress greetd session."""