From 131ca2b2c936528329d400a1589367fd23a10002 Mon Sep 17 00:00:00 2001 From: Null Prism Date: Wed, 29 Jul 2026 01:16:10 -0400 Subject: [PATCH 1/2] fix(linux): detect stale SELinux host labels --- tests/RunLinuxLauncherTests.sh | 81 ++++++++++++++++++++++++++++++++++ tools/linux/run_ue4ss.sh | 78 ++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) diff --git a/tests/RunLinuxLauncherTests.sh b/tests/RunLinuxLauncherTests.sh index 05fe8746..4272aa8b 100755 --- a/tests/RunLinuxLauncherTests.sh +++ b/tests/RunLinuxLauncherTests.sh @@ -162,4 +162,85 @@ if [[ ${non_regular_host_status} -eq 0 ]]; then fi grep -Fq 'host executable not found or not executable' <<<"${non_regular_host_output}" +selinux_mock_bin="${stage_directory}/selinux mock bin" +mkdir -p -- "${selinux_mock_bin}" + +cat >"${selinux_mock_bin}/getenforce" <<'MOCK_GETENFORCE' +#!/usr/bin/env bash +printf '%s\n' Enforcing +MOCK_GETENFORCE + +cat >"${selinux_mock_bin}/stat" <<'MOCK_STAT' +#!/usr/bin/env bash +printf '%s\n' \ + "${MOCK_SELINUX_CONTEXT:-unconfined_u:object_r:user_home_t:s0}" +MOCK_STAT + +chmod +x \ + "${selinux_mock_bin}/getenforce" \ + "${selinux_mock_bin}/stat" + +selinux_warning_output="$( + env -u LD_PRELOAD \ + PATH="${selinux_mock_bin}:${PATH}" \ + MOCK_SELINUX_CONTEXT='unconfined_u:object_r:user_home_t:s0' \ + UE4SS_DISABLE_AUTO_START=1 \ + UE4SS_SELINUX_PREFLIGHT=warn \ + "${launcher}" \ + "${target}" \ + 2>&1 +)" + +grep -Fq \ + "SELinux preflight: host executable type 'user_home_t'" \ + <<<"${selinux_warning_output}" + +grep -Fq \ + "restore its persistent file context before launch" \ + <<<"${selinux_warning_output}" + +set +e + +selinux_strict_output="$( + env -u LD_PRELOAD \ + PATH="${selinux_mock_bin}:${PATH}" \ + MOCK_SELINUX_CONTEXT='unconfined_u:object_r:user_home_t:s0' \ + UE4SS_DISABLE_AUTO_START=1 \ + UE4SS_SELINUX_PREFLIGHT=strict \ + UE4SS_EXPECTED_SELINUX_TYPE=palworld_ue4ss_exec_t \ + "${launcher}" \ + "${target}" \ + 2>&1 +)" + +selinux_strict_status=$? + +set -e + +if [[ ${selinux_strict_status} -ne 8 ]]; then + echo "strict SELinux preflight returned ${selinux_strict_status}, expected 8" >&2 + exit 12 +fi + +grep -Fq \ + "does not match UE4SS_EXPECTED_SELINUX_TYPE='palworld_ue4ss_exec_t'" \ + <<<"${selinux_strict_output}" + +selinux_matching_output="$( + env -u LD_PRELOAD \ + PATH="${selinux_mock_bin}:${PATH}" \ + MOCK_SELINUX_CONTEXT='system_u:object_r:palworld_ue4ss_exec_t:s0' \ + UE4SS_DISABLE_AUTO_START=1 \ + UE4SS_SELINUX_PREFLIGHT=strict \ + UE4SS_EXPECTED_SELINUX_TYPE=palworld_ue4ss_exec_t \ + "${launcher}" \ + "${target}" \ + 2>&1 +)" + +if grep -Fq 'SELinux preflight:' <<<"${selinux_matching_output}"; then + echo "strict SELinux preflight warned for the expected host type" >&2 + exit 13 +fi + rm -rf -- "${stage_directory}" diff --git a/tools/linux/run_ue4ss.sh b/tools/linux/run_ue4ss.sh index 015d64c1..991691e7 100755 --- a/tools/linux/run_ue4ss.sh +++ b/tools/linux/run_ue4ss.sh @@ -16,6 +16,82 @@ is_elf_file() { [[ "$(LC_ALL=C od -An -t x1 -N4 -- "$1" | tr -d '[:space:]')" == "7f454c46" ]] } +selinux_file_type() { + local context + local selinux_user + local selinux_role + local file_type + local selinux_level + + context="$(stat -Lc '%C' -- "$1" 2>/dev/null || true)" + + if [[ -z "${context}" || "${context}" == "?" ]]; then + return 1 + fi + + IFS=: read -r \ + selinux_user \ + selinux_role \ + file_type \ + selinux_level \ + <<<"${context}" + + if [[ -z "${file_type}" ]]; then + return 1 + fi + + printf '%s\n' "${file_type}" +} + +selinux_preflight_problem() { + local mode="$1" + shift + + echo "UE4SS launcher: SELinux preflight: $*" >&2 + echo "UE4SS launcher: a game update may have replaced the labeled host executable; restore its persistent file context before launch." >&2 + + if [[ "${mode}" == "strict" ]]; then + exit 8 + fi +} + +selinux_preflight() { + local mode="${UE4SS_SELINUX_PREFLIGHT:-warn}" + local file_type + local expected_type="${UE4SS_EXPECTED_SELINUX_TYPE:-}" + + case "${mode}" in + off|warn|strict) + ;; + *) + echo "UE4SS launcher: invalid UE4SS_SELINUX_PREFLIGHT value '${mode}'; expected off, warn, or strict" >&2 + exit 8 + ;; + esac + + [[ "${mode}" != "off" ]] || return 0 + command -v getenforce >/dev/null 2>&1 || return 0 + [[ "$(getenforce 2>/dev/null || true)" == "Enforcing" ]] || return 0 + + file_type="$(selinux_file_type "${host_executable}" || true)" + [[ -n "${file_type}" ]] || return 0 + + if [[ -n "${expected_type}" && "${file_type}" != "${expected_type}" ]]; then + selinux_preflight_problem \ + "${mode}" \ + "host executable type '${file_type}' does not match UE4SS_EXPECTED_SELINUX_TYPE='${expected_type}'" + return 0 + fi + + case "${file_type}" in + user_home_t|home_root_t|default_t|unlabeled_t) + selinux_preflight_problem \ + "${mode}" \ + "host executable type '${file_type}' does not identify a scoped UE4SS SELinux entrypoint" + ;; + esac +} + host_executable="" if [[ "${1:-}" == "--host-executable" ]]; then if [[ $# -lt 3 ]]; then @@ -57,6 +133,8 @@ if ! is_elf_file "${host_executable}"; then exit 7 fi +selinux_preflight + export UE4SS_LAUNCH_TARGET_EXE="$(canonical_path "${host_executable}")" if [[ -v LD_PRELOAD ]]; then export UE4SS_LAUNCH_LD_PRELOAD_WAS_SET=1 From 976af59c04244709068eacc4984a94db30e88346 Mon Sep 17 00:00:00 2001 From: Null Prism Date: Wed, 29 Jul 2026 01:22:01 -0400 Subject: [PATCH 2/2] docs(linux): restore host labels after game updates --- docs/linux.md | 56 +++++++++++++++++++++++++++++++++ packaging/linux/INSTALL.md | 64 +++++++++++++++++++++++++++++++++----- 2 files changed, 112 insertions(+), 8 deletions(-) diff --git a/docs/linux.md b/docs/linux.md index 45bd2159..28740bc2 100644 --- a/docs/linux.md +++ b/docs/linux.md @@ -148,6 +148,62 @@ Windows does not need this policy: proxy DLL and manual DLL injection load UE4SS Marker-free manual `LD_PRELOAD` and explicit `dlopen` do not auto-start UE4SS by default. Use `run_ue4ss.sh` for the supported process-scoped workflow. Deliberate legacy compatibility can be enabled with `UE4SS_ALLOW_LEGACY_START=1`, but that mode retains normal Linux child inheritance and may initialize UE4SS in descendant or helper processes. If the user's original `LD_PRELOAD` already contains UE4SS, exact preservation takes precedence. +## SELinux preflight and game updates + +Do not enable the global `execheap` boolean solely for UE4SS. Use a narrowly +scoped SELinux domain that permits executable trampoline memory only for the +intended UE4SS-enabled host process. + +The launcher supports these SELinux preflight modes: + +```bash +UE4SS_SELINUX_PREFLIGHT=off +UE4SS_SELINUX_PREFLIGHT=warn +UE4SS_SELINUX_PREFLIGHT=strict +``` + +The default is `warn`. A scoped deployment should also identify the expected +entrypoint type: + +```bash +UE4SS_SELINUX_PREFLIGHT=strict \ +UE4SS_EXPECTED_SELINUX_TYPE=palworld_ue4ss_exec_t \ + "$stage/run_ue4ss.sh" \ + --host-executable "$server" \ + "$wrapper" +``` + +When SELinux is Enforcing, `strict` mode exits with status 8 if the host +executable does not have the expected type. This prevents a later executable +trampoline fault when the process fails to transition into its scoped domain. + +Game updates and validation operations can replace the host executable with a +new inode. The replacement may receive the directory's default SELinux type +instead of the persistent custom entrypoint type. + +Define a persistent file-context mapping once: + +```bash +semanage fcontext -a \ + -t palworld_ue4ss_exec_t \ + '/srv/palworld/Pal/Binaries/Linux/PalServer-Linux-Shipping' +``` + +After every game update or validation, restore the configured context before +starting UE4SS: + +```bash +restorecon -Fv \ + /srv/palworld/Pal/Binaries/Linux/PalServer-Linux-Shipping + +matchpathcon -V \ + /srv/palworld/Pal/Binaries/Linux/PalServer-Linux-Shipping +``` + +Use `semanage fcontext -m` instead of `-a` when changing an existing mapping. +Do not rely on `chcon`; it changes the current label without defining the +persistent file-context rule. + ## Diagnostics and logs Set `UE4SS_DIAGNOSE=1` for a support-oriented startup report: diff --git a/packaging/linux/INSTALL.md b/packaging/linux/INSTALL.md index 8a81ab06..0d64048b 100644 --- a/packaging/linux/INSTALL.md +++ b/packaging/linux/INSTALL.md @@ -112,6 +112,62 @@ When no wrapper is required: -NoAsyncLoadingThread \ -UseMultithreadForDS +## SELinux preflight and game updates + +Do not enable the global `execheap` boolean solely for UE4SS. Use a narrowly +scoped SELinux domain that permits executable trampoline memory only for the +intended UE4SS-enabled host process. + +The launcher supports these SELinux preflight modes: + +```bash +UE4SS_SELINUX_PREFLIGHT=off +UE4SS_SELINUX_PREFLIGHT=warn +UE4SS_SELINUX_PREFLIGHT=strict +``` + +The default is `warn`. A scoped deployment should also identify the expected +entrypoint type: + +```bash +UE4SS_SELINUX_PREFLIGHT=strict \ +UE4SS_EXPECTED_SELINUX_TYPE=palworld_ue4ss_exec_t \ + "$stage/run_ue4ss.sh" \ + --host-executable "$server" \ + "$wrapper" +``` + +When SELinux is Enforcing, `strict` mode exits with status 8 if the host +executable does not have the expected type. This prevents a later executable +trampoline fault when the process fails to transition into its scoped domain. + +Game updates and validation operations can replace the host executable with a +new inode. The replacement may receive the directory's default SELinux type +instead of the persistent custom entrypoint type. + +Define a persistent file-context mapping once: + +```bash +semanage fcontext -a \ + -t palworld_ue4ss_exec_t \ + '/srv/palworld/Pal/Binaries/Linux/PalServer-Linux-Shipping' +``` + +After every game update or validation, restore the configured context before +starting UE4SS: + +```bash +restorecon -Fv \ + /srv/palworld/Pal/Binaries/Linux/PalServer-Linux-Shipping + +matchpathcon -V \ + /srv/palworld/Pal/Binaries/Linux/PalServer-Linux-Shipping +``` + +Use `semanage fcontext -m` instead of `-a` when changing an existing mapping. +Do not rely on `chcon`; it changes the current label without defining the +persistent file-context rule. + ## Diagnostics Enable the Linux startup diagnostic report with: @@ -131,11 +187,3 @@ Review: Do not unload `libUE4SS.so` or native C++ mods with `dlclose`. Stop the host game process to unload the loader and its native mods. - -## SELinux - -Do not enable global `execheap` solely for UE4SS. - -The validated Palworld environment used a narrowly scoped SELinux domain and -policy for the isolated UE4SS-enabled server process. Deployments using SELinux -should apply an equally restricted local policy.