fix(runner): preserve macOS verified runtime layout

This commit is contained in:
Dotta 2026-09-03 22:19:10 -05:00
parent b8d84471a3
commit 72fc40a21e
3 changed files with 94 additions and 23 deletions

View File

@ -15,7 +15,7 @@ use std::os::unix::process::CommandExt;
use std::os::fd::AsRawFd;
#[cfg(target_os = "macos")]
use std::os::unix::fs::{DirBuilderExt, FileExt, MetadataExt, OpenOptionsExt, PermissionsExt};
use std::os::unix::fs::{FileExt, MetadataExt, OpenOptionsExt, PermissionsExt};
#[cfg(target_os = "macos")]
use uuid::Uuid;
@ -290,7 +290,6 @@ struct InheritedCommand {
#[cfg(target_os = "macos")]
struct TemporaryExecutable {
path: PathBuf,
directory: PathBuf,
_file: File,
}
@ -298,7 +297,6 @@ struct TemporaryExecutable {
impl Drop for TemporaryExecutable {
fn drop(&mut self) {
let _ = fs::remove_file(&self.path);
let _ = fs::remove_dir(&self.directory);
}
}
@ -306,29 +304,31 @@ impl Drop for TemporaryExecutable {
fn materialize_executable(
artifact: &VerifiedProcessArtifact,
) -> Result<TemporaryExecutable, LocalRunnerError> {
let directory = std::env::temp_dir().join(format!(
let directory = artifact.display_path.parent().ok_or_else(|| {
LocalRunnerError::invalid(format!(
"verified process artifact {} has no parent directory",
artifact.display_path.display()
))
})?;
let directory_metadata = fs::symlink_metadata(directory)
.map_err(|error| snapshot_error(&artifact.display_path, error))?;
if !directory_metadata.is_dir() || directory_metadata.permissions().mode() & 0o022 != 0 {
return Err(LocalRunnerError::invalid(format!(
"verified process artifact directory {} must be a directory that is not group- or world-writable",
directory.display()
)));
}
let path = directory.join(format!(
".paperclip-verified-executable-{}",
Uuid::new_v4().simple()
));
let mut directory_builder = fs::DirBuilder::new();
directory_builder.mode(0o700);
directory_builder
.create(&directory)
.map_err(|error| snapshot_error(&artifact.display_path, error))?;
let path = directory.join("launch");
let writable = OpenOptions::new()
let mut writable = OpenOptions::new()
.read(true)
.write(true)
.create_new(true)
.mode(0o700)
.open(&path);
let mut writable = match writable {
Ok(file) => file,
Err(error) => {
let _ = fs::remove_dir(&directory);
return Err(snapshot_error(&artifact.display_path, error));
}
};
.open(&path)
.map_err(|error| snapshot_error(&artifact.display_path, error))?;
let result = (|| {
let length = artifact
.file
@ -375,13 +375,11 @@ fn materialize_executable(
drop(writable);
Ok(TemporaryExecutable {
path: path.clone(),
directory: directory.clone(),
_file: file,
})
})();
if result.is_err() {
let _ = fs::remove_file(path);
let _ = fs::remove_dir(directory);
}
result
}

View File

@ -1,6 +1,8 @@
#![cfg(unix)]
use std::fs::{self, File};
#[cfg(target_os = "macos")]
use std::io::Read;
use std::io::Write;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
@ -35,6 +37,21 @@ fn sha256(contents: &str) -> String {
format!("sha256:{:x}", Sha256::digest(contents.as_bytes()))
}
#[cfg(target_os = "macos")]
fn sha256_file(path: &Path) -> String {
let mut file = File::open(path).unwrap();
let mut digest = Sha256::new();
let mut buffer = [0_u8; 64 * 1024];
loop {
let count = file.read(&mut buffer).unwrap();
if count == 0 {
break;
}
digest.update(&buffer[..count]);
}
format!("sha256:{:x}", digest.finalize())
}
#[test]
fn verified_launch_uses_open_command_and_script_after_atomic_path_replacement() {
let directory = std::env::temp_dir().join(format!(
@ -105,7 +122,14 @@ fn verified_launch_uses_open_command_and_script_after_atomic_path_replacement()
#[cfg(target_os = "linux")]
assert!(inherited_runtime.starts_with("/proc/self/fd/"));
#[cfg(target_os = "macos")]
assert!(inherited_runtime.contains(".paperclip-verified-executable-"));
{
assert!(inherited_runtime.contains(".paperclip-verified-executable-"));
assert_eq!(
Path::new(&inherited_runtime).parent(),
command.parent(),
"macOS verified launches must preserve loader-relative runtime layout"
);
}
assert_eq!(
process
.receive_stdout_line(Duration::from_secs(1))
@ -117,6 +141,55 @@ fn verified_launch_uses_open_command_and_script_after_atomic_path_replacement()
fs::remove_dir_all(directory).unwrap();
}
#[cfg(target_os = "macos")]
#[test]
fn verified_launch_preserves_homebrew_node_loader_layout() {
let resolved = Command::new("/usr/bin/which")
.arg("node")
.output()
.expect("node lookup should run");
assert!(
resolved.status.success(),
"node should be available on PATH"
);
let node = fs::canonicalize(
String::from_utf8(resolved.stdout)
.expect("node path should be UTF-8")
.trim(),
)
.expect("node path should resolve");
let launch = VerifiedProcessLaunch::new(
VerifiedProcessArtifact::snapshot_verified(
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("verified Node should start with its loader-relative libraries");
let executed_node = process
.receive_stdout_line(Duration::from_secs(2))
.unwrap()
.expect("Node should report its executable path");
assert_eq!(
Path::new(&executed_node).parent(),
node.parent(),
"verified Node must execute beside the authenticated runtime"
);
process.wait().unwrap();
}
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"))

View File

@ -175,7 +175,7 @@ const rustSources = await filesUnder(
path.endsWith(".rs") ||
path.endsWith("Cargo.toml") ||
path.endsWith("Cargo.lock"),
(path) => path.endsWith("/target"),
(path) => /\/(?:target|tests|benches|examples)$/u.test(path),
);
await assertArtifactsFresh("runnerd", rustSources, [runnerd]);