From a9f68aa7cbabc68a3c6728fc4718c3fbe2bbba8c Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 29 Aug 2026 23:42:12 -0700 Subject: [PATCH] fix(linux): harden AppImage desktop startup oracle --- .../run-appimage-desktop-startup-case.sh | 99 ++++++++++++++----- .../headless-serve-shutdown-workflow.test.mjs | 13 +++ .../run-headless-serve-shutdown-docker.mjs | 2 +- 3 files changed, 91 insertions(+), 23 deletions(-) diff --git a/config/docker/headless-serve-shutdown/run-appimage-desktop-startup-case.sh b/config/docker/headless-serve-shutdown/run-appimage-desktop-startup-case.sh index 53e33d2a26e..5a1d8058426 100755 --- a/config/docker/headless-serve-shutdown/run-appimage-desktop-startup-case.sh +++ b/config/docker/headless-serve-shutdown/run-appimage-desktop-startup-case.sh @@ -9,30 +9,41 @@ if [[ $# -gt 1 ]]; then fi if ((EUID == 0)); then - state_dir=$(mktemp -d /tmp/orca-appimage-startup.XXXXXX) - chown -R orca:orca "$state_dir" - exec runuser --user orca --preserve-environment -- env ORCA_STARTUP_STATE_DIR="$state_dir" "$0" "$@" + if ! state_dir=$(mktemp -d /tmp/orca-appimage-startup.XXXXXX); then + echo 'FAIL: unable to create the AppImage startup state directory' >&2 + exit 1 + fi + if ! chown orca:orca "$state_dir"; then + echo "FAIL: unable to hand the AppImage startup state directory to orca: $state_dir" >&2 + rm -rf -- "$state_dir" || true + exit 1 + fi + exec runuser --user orca --preserve-environment -- env \ + ORCA_STARTUP_STATE_DIR="$state_dir" \ + ORCA_STARTUP_STATE_DIR_CLEANUP=1 \ + "$0" "$@" fi -state_dir=${ORCA_STARTUP_STATE_DIR:-$(mktemp -d /tmp/orca-appimage-startup.XXXXXX)} +remove_state_dir_on_exit=${ORCA_STARTUP_STATE_DIR_CLEANUP:-0} +if [[ -n "${ORCA_STARTUP_STATE_DIR:-}" ]]; then + state_dir=$ORCA_STARTUP_STATE_DIR +else + if ! state_dir=$(mktemp -d /tmp/orca-appimage-startup.XXXXXX); then + echo 'FAIL: unable to create the AppImage startup state directory' >&2 + exit 1 + fi + remove_state_dir_on_exit=1 +fi stdout_log="$state_dir/stdout.log" stderr_log="$state_dir/stderr.log" launcher_pid= launcher_start_ticks= launcher_pgid= +launcher_status= +launcher_waited=false tree_pids=() declare -A tree_start_ticks=() -mkdir -p "$state_dir/home" "$state_dir/config" "$state_dir/cache" "$state_dir/runtime" -chmod 700 "$state_dir/runtime" -export HOME="$state_dir/home" -export XDG_CONFIG_HOME="$state_dir/config" -export XDG_CACHE_HOME="$state_dir/cache" -export XDG_RUNTIME_DIR="$state_dir/runtime" -export LIBGL_ALWAYS_SOFTWARE=1 -export ORCA_STARTUP_DIAGNOSTICS=1 -ulimit -c 0 - read_start_ticks() { local pid=$1 [[ -r "/proc/$pid/stat" ]] || return 1 @@ -127,9 +138,41 @@ wait_for_owned_exit() { dump_logs() { echo "--- desktop startup stdout ---" >&2 - cat "$stdout_log" 2>/dev/null || true + cat "$stdout_log" >&2 2>/dev/null || true echo "--- desktop startup stderr ---" >&2 - cat "$stderr_log" 2>/dev/null || true + cat "$stderr_log" >&2 2>/dev/null || true +} + +cleanup_state_dir() { + [[ "$remove_state_dir_on_exit" == 1 ]] || return 0 + [[ "$state_dir" =~ ^/tmp/orca-appimage-startup\.[^/]+$ ]] || return 0 + [[ -d "$state_dir" && ! -L "$state_dir" && -O "$state_dir" ]] || return 0 + rm -rf -- "$state_dir" +} + +capture_launcher_status() { + [[ "$launcher_waited" == false ]] || return 0 + [[ -n "$launcher_pid" ]] || return 1 + if wait "$launcher_pid"; then + launcher_status=0 + else + launcher_status=$? + fi + launcher_waited=true +} + +report_launcher_exit() { + local reason=$1 + local observed_status=unknown + local exit_status=1 + if capture_launcher_status; then + observed_status=$launcher_status + if ((launcher_status != 0)); then + exit_status=$launcher_status + fi + fi + echo "FAIL: desktop launcher exited before ${reason} (status=${observed_status})" >&2 + exit "$exit_status" } cleanup() { @@ -142,12 +185,12 @@ cleanup() { signal_owned_processes KILL || true wait_for_owned_exit 5 || status=1 fi - wait "$launcher_pid" 2>/dev/null || true + capture_launcher_status || true if ((status != 0)); then dump_logs else - rm -rf "$state_dir" || status=1 - if ((status != 0)); then + if ! cleanup_state_dir; then + status=1 dump_logs fi fi @@ -155,6 +198,16 @@ cleanup() { } trap cleanup EXIT +mkdir -p "$state_dir/home" "$state_dir/config" "$state_dir/cache" "$state_dir/runtime" +chmod 700 "$state_dir/runtime" +export HOME="$state_dir/home" +export XDG_CONFIG_HOME="$state_dir/config" +export XDG_CACHE_HOME="$state_dir/cache" +export XDG_RUNTIME_DIR="$state_dir/runtime" +export LIBGL_ALWAYS_SOFTWARE=1 +export ORCA_STARTUP_DIAGNOSTICS=1 +ulimit -c 0 + [[ -r "$appimage" ]] || { echo "FAIL: AppImage is not readable: $appimage" >&2; exit 1; } setsid --wait dbus-run-session -- xvfb-run -a "$appimage" --appimage-extract-and-run --no-sandbox \ @@ -163,8 +216,7 @@ launcher_pid=$! launcher_start_ticks=$(read_start_ticks "$launcher_pid" 2>/dev/null || true) launcher_pgid=$(ps -o pgid= -p "$launcher_pid" 2>/dev/null | tr -d ' ' || true) if [[ -z "$launcher_start_ticks" ]]; then - echo "FAIL: desktop launcher exited before its identity could be recorded" >&2 - exit 1 + report_launcher_exit 'its identity could be recorded' fi marker_seen=false @@ -175,11 +227,14 @@ while ((SECONDS < deadline)); do break fi if ! identity_alive "$launcher_pid" "$launcher_start_ticks"; then - break + report_launcher_exit 'the updater-setup-done marker' fi sleep 0.2 done if [[ "$marker_seen" != true ]]; then + if ! identity_alive "$launcher_pid" "$launcher_start_ticks"; then + report_launcher_exit 'the updater-setup-done marker' + fi echo "FAIL: desktop AppImage did not emit updater-setup-done within ${startup_timeout_seconds}s" >&2 exit 1 fi diff --git a/config/scripts/headless-serve-shutdown-workflow.test.mjs b/config/scripts/headless-serve-shutdown-workflow.test.mjs index 1df843c0f80..04a326cb974 100644 --- a/config/scripts/headless-serve-shutdown-workflow.test.mjs +++ b/config/scripts/headless-serve-shutdown-workflow.test.mjs @@ -129,6 +129,19 @@ describe('headless serve shutdown PR gate', () => { it('preserves startup logs when the launcher exits before its marker', () => { expect(desktopStartupOracle).toContain('signal_process_group TERM || true') expect(desktopStartupOracle).toContain('signal_process_group KILL || true') + expect(desktopStartupOracle).toContain('cat "$stdout_log" >&2 2>/dev/null || true') + expect(desktopStartupOracle).toContain('cat "$stderr_log" >&2 2>/dev/null || true') + expect(desktopStartupOracle).toContain( + 'FAIL: desktop launcher exited before ${reason} (status=${observed_status})' + ) + expect(desktopStartupOracle).toContain('ORCA_STARTUP_STATE_DIR_CLEANUP=1') + expect(desktopStartupOracle).toContain( + '[[ "$state_dir" =~ ^/tmp/orca-appimage-startup\\.[^/]+$ ]] || return 0' + ) + }) + + it('gives the original AppImage enough bounded extraction space', () => { + expect(shutdownDockerRunner).toContain("'/tmp:rw,nosuid,nodev,exec,size=1g'") }) it('keeps owned Xvfb alive during the documented systemd graceful stop', () => { diff --git a/config/scripts/run-headless-serve-shutdown-docker.mjs b/config/scripts/run-headless-serve-shutdown-docker.mjs index ca48b77a495..03bc99fd52d 100755 --- a/config/scripts/run-headless-serve-shutdown-docker.mjs +++ b/config/scripts/run-headless-serve-shutdown-docker.mjs @@ -156,7 +156,7 @@ function runDesktopStartupOracle({ image, appImage, platform }) { 'none', '--read-only', '--tmpfs', - '/tmp:rw,nosuid,nodev,exec,size=128m', + '/tmp:rw,nosuid,nodev,exec,size=1g', '--shm-size', '256m', '--cap-drop',