aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-15 07:50:54 +0900
committernsfisis <nsfisis@gmail.com>2026-08-15 07:50:54 +0900
commit55f385450407a3d8c6c7c90ec4dc8995f5277a94 (patch)
tree5986bcaa39026dadc960147e0c35440c1d63c81d /crates
parent548463bad1f72c97f68b47f54a263a4f4ad87b3a (diff)
downloadphp-shirabe-55f385450407a3d8c6c7c90ec4dc8995f5277a94.tar.gz
php-shirabe-55f385450407a3d8c6c7c90ec4dc8995f5277a94.tar.zst
php-shirabe-55f385450407a3d8c6c7c90ec4dc8995f5277a94.zip
fix(phar): match the stub token in any letter case
`__halt_compiler` is a PHP keyword, so a stub may spell the token in any letter case. The native phar reader compared the bytes exactly and rejected such an archive, and the lint that keeps a second token out of the executable missed lowercase ones, which would shadow the embedded bundle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates')
-rw-r--r--crates/shirabe-php-shim/src/phar.rs36
1 files changed, 28 insertions, 8 deletions
diff --git a/crates/shirabe-php-shim/src/phar.rs b/crates/shirabe-php-shim/src/phar.rs
index ecd842f3..1815cbf1 100644
--- a/crates/shirabe-php-shim/src/phar.rs
+++ b/crates/shirabe-php-shim/src/phar.rs
@@ -289,8 +289,8 @@ fn verify_phar_signature(path: &std::path::Path, bytes: &[u8]) -> anyhow::Result
/// The special token to separate a phar stub and contents.
///
/// The Shirabe executable embeds the Composer runtime bundle as a phar archive, so the token must
-/// not appear in the binary. See `docs/dev/composer-runtime-bundle.md`. We use `black_box()` to
-/// prevent the Rust compiler from inlining the function to a constant.
+/// not appear in the binary in any letter case. See `docs/dev/composer-runtime-bundle.md`. We use
+/// `black_box()` to prevent the Rust compiler from inlining the function to a constant.
fn halt_compiler_token() -> [u8; 18] {
let mut token = *std::hint::black_box(b"__UNYG_PBZCVYRE();");
for byte in &mut token {
@@ -308,7 +308,7 @@ fn parse_native_phar(path: &std::path::Path) -> anyhow::Result<Vec<PharEntry>> {
let halt = halt_compiler_token();
let halt_pos = bytes
.windows(halt.len())
- .position(|window| window == halt)
+ .position(|window| window.eq_ignore_ascii_case(&halt))
.ok_or_else(|| {
corruption_error(
path,
@@ -958,8 +958,8 @@ mod tests {
}
/// Builds a minimal native phar (one stored file, one deflated file, SHA-1
- /// signature) following the php.net manual layout.
- fn build_native_phar(tampered: bool) -> Vec<u8> {
+ /// signature) following the php.net manual layout, with `token` as the stub token.
+ fn build_native_phar(tampered: bool, token: [u8; 18]) -> Vec<u8> {
let stored = (b"Hello World".to_vec(), "dir/hello.txt");
let big = "abc".repeat(1000).into_bytes();
let mut deflated = Vec::new();
@@ -1003,7 +1003,7 @@ mod tests {
}
let mut bytes = b"<?php ".to_vec();
- bytes.extend_from_slice(&halt_compiler_token());
+ bytes.extend_from_slice(&token);
bytes.extend_from_slice(b" ?>\r\n");
bytes.extend_from_slice(&(manifest.len() as u32).to_le_bytes());
bytes.extend_from_slice(&manifest);
@@ -1024,7 +1024,7 @@ mod tests {
fn phar_native_read_and_extract() {
let dir = tempfile::tempdir().unwrap();
let phar_path = dir.path().join("test.phar");
- std::fs::write(&phar_path, build_native_phar(false)).unwrap();
+ std::fs::write(&phar_path, build_native_phar(false, halt_compiler_token())).unwrap();
let phar = Phar::new(&phar_path).unwrap();
let out = dir.path().join("extracted");
@@ -1040,10 +1040,30 @@ mod tests {
}
#[test]
+ fn phar_native_stub_token_is_read_in_any_letter_case() {
+ let dir = tempfile::tempdir().unwrap();
+ let mut token = halt_compiler_token();
+ token.make_ascii_lowercase();
+ let phar_path = dir.path().join("lowercase.phar");
+ std::fs::write(&phar_path, build_native_phar(false, token)).unwrap();
+
+ let out = dir.path().join("extracted");
+ Phar::new(&phar_path)
+ .unwrap()
+ .extract_to(&out, None, true)
+ .unwrap();
+
+ assert_eq!(
+ std::fs::read(out.join("dir/hello.txt")).unwrap(),
+ b"Hello World"
+ );
+ }
+
+ #[test]
fn phar_native_broken_signature_is_rejected() {
let dir = tempfile::tempdir().unwrap();
let phar_path = dir.path().join("tampered.phar");
- std::fs::write(&phar_path, build_native_phar(true)).unwrap();
+ std::fs::write(&phar_path, build_native_phar(true, halt_compiler_token())).unwrap();
let error = Phar::new(&phar_path).unwrap_err();
assert!(