mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 16:27:18 +02:00
Verify named Darwin executable loader compatibility
Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
7b3731fbe6
commit
bba63f5120
1 file changed
+171
@@ -190,6 +190,177 @@ fn verified_launch_preserves_homebrew_node_loader_layout() {
|
||||
process.wait().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod darwin_executable_role {
|
||||
use super::*;
|
||||
use std::time::Instant;
|
||||
|
||||
struct OwnedDirectory(PathBuf);
|
||||
|
||||
impl OwnedDirectory {
|
||||
fn new() -> Self {
|
||||
let path = std::env::temp_dir().join(format!(
|
||||
"paperclip-executable-role-{}",
|
||||
uuid::Uuid::new_v4().simple()
|
||||
));
|
||||
fs::create_dir(&path).unwrap();
|
||||
let mut owned = Self(path);
|
||||
fs::set_permissions(&owned.0, fs::Permissions::from_mode(0o700)).unwrap();
|
||||
owned.0 = fs::canonicalize(&owned.0).unwrap();
|
||||
owned
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for OwnedDirectory {
|
||||
fn drop(&mut self) {
|
||||
let _ = fs::remove_dir_all(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_for_success(process: &mut SupervisedProcess) {
|
||||
let deadline = Instant::now() + Duration::from_secs(2);
|
||||
loop {
|
||||
if let Some(fact) = process.try_wait().unwrap() {
|
||||
assert!(fact.success, "verified child must exit successfully");
|
||||
return;
|
||||
}
|
||||
assert!(Instant::now() < deadline, "verified child did not exit");
|
||||
std::thread::sleep(Duration::from_millis(5));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn named_executable_preserves_command_and_script_after_atomic_replacement() {
|
||||
// Declared first so process/launch guards retire before directory cleanup,
|
||||
// including assertion failures and unsuccessful process admission.
|
||||
let directory = OwnedDirectory::new();
|
||||
let command = directory.0.join("command");
|
||||
let script = directory.0.join("script");
|
||||
let original_command = "#!/bin/sh\nprintf '%s\\n' old-command\nprintf '%s\\n' \"$PAPERCLIP_VERIFIED_RUNTIME_EXECUTABLE\"\nexec /bin/sh \"$1\"\n";
|
||||
let original_script = "#!/bin/sh\nprintf '%s\\n' old-script\n";
|
||||
write_executable(&command, original_command);
|
||||
write_executable(&script, original_script);
|
||||
let launch = VerifiedProcessLaunch::new(
|
||||
VerifiedProcessArtifact::snapshot_verified_executable(
|
||||
command.clone(),
|
||||
File::open(&command).unwrap(),
|
||||
&sha256(original_command),
|
||||
)
|
||||
.unwrap(),
|
||||
vec![VerifiedProcessArgument::Artifact(
|
||||
VerifiedProcessArtifact::snapshot_verified(
|
||||
script.clone(),
|
||||
File::open(&script).unwrap(),
|
||||
&sha256(original_script),
|
||||
)
|
||||
.unwrap(),
|
||||
)],
|
||||
)
|
||||
.with_inherited_runtime_executable();
|
||||
let replacement_command = directory.0.join("replacement-command");
|
||||
let replacement_script = directory.0.join("replacement-script");
|
||||
write_executable(
|
||||
&replacement_command,
|
||||
"#!/bin/sh\nprintf '%s\\n' replacement-command\nexec /bin/sh \"$1\"\n",
|
||||
);
|
||||
write_executable(
|
||||
&replacement_script,
|
||||
"#!/bin/sh\nprintf '%s\\n' replacement-script\n",
|
||||
);
|
||||
fs::rename(replacement_command, &command).unwrap();
|
||||
fs::rename(replacement_script, &script).unwrap();
|
||||
let mut process = SupervisedProcess::spawn_verified_with_environment_keys(
|
||||
&launch,
|
||||
Duration::from_millis(50),
|
||||
1024,
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
// The live supervised process must retain the named image independently.
|
||||
drop(launch);
|
||||
assert_eq!(
|
||||
process
|
||||
.receive_stdout_line(Duration::from_secs(1))
|
||||
.unwrap()
|
||||
.as_deref(),
|
||||
Some("old-command")
|
||||
);
|
||||
let inherited_runtime = process
|
||||
.receive_stdout_line(Duration::from_secs(1))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Path::new(&inherited_runtime).parent(),
|
||||
Some(directory.0.as_path())
|
||||
);
|
||||
assert!(Path::new(&inherited_runtime)
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.starts_with(".paperclip-verified-executable-"));
|
||||
assert!(Path::new(&inherited_runtime).exists());
|
||||
assert_eq!(
|
||||
process
|
||||
.receive_stdout_line(Duration::from_secs(1))
|
||||
.unwrap()
|
||||
.as_deref(),
|
||||
Some("old-script")
|
||||
);
|
||||
wait_for_success(&mut process);
|
||||
drop(process);
|
||||
assert!(!Path::new(&inherited_runtime).exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn named_executable_preserves_homebrew_node_loader_layout() {
|
||||
// Resolve the same executable PATH would choose without an extra helper
|
||||
// process; only the supervised Node image is launched by this test.
|
||||
let node = std::env::split_paths(&std::env::var_os("PATH").expect("PATH is set"))
|
||||
.map(|directory| directory.join("node"))
|
||||
.find(|path| {
|
||||
fs::metadata(path).is_ok_and(|metadata| {
|
||||
metadata.is_file() && metadata.permissions().mode() & 0o111 != 0
|
||||
})
|
||||
})
|
||||
.and_then(|path| fs::canonicalize(path).ok())
|
||||
.expect("node must resolve on PATH");
|
||||
let launch = VerifiedProcessLaunch::new(
|
||||
VerifiedProcessArtifact::snapshot_verified_executable(
|
||||
node.clone(),
|
||||
File::open(&node).unwrap(),
|
||||
&sha256_file(&node),
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
VerifiedProcessArgument::Literal("--eval".to_owned()),
|
||||
VerifiedProcessArgument::Literal("console.log(process.execPath)".to_owned()),
|
||||
],
|
||||
);
|
||||
let mut process = SupervisedProcess::spawn_verified_with_environment_keys(
|
||||
&launch,
|
||||
Duration::from_millis(50),
|
||||
1024,
|
||||
&[],
|
||||
)
|
||||
.expect("named verified Node must start with loader-relative libraries");
|
||||
drop(launch);
|
||||
let executed_node = process
|
||||
.receive_stdout_line(Duration::from_secs(2))
|
||||
.unwrap()
|
||||
.expect("Node must report executable path");
|
||||
assert_eq!(Path::new(&executed_node).parent(), node.parent());
|
||||
assert!(Path::new(&executed_node)
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.starts_with(".paperclip-verified-executable-"));
|
||||
assert!(Path::new(&executed_node).exists());
|
||||
wait_for_success(&mut process);
|
||||
drop(process);
|
||||
assert!(!Path::new(&executed_node).exists());
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_linger_process() -> (SupervisedProcess, u32, u64) {
|
||||
let harness = PathBuf::from(env!("CARGO_BIN_EXE_fake-harness"));
|
||||
let script = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
|
||||
Reference in new issue
Block a user