Skip to content

Commit

Permalink
Support non-utf8 scripts in SIP handling (#2948)
Browse files Browse the repository at this point in the history
* Read only the shebang as a string.

When reading the shebang, read just the first line of the file.
When creating the patched script, don't try to decode the rest of
the file contents to a string, just copy the bytes over.

* rename var

* changelog

* CR: docs

Co-authored-by: Gemma <[email protected]>

---------

Co-authored-by: Gemma <[email protected]>
  • Loading branch information
t4lz and gememma authored Nov 29, 2024
1 parent 8eb7a16 commit e24147c
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 23 deletions.
1 change: 1 addition & 0 deletions changelog.d/2947.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Can now run cs-installed sbt. We now only need to be able to parse the first line of a script, so we now support scripts like that sbt, which starts with a normal shebang but then has text in a weird encoding, or maybe non-textual data.
2 changes: 1 addition & 1 deletion mirrord/sip/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ workspace = true

[dependencies]
# we don't like upstream apple-platform-rs because it depends on RSA which has an open CVE.
apple-codesign = { git = "https://github.com/metalbear-co/apple-platform-rs-mini", version = "0.27", default-features = false}
apple-codesign = { git = "https://github.com/metalbear-co/apple-platform-rs-mini", version = "0.27", default-features = false }
object = "0.36"
tempfile = "3"

Expand Down
51 changes: 29 additions & 22 deletions mirrord/sip/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mod main {
use std::{
env,
ffi::OsStr,
io::{self, ErrorKind::AlreadyExists, Read},
io::{self, BufRead, ErrorKind::AlreadyExists},
os::{macos::fs::MetadataExt, unix::fs::PermissionsExt},
path::{Path, PathBuf},
str::from_utf8,
Expand Down Expand Up @@ -343,12 +343,10 @@ mod main {
// trailing newline is needed for scripts without an original shebang
new_contents.push('\n');
}
let mut bytes = new_contents.into_bytes();
bytes.extend_from_slice(contents);

new_contents.push_str(
from_utf8(contents)
.map_err(|_utf| UnlikelyError("Can't read script contents as utf8".to_string()))?,
);
std::fs::write(&patched_path, new_contents)?;
std::fs::write(&patched_path, bytes)?;

// We set the permissions of the patched script to be like those of the original
// script, but allowing the user to write, so that in the next run, when we are here
Expand All @@ -363,9 +361,9 @@ mod main {

const SF_RESTRICTED: u32 = 0x00080000; // entitlement required for writing, from stat.h (macos)

/// Extract shebang from file contents.
fn get_shebang_from_string(file_contents: &str) -> Option<ScriptShebang> {
let rest = file_contents.strip_prefix("#!")?;
/// Extract shebang from first line of file.
fn get_shebang_from_string(first_line: &str) -> Option<ScriptShebang> {
let rest = first_line.strip_prefix("#!")?;

let mut char_iter = rest.char_indices().skip_while(|(_, c)| c.is_whitespace()); // any whitespace directly after #!
let (start_of_path, _first_char_of_path) = char_iter.next()?;
Expand All @@ -374,11 +372,11 @@ mod main {
let (interpreter, len_with_whitespace) =
if let Some((path_len, _next_char)) = path_char_iter.next() {
let total_len = path_len + 2; // +2 for #! because the index is in `rest`
(file_contents.get(start_of_path + 2..total_len)?, total_len)
(first_line.get(start_of_path + 2..total_len)?, total_len)
} else {
// There is no next character after the interpreter, so the whole file is just
// magic, whitespace and path.
(file_contents.get(start_of_path + 2..)?, file_contents.len())
(first_line.get(start_of_path + 2..)?, first_line.len())
};
Some(ScriptShebang {
interpreter_path: PathBuf::from(interpreter),
Expand All @@ -389,15 +387,22 @@ mod main {
/// Including '#!', just until whitespace, no arguments.
/// will not be called on object files
fn read_shebang_from_file<P: AsRef<Path>>(path: P) -> Result<Option<ScriptShebang>> {
let mut f = std::fs::File::open(path)?;
let mut buffer = String::new();
match f.read_to_string(&mut buffer) {
Ok(_) => {}
Err(e) if e.kind() == io::ErrorKind::InvalidData => return Ok(None),
Err(e) => return Err(SipError::IO(e)),
let f = std::fs::File::open(path)?;
let reader = io::BufReader::new(f);
let mut lines = reader.lines();
let Some(first_line_res) = lines.next() else {
trace!("No lines in script.");
return Ok(None);
};

match first_line_res {
Err(e) if e.kind() == io::ErrorKind::InvalidData => {
trace!("First line of file is not utf-8. Can't read a shebang.");
Ok(None)
}
Err(e) => Err(SipError::IO(e)),
Ok(line_string) => Ok(get_shebang_from_string(&line_string)),
}

Ok(get_shebang_from_string(&buffer))
}

#[derive(Debug)]
Expand Down Expand Up @@ -496,13 +501,15 @@ mod main {
}
})
} else {
let shebang =
read_shebang_from_file(&complete_path)?.unwrap_or_else(|| ScriptShebang {
let shebang = read_shebang_from_file(&complete_path)?.unwrap_or_else(|| {
trace!("Did not find a shebang, defaulting to $SHELL.");
ScriptShebang {
interpreter_path: PathBuf::from(
env::var("SHELL").expect("$SHELL should be present"),
),
start_of_rest_of_file: 0,
});
}
});

let interpreter_complete_path = get_complete_path(&shebang.interpreter_path)?;
if is_in_mirrord_tmp_dir(&interpreter_complete_path)? {
Expand Down

0 comments on commit e24147c

Please sign in to comment.