1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

feat: codesign for deno compile binaries (#24604)

Uses [sui](https://github.com/littledivy/sui) to inject metadata as a
custom section in the denort binary.

Metadata is stored as a Mach-O segment on macOS and PE `RT_RCDATA`
resource on Windows.

Fixes #11154 
Fixes https://github.com/denoland/deno/issues/17753

```cpp
deno compile app.tsx

# on macOS
codesign --sign - ./app

# on Windows
signtool sign /fd SHA256 .\app.exe
```

---------

Signed-off-by: Divy Srivastava <dj.srivastava23@gmail.com>
This commit is contained in:
Divy Srivastava 2024-07-31 21:15:13 -07:00 committed by GitHub
parent f57745fe21
commit 5bd76609f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 109 additions and 69 deletions

34
Cargo.lock generated
View file

@ -1092,6 +1092,12 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41b319d1b62ffbd002e057f36bebd1f42b9f97927c9577461d855f3513c4289f" checksum = "41b319d1b62ffbd002e057f36bebd1f42b9f97927c9577461d855f3513c4289f"
[[package]]
name = "debug-ignore"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe7ed1d93f4553003e20b629abe9085e1e81b1429520f897f8f8860bc6dfc21"
[[package]] [[package]]
name = "debugid" name = "debugid"
version = "0.8.0" version = "0.8.0"
@ -1160,6 +1166,7 @@ dependencies = [
"junction", "junction",
"lazy-regex", "lazy-regex",
"libc", "libc",
"libsui",
"libz-sys", "libz-sys",
"log", "log",
"lsp-types", "lsp-types",
@ -2524,6 +2531,19 @@ dependencies = [
"spki", "spki",
] ]
[[package]]
name = "editpe"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48cede2bb1b07dd598d269f973792c43e0cd92686d3b452bd6e01d7a8eb01211"
dependencies = [
"debug-ignore",
"indexmap",
"log",
"thiserror",
"zerocopy",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.10.0" version = "1.10.0"
@ -4025,6 +4045,19 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "libsui"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c2fedcf6cb4dd935f94a90e1c4300c727fe7112b8455615e902828c7401f84d"
dependencies = [
"editpe",
"libc",
"sha2",
"windows-sys 0.48.0",
"zerocopy",
]
[[package]] [[package]]
name = "libz-sys" name = "libz-sys"
version = "1.1.16" version = "1.1.16"
@ -8281,6 +8314,7 @@ version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [ dependencies = [
"byteorder",
"zerocopy-derive", "zerocopy-derive",
] ]

View file

@ -79,6 +79,7 @@ deno_semver = "=0.5.7"
deno_task_shell = "=0.17.0" deno_task_shell = "=0.17.0"
deno_terminal.workspace = true deno_terminal.workspace = true
eszip = "=0.73.0" eszip = "=0.73.0"
libsui = "0.1.0"
napi_sym.workspace = true napi_sym.workspace = true
node_resolver.workspace = true node_resolver.workspace = true

View file

@ -82,9 +82,7 @@ fn load_env_vars(env_vars: &HashMap<String, String>) {
fn main() { fn main() {
let args: Vec<_> = env::args_os().collect(); let args: Vec<_> = env::args_os().collect();
let current_exe_path = current_exe().unwrap(); let standalone = standalone::extract_standalone(Cow::Owned(args));
let standalone =
standalone::extract_standalone(&current_exe_path, Cow::Owned(args));
let future = async move { let future = async move {
match standalone { match standalone {
Ok(Some(future)) => { Ok(Some(future)) => {

View file

@ -7,6 +7,7 @@ use std::collections::VecDeque;
use std::env::current_exe; use std::env::current_exe;
use std::ffi::OsString; use std::ffi::OsString;
use std::fs; use std::fs;
use std::fs::File;
use std::future::Future; use std::future::Future;
use std::io::Read; use std::io::Read;
use std::io::Seek; use std::io::Seek;
@ -106,16 +107,19 @@ pub struct Metadata {
} }
pub fn load_npm_vfs(root_dir_path: PathBuf) -> Result<FileBackedVfs, AnyError> { pub fn load_npm_vfs(root_dir_path: PathBuf) -> Result<FileBackedVfs, AnyError> {
let file_path = current_exe().unwrap(); let data = libsui::find_section("d3n0l4nd").unwrap();
let mut file = std::fs::File::open(file_path)?;
file.seek(SeekFrom::End(-(TRAILER_SIZE as i64)))?; // We do the first part sync so it can complete quickly
let mut trailer = [0; TRAILER_SIZE]; let trailer: [u8; TRAILER_SIZE] = data[0..TRAILER_SIZE].try_into().unwrap();
file.read_exact(&mut trailer)?; let trailer = match Trailer::parse(&trailer)? {
let trailer = Trailer::parse(&trailer)?.unwrap(); None => panic!("Could not find trailer"),
file.seek(SeekFrom::Start(trailer.npm_vfs_pos))?; Some(trailer) => trailer,
let mut vfs_data = vec![0; trailer.npm_vfs_len() as usize]; };
file.read_exact(&mut vfs_data)?; let data = &data[TRAILER_SIZE..];
let mut dir: VirtualDirectory = serde_json::from_slice(&vfs_data)?;
let vfs_data =
&data[trailer.npm_vfs_pos as usize..trailer.npm_files_pos as usize];
let mut dir: VirtualDirectory = serde_json::from_slice(vfs_data)?;
// align the name of the directory with the root dir // align the name of the directory with the root dir
dir.name = root_dir_path dir.name = root_dir_path
@ -129,38 +133,32 @@ pub fn load_npm_vfs(root_dir_path: PathBuf) -> Result<FileBackedVfs, AnyError> {
root_path: root_dir_path, root_path: root_dir_path,
start_file_offset: trailer.npm_files_pos, start_file_offset: trailer.npm_files_pos,
}; };
Ok(FileBackedVfs::new(file, fs_root)) Ok(FileBackedVfs::new(data.to_vec(), fs_root))
} }
fn write_binary_bytes( fn write_binary_bytes(
writer: &mut impl Write, mut file_writer: File,
original_bin: Vec<u8>, original_bin: Vec<u8>,
metadata: &Metadata, metadata: &Metadata,
eszip: eszip::EszipV2, eszip: eszip::EszipV2,
npm_vfs: Option<&VirtualDirectory>, npm_vfs: Option<&VirtualDirectory>,
npm_files: &Vec<Vec<u8>>, npm_files: &Vec<Vec<u8>>,
compile_flags: &CompileFlags,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let metadata = serde_json::to_string(metadata)?.as_bytes().to_vec(); let metadata = serde_json::to_string(metadata)?.as_bytes().to_vec();
let npm_vfs = serde_json::to_string(&npm_vfs)?.as_bytes().to_vec(); let npm_vfs = serde_json::to_string(&npm_vfs)?.as_bytes().to_vec();
let eszip_archive = eszip.into_bytes(); let eszip_archive = eszip.into_bytes();
writer.write_all(&original_bin)?; let mut writer = Vec::new();
writer.write_all(&eszip_archive)?;
writer.write_all(&metadata)?;
writer.write_all(&npm_vfs)?;
for file in npm_files {
writer.write_all(file)?;
}
// write the trailer, which includes the positions // write the trailer, which includes the positions
// of the data blocks in the file // of the data blocks in the file
writer.write_all(&{ writer.write_all(&{
let eszip_pos = original_bin.len() as u64; let metadata_pos = eszip_archive.len() as u64;
let metadata_pos = eszip_pos + (eszip_archive.len() as u64);
let npm_vfs_pos = metadata_pos + (metadata.len() as u64); let npm_vfs_pos = metadata_pos + (metadata.len() as u64);
let npm_files_pos = npm_vfs_pos + (npm_vfs.len() as u64); let npm_files_pos = npm_vfs_pos + (npm_vfs.len() as u64);
Trailer { Trailer {
eszip_pos, eszip_pos: 0,
metadata_pos, metadata_pos,
npm_vfs_pos, npm_vfs_pos,
npm_files_pos, npm_files_pos,
@ -168,27 +166,36 @@ fn write_binary_bytes(
.as_bytes() .as_bytes()
})?; })?;
writer.write_all(&eszip_archive)?;
writer.write_all(&metadata)?;
writer.write_all(&npm_vfs)?;
for file in npm_files {
writer.write_all(file)?;
}
let target = compile_flags.resolve_target();
if target.contains("linux") {
libsui::Elf::new(&original_bin).append(&writer, &mut file_writer)?;
} else if target.contains("windows") {
libsui::PortableExecutable::from(&original_bin)?
.write_resource("d3n0l4nd", writer)?
.build(&mut file_writer)?;
} else if target.contains("darwin") {
libsui::Macho::from(original_bin)?
.write_section("d3n0l4nd", writer)?
.build(&mut file_writer)?;
}
Ok(()) Ok(())
} }
pub fn is_standalone_binary(exe_path: &Path) -> bool { pub fn is_standalone_binary(exe_path: &Path) -> bool {
let Ok(mut output_file) = std::fs::File::open(exe_path) else { let Ok(data) = std::fs::read(exe_path) else {
return false; return false;
}; };
if output_file
.seek(SeekFrom::End(-(TRAILER_SIZE as i64))) libsui::utils::is_elf(&data)
.is_err() | libsui::utils::is_pe(&data)
{ | libsui::utils::is_macho(&data)
// This seek may fail because the file is too small to possibly be
// `deno compile` output.
return false;
}
let mut trailer = [0; TRAILER_SIZE];
if output_file.read_exact(&mut trailer).is_err() {
return false;
};
let (magic_trailer, _) = trailer.split_at(8);
magic_trailer == MAGIC_TRAILER
} }
/// This function will try to run this binary as a standalone binary /// This function will try to run this binary as a standalone binary
@ -197,40 +204,32 @@ pub fn is_standalone_binary(exe_path: &Path) -> bool {
/// then checking for the magic trailer string `d3n0l4nd`. If found, /// then checking for the magic trailer string `d3n0l4nd`. If found,
/// the bundle is executed. If not, this function exits with `Ok(None)`. /// the bundle is executed. If not, this function exits with `Ok(None)`.
pub fn extract_standalone( pub fn extract_standalone(
exe_path: &Path,
cli_args: Cow<Vec<OsString>>, cli_args: Cow<Vec<OsString>>,
) -> Result< ) -> Result<
Option<impl Future<Output = Result<(Metadata, eszip::EszipV2), AnyError>>>, Option<impl Future<Output = Result<(Metadata, eszip::EszipV2), AnyError>>>,
AnyError, AnyError,
> { > {
let Some(data) = libsui::find_section("d3n0l4nd") else {
return Ok(None);
};
// We do the first part sync so it can complete quickly // We do the first part sync so it can complete quickly
let mut file = std::fs::File::open(exe_path)?; let trailer = match Trailer::parse(&data[0..TRAILER_SIZE])? {
file.seek(SeekFrom::End(-(TRAILER_SIZE as i64)))?;
let mut trailer = [0; TRAILER_SIZE];
file.read_exact(&mut trailer)?;
let trailer = match Trailer::parse(&trailer)? {
None => return Ok(None), None => return Ok(None),
Some(trailer) => trailer, Some(trailer) => trailer,
}; };
file.seek(SeekFrom::Start(trailer.eszip_pos))?;
let cli_args = cli_args.into_owned(); let cli_args = cli_args.into_owned();
// If we have an eszip, read it out // If we have an eszip, read it out
Ok(Some(async move { Ok(Some(async move {
let bufreader = let bufreader =
deno_core::futures::io::BufReader::new(AllowStdIo::new(file)); deno_core::futures::io::BufReader::new(&data[TRAILER_SIZE..]);
let (eszip, loader) = eszip::EszipV2::parse(bufreader) let (eszip, loader) = eszip::EszipV2::parse(bufreader)
.await .await
.context("Failed to parse eszip header")?; .context("Failed to parse eszip header")?;
let mut bufreader = let bufreader = loader.await.context("Failed to parse eszip archive")?;
loader.await.context("Failed to parse eszip archive")?;
bufreader
.seek(SeekFrom::Start(trailer.metadata_pos))
.await?;
let mut metadata = String::new(); let mut metadata = String::new();
@ -405,7 +404,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
pub async fn write_bin( pub async fn write_bin(
&self, &self,
writer: &mut impl Write, writer: File,
eszip: eszip::EszipV2, eszip: eszip::EszipV2,
root_dir_url: EszipRelativeFileBaseUrl<'_>, root_dir_url: EszipRelativeFileBaseUrl<'_>,
entrypoint: &ModuleSpecifier, entrypoint: &ModuleSpecifier,
@ -518,7 +517,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn write_standalone_binary( fn write_standalone_binary(
&self, &self,
writer: &mut impl Write, writer: File,
original_bin: Vec<u8>, original_bin: Vec<u8>,
mut eszip: eszip::EszipV2, mut eszip: eszip::EszipV2,
root_dir_url: EszipRelativeFileBaseUrl<'_>, root_dir_url: EszipRelativeFileBaseUrl<'_>,
@ -654,6 +653,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
eszip, eszip,
npm_vfs.as_ref(), npm_vfs.as_ref(),
&npm_files, &npm_files,
compile_flags,
) )
} }

View file

@ -748,12 +748,12 @@ impl deno_io::fs::File for FileBackedVfsFile {
#[derive(Debug)] #[derive(Debug)]
pub struct FileBackedVfs { pub struct FileBackedVfs {
file: Mutex<File>, file: Mutex<Vec<u8>>,
fs_root: VfsRoot, fs_root: VfsRoot,
} }
impl FileBackedVfs { impl FileBackedVfs {
pub fn new(file: File, fs_root: VfsRoot) -> Self { pub fn new(file: Vec<u8>, fs_root: VfsRoot) -> Self {
Self { Self {
file: Mutex::new(file), file: Mutex::new(file),
fs_root, fs_root,
@ -836,11 +836,18 @@ impl FileBackedVfs {
pos: u64, pos: u64,
buf: &mut [u8], buf: &mut [u8],
) -> std::io::Result<usize> { ) -> std::io::Result<usize> {
let mut fs_file = self.file.lock(); let data = self.file.lock();
fs_file.seek(SeekFrom::Start( let start = self.fs_root.start_file_offset + file.offset + pos;
self.fs_root.start_file_offset + file.offset + pos, let end = start + buf.len() as u64;
))?; if end > data.len() as u64 {
fs_file.read(buf) return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"unexpected EOF",
));
}
buf.copy_from_slice(&data[start as usize..end as usize]);
Ok(buf.len())
} }
pub fn dir_entry(&self, path: &Path) -> std::io::Result<&VirtualDirectory> { pub fn dir_entry(&self, path: &Path) -> std::io::Result<&VirtualDirectory> {
@ -1016,12 +1023,12 @@ mod test {
file.write_all(file_data).unwrap(); file.write_all(file_data).unwrap();
} }
} }
let file = std::fs::File::open(&virtual_fs_file).unwrap();
let dest_path = temp_dir.path().join("dest"); let dest_path = temp_dir.path().join("dest");
let data = std::fs::read(&virtual_fs_file).unwrap();
( (
dest_path.to_path_buf(), dest_path.to_path_buf(),
FileBackedVfs::new( FileBackedVfs::new(
file, data,
VfsRoot { VfsRoot {
dir: root_dir, dir: root_dir,
root_path: dest_path.to_path_buf(), root_path: dest_path.to_path_buf(),

View file

@ -124,12 +124,13 @@ pub async fn compile(
)); ));
let temp_path = output_path.with_file_name(temp_filename); let temp_path = output_path.with_file_name(temp_filename);
let mut file = std::fs::File::create(&temp_path).with_context(|| { let file = std::fs::File::create(&temp_path).with_context(|| {
format!("Opening temporary file '{}'", temp_path.display()) format!("Opening temporary file '{}'", temp_path.display())
})?; })?;
let write_result = binary_writer let write_result = binary_writer
.write_bin( .write_bin(
&mut file, file,
eszip, eszip,
root_dir_url, root_dir_url,
&module_specifier, &module_specifier,
@ -140,7 +141,6 @@ pub async fn compile(
.with_context(|| { .with_context(|| {
format!("Writing temporary file '{}'", temp_path.display()) format!("Writing temporary file '{}'", temp_path.display())
}); });
drop(file);
// set it as executable // set it as executable
#[cfg(unix)] #[cfg(unix)]