diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-03-11 04:40:30 -0500 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-03-11 04:40:30 -0500 |
| commit | b3f1ba196c384e3d96f84862a1e6cf21311a9cef (patch) | |
| tree | 7a06ae86873ad33c5df8c1bf243c5a18e8c006a4 | |
| parent | 8b0057aca5f80bf6eab15c898e7d373d4c003453 (diff) | |
Check CAP_SYS_ADMIN at runtime before negotiating FUSE passthrough
Negotiating passthrough without the capability caused Wine/Proton DLL
loading failures (SKSE plugins returning error 0x36). Now checks the
process's effective capability set via libcap before telling the kernel
we want passthrough. Also adds qt.conf for AT_SECURE compatibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| -rw-r--r-- | docker/Dockerfile | 2 | ||||
| -rwxr-xr-x | docker/build-inner.sh | 9 | ||||
| -rw-r--r-- | src/src/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | src/src/vfs/mo2filesystem.cpp | 21 |
4 files changed, 33 insertions, 2 deletions
diff --git a/docker/Dockerfile b/docker/Dockerfile index 7f319e4..68c7570 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -17,7 +17,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libtinyxml2-dev \ libfontconfig1-dev \ libspdlog-dev \ - libfuse3-dev fuse3 \ + libfuse3-dev fuse3 libcap-dev \ liblz4-dev zlib1g-dev libzstd-dev libbz2-dev liblzma-dev \ libssl-dev libcurl4-openssl-dev \ libtomlplusplus-dev \ diff --git a/docker/build-inner.sh b/docker/build-inner.sh index 93ee6ba..c62285b 100755 --- a/docker/build-inner.sh +++ b/docker/build-inner.sh @@ -371,6 +371,15 @@ exec env PYTHONHOME="${MO2_PYTHONHOME}" "${RUN}/ModOrganizer-core" "$@" LAUNCH chmod +x "${OUT_DIR}/fluorine-manager" +# ── qt.conf — tells Qt where to find plugins without QT_PLUGIN_PATH env ── +# This is required when the binary has file capabilities (cap_sys_admin for +# FUSE passthrough), because glibc strips environment variables in AT_SECURE +# mode. qt.conf is read by Qt at startup regardless of env. +cat > "${OUT_DIR}/qt.conf" <<'QTCONF' +[Paths] +Plugins = qt6plugins +QTCONF + # ── Desktop integration files ── cp -f /src/data/com.fluorine.manager.desktop "${OUT_DIR}/" cp -f /src/data/com.fluorine.manager.png "${OUT_DIR}/" diff --git a/src/src/CMakeLists.txt b/src/src/CMakeLists.txt index d7a7642..fa5d658 100644 --- a/src/src/CMakeLists.txt +++ b/src/src/CMakeLists.txt @@ -10,6 +10,7 @@ find_package(Boost CONFIG REQUIRED COMPONENTS find_package(spdlog CONFIG REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(FUSE3 REQUIRED IMPORTED_TARGET fuse3) +pkg_check_modules(LIBCAP REQUIRED IMPORTED_TARGET libcap) find_package(Threads REQUIRED) qt_standard_project_setup() @@ -110,6 +111,8 @@ target_link_libraries(organizer PRIVATE spdlog::spdlog_header_only # FUSE3 (low-level API) PkgConfig::FUSE3 + # libcap (runtime capability check for passthrough) + PkgConfig::LIBCAP # Linux system libs dl) diff --git a/src/src/vfs/mo2filesystem.cpp b/src/src/vfs/mo2filesystem.cpp index 6c8cb87..908c5bb 100644 --- a/src/src/vfs/mo2filesystem.cpp +++ b/src/src/vfs/mo2filesystem.cpp @@ -2,6 +2,7 @@ #include <fcntl.h> #include <linux/fs.h> +#include <sys/capability.h> #include <sys/time.h> #include <unistd.h> @@ -573,11 +574,29 @@ void mo2_init(void* userdata, struct fuse_conn_info* conn) // Requires: kernel 6.9+, libfuse 3.16+, CAP_SYS_ADMIN on the binary. ctx->passthrough_active = false; if constexpr (FUSE_CAP_PASSTHROUGH != 0) { - if (ctx->passthrough_requested && + // Check if the process actually has CAP_SYS_ADMIN in its effective set. + // Without it, fuse_passthrough_open will fail with EPERM, and negotiating + // passthrough at the kernel level can break Wine/Proton DLL loading. + bool hasCap = false; + { + cap_t caps = cap_get_proc(); + if (caps) { + cap_flag_value_t val = CAP_CLEAR; + cap_get_flag(caps, CAP_SYS_ADMIN, CAP_EFFECTIVE, &val); + hasCap = (val == CAP_SET); + cap_free(caps); + } + } + + if (ctx->passthrough_requested && hasCap && (conn->capable & FUSE_CAP_PASSTHROUGH)) { conn->want |= FUSE_CAP_PASSTHROUGH; ctx->passthrough_active = true; std::fprintf(stderr, "[VFS] FUSE passthrough enabled (kernel supports it)\n"); + } else if (ctx->passthrough_requested && !hasCap) { + std::fprintf(stderr, + "[VFS] FUSE passthrough requested but process lacks " + "CAP_SYS_ADMIN. Falling back to userspace I/O.\n"); } else if (ctx->passthrough_requested) { std::fprintf(stderr, "[VFS] FUSE passthrough requested but NOT supported by kernel " |
