Skip to content

Commit 1ba0a41

Browse files
fix(plugins): avoid probing native uploads from noexec temp mounts
Move the uploaded temp file into the plugin subdirectory (.tmp extension) before dlopen-probing it. The original temp file may reside on a noexec mount (e.g. /tmp on hardened hosts), causing dlopen to fail even though .plugins/native/ would work fine. The probe file is cleaned up on conflict or rename failure, and an existing plugin's library is never overwritten until the probe passes. Signed-off-by: StreamKit Devin <devin@streamkit.dev> Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
1 parent 4df62fa commit 1ba0a41

File tree

1 file changed

+51
-19
lines changed

1 file changed

+51
-19
lines changed

apps/skit/src/plugins.rs

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,30 +1064,62 @@ impl UnifiedPluginManager {
10641064
return Err(anyhow!("temp plugin path is not a file: {}", temp_path.display()));
10651065
}
10661066

1067-
// For native plugins, probe the kind from the temp file to detect
1068-
// conflicts before moving to the final location. This prevents
1069-
// overwriting an existing plugin's library file when the uploaded
1070-
// plugin's kind is already loaded.
10711067
if plugin_type == PluginType::Native {
1072-
self.check_native_upload_conflict(temp_path)?;
1073-
}
1068+
// Move the temp file into the plugin subdirectory *before*
1069+
// probing. The original temp file may live on a noexec mount
1070+
// (e.g. /tmp), which would cause dlopen to fail even though
1071+
// .plugins/native/ is fine. Using a .tmp extension keeps it
1072+
// separate from the final target so a pre-existing plugin is
1073+
// never overwritten until the probe passes.
1074+
let probe_path = target_path.with_extension("tmp");
1075+
if let Err(e) = std::fs::rename(temp_path, &probe_path) {
1076+
debug!(
1077+
error = %e,
1078+
from = %temp_path.display(),
1079+
to = %probe_path.display(),
1080+
"rename to probe path failed; falling back to copy+remove"
1081+
);
1082+
std::fs::copy(temp_path, &probe_path).with_context(|| {
1083+
format!(
1084+
"failed to copy temp plugin file from {} to {}",
1085+
temp_path.display(),
1086+
probe_path.display()
1087+
)
1088+
})?;
1089+
let _ = std::fs::remove_file(temp_path);
1090+
}
10741091

1075-
// Prefer atomic move; fall back to copy+remove for cross-device temp dirs.
1076-
if let Err(e) = std::fs::rename(temp_path, &target_path) {
1077-
debug!(
1078-
error = %e,
1079-
from = %temp_path.display(),
1080-
to = %target_path.display(),
1081-
"rename failed; falling back to copy+remove"
1082-
);
1083-
std::fs::copy(temp_path, &target_path).with_context(|| {
1084-
format!(
1085-
"failed to copy temp plugin file from {} to {}",
1086-
temp_path.display(),
1092+
if let Err(e) = self.check_native_upload_conflict(&probe_path) {
1093+
let _ = std::fs::remove_file(&probe_path);
1094+
return Err(e);
1095+
}
1096+
1097+
std::fs::rename(&probe_path, &target_path).map_err(|e| {
1098+
let _ = std::fs::remove_file(&probe_path);
1099+
anyhow!(
1100+
"failed to move probe file from {} to {}: {e}",
1101+
probe_path.display(),
10871102
target_path.display()
10881103
)
10891104
})?;
1090-
let _ = std::fs::remove_file(temp_path);
1105+
} else {
1106+
// WASM plugins don't need dlopen probing; move directly.
1107+
if let Err(e) = std::fs::rename(temp_path, &target_path) {
1108+
debug!(
1109+
error = %e,
1110+
from = %temp_path.display(),
1111+
to = %target_path.display(),
1112+
"rename failed; falling back to copy+remove"
1113+
);
1114+
std::fs::copy(temp_path, &target_path).with_context(|| {
1115+
format!(
1116+
"failed to copy temp plugin file from {} to {}",
1117+
temp_path.display(),
1118+
target_path.display()
1119+
)
1120+
})?;
1121+
let _ = std::fs::remove_file(temp_path);
1122+
}
10911123
}
10921124
file_placed = true;
10931125

0 commit comments

Comments
 (0)