diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt index 730c43787..419b4258f 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt @@ -741,6 +741,30 @@ class SessionService : Service() { * behind, still holding the Wayland socket and the audio server the next session needs. They * are our uid, so they are ours to kill. */ + /** + * Every process of ours still running a program out of the Linux runtime once proot is gone. + * proot's --kill-on-exit and the tree sweep only reach what proot still traces; a tracee it + * lost - an Xwayland that aborted from one thread and then looped on a syscall proot's own + * seccomp filter, with no tracer left, answers ENOSYS - survives both, reparented to init, + * and wrote 9 GB of one line into the session log before anything killed it. + */ + private fun killGuestLeftovers() { + val me = android.os.Process.myPid() + val rootfs = try { File(filesDir, "linuxfs").canonicalPath } catch (e: Exception) { return } + "/" + val procs = File("/proc").listFiles { f -> f.name.all { it.isDigit() } } ?: return + var killed = 0 + for (proc in procs) { + val pid = proc.name.toIntOrNull() ?: continue + if (pid == me) continue + // Another uid's exe is not ours to read; a process already gone has none. + val exe = try { File(proc, "exe").canonicalPath } catch (e: Exception) { continue } + if (!exe.startsWith(rootfs)) continue + android.os.Process.killProcess(pid) + killed++ + } + if (killed > 0) Log.w(TAG, "killed $killed guest process(es) proot no longer tracked") + } + private fun killStragglers() { val me = android.os.Process.myPid() val procs = File("/proc").listFiles { f -> f.name.all { it.isDigit() } } ?: return @@ -917,6 +941,7 @@ class SessionService : Service() { Thread({ auxiliary.forEach { (pid, started) -> teardown(pid, started) } if (prootPid > 1) teardown(prootPid) + killGuestLeftovers() finishSessionStop(status) }, "session-teardown").start() } else { diff --git a/tools/linuxfs/overlay/usr/local/bin/bannerlator-session b/tools/linuxfs/overlay/usr/local/bin/bannerlator-session index 65e6e414e..4e8402291 100755 --- a/tools/linuxfs/overlay/usr/local/bin/bannerlator-session +++ b/tools/linuxfs/overlay/usr/local/bin/bannerlator-session @@ -19,7 +19,10 @@ if [ -n "${BL_LOG:-}" ] && [ -z "${BL_INSIDE:-}" ]; then if [ "$(stat -c %s "$BL_LOG" 2>/dev/null || echo 0)" -gt 8388608 ]; then mv -f "$BL_LOG" "$BL_LOG.1" fi - exec >>"$BL_LOG" 2>&1 + # A guest looping on an error writes gigabytes in minutes (an Xwayland proot had lost track of + # managed 9 GB of one line in 19 min): the log keeps its first BL_LOG_CAP bytes and the rest is + # drained, not refused - a writer that filled it must not die of SIGPIPE. + exec > >({ head -c "${BL_LOG_CAP:-268435456}" >>"$BL_LOG"; cat >/dev/null; }) 2>&1 echo "== bannerlator-session $(date) mode=$mode $*" echo "== uid=$(id -u) size=${BL_WIDTH:-?}x${BL_HEIGHT:-?} fps=${BL_FPS:-0} icd=${VK_ICD_FILENAMES:-none}" echo "== gamescope: $(gamescope --version 2>&1 | head -1) at $(command -v gamescope)"