From 062f49b3fe477708918494e04a82edf6d3aa6136 Mon Sep 17 00:00:00 2001 From: qjerome Date: Mon, 1 Jun 2026 16:43:41 +0200 Subject: [PATCH 1/2] chore(fuzz): update Cargo.lock --- fuzz/Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 1d31cf3..4729655 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -426,7 +426,7 @@ checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "magic-db" -version = "0.5.2" +version = "0.5.3" dependencies = [ "magic-embed", "pure-magic", @@ -434,7 +434,7 @@ dependencies = [ [[package]] name = "magic-embed" -version = "0.4.2" +version = "0.4.3" dependencies = [ "fs-walk", "proc-macro2", @@ -543,7 +543,7 @@ dependencies = [ [[package]] name = "pure-magic" -version = "0.3.2" +version = "0.3.3" dependencies = [ "bincode", "chrono", From b9fcd7798c81b4e01249aec558bb8b442fedb48d Mon Sep 17 00:00:00 2001 From: qjerome Date: Thu, 11 Jun 2026 09:03:36 +0200 Subject: [PATCH 2/2] fix: terminal injection --- pure-magic/src/lib.rs | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/pure-magic/src/lib.rs b/pure-magic/src/lib.rs index 25f4fc2..60a52fe 100644 --- a/pure-magic/src/lib.rs +++ b/pure-magic/src/lib.rs @@ -3047,24 +3047,49 @@ impl<'m> Magic<'m> { /// # Returns /// /// * `String` - The formatted message + /// + /// # Note + /// + /// The returned string only ever contains printable ASCII (graphic + /// characters and spaces). Any other byte, including non-ASCII UTF-8 + /// sequences, control characters and Unicode format/bidi characters, is + /// escaped as `\NNN` (octal), the same way libmagic does. This makes the + /// output safe to print directly to a terminal. #[inline(always)] pub fn message(&self) -> String { let mut out = String::new(); + + macro_rules! push_str { + ($input: expr) => {{ + for c in $input.as_bytes() { + match c { + b'\r' => out.push_str("\\r"), + b'\t' => out.push_str("\\t"), + b'\n' => out.push_str("\\n"), + b' ' => out.push(' '), + c if c.is_ascii_graphic() => out.push(*c as char), + // the way libmagic handles non printable bytes + _ => out.push_str(&format!("\\{:03o}", *c as u8)), + } + } + }}; + } + for (i, m) in self.message.iter().enumerate() { if let Some(s) = m.strip_prefix(r#"\b"#) { - out.push_str(s); + push_str!(s); } else { // don't put space on first string if i > 0 { out.push(' '); } - out.push_str(m); + push_str!(m); } } out } - /// Returns an iterator over the individual parts of the magic message + /// Returns an iterator over the individual raw parts of the magic message /// /// A magic message is typically composed of multiple parts, each appended /// during successful magic tests. This method provides an efficient way to @@ -3074,6 +3099,14 @@ impl<'m> Magic<'m> { /// # Returns /// /// * `impl Iterator` - An iterator yielding string slices of each message part + /// + /// # Note + /// + /// Unlike [`Magic::message`], the returned parts are **not** sanitized: + /// they may contain raw control characters (e.g. `ESC`) extracted from + /// the scanned data. Do not print them directly to a terminal, as this + /// could result in terminal escape sequence injection. This method is + /// intended for programmatic inspection only. #[inline] pub fn message_parts(&self) -> impl Iterator { self.message.iter().map(|p| p.as_ref())