aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-03-11 04:40:30 -0500
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-03-11 04:40:30 -0500
commitb3f1ba196c384e3d96f84862a1e6cf21311a9cef (patch)
tree7a06ae86873ad33c5df8c1bf243c5a18e8c006a4
parent8b0057aca5f80bf6eab15c898e7d373d4c003453 (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/Dockerfile2
-rwxr-xr-xdocker/build-inner.sh9
-rw-r--r--src/src/CMakeLists.txt3
-rw-r--r--src/src/vfs/mo2filesystem.cpp21
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 "