Terminal Injection via Detection Output (Issue #498)#
Magic-rule description output built from file content is not escaped for terminal control bytes. This is a distinct and open vulnerability compared to the TTY-gated escaping that exists for symlink-target rendering.
The Vulnerability#
When a magic rule like 0 string x STR=[%s] reads file content into a description, the raw bytes — including OSC sequences, SGR escape codes, and newlines — reach stdout unescaped. A planted file processed by rmagic * can:
- Spoof the terminal title via OSC sequences (e.g.,
ESC ] 0 ; TITLE BEL) - Execute OSC 52 clipboard commands
- Produce multi-line output that overflows the expected single-line result
Confirmed in issue #498: a file containing ZZZZ\x1b]0;PWN\x07X\nSECOND\n causes rmagic to emit two lines including the raw ESC sequence, while GNU file stops at the first newline .
Root Cause: Conflated Scan and Render Bounds#
rmagic uses EvaluationConfig::max_string_length (default 8192) for both the scan-window cap on read_string/read_pattern_match and the render output bound. GNU file keeps these separate: it caps the rendered field at 127 bytes and stops at the first newline .
The divergence is load-bearing:
pstringis not bounded bymax_string_lengthat all — it is bounded only by its length prefix and the buffer (~64 KB) .- Nothing in
src/output/enforces a render-time truncation;concatenate_messagesinsrc/lib.rsassembles raw rule-match strings without any output-length or newline guard .
The Gap: Escaping Is TTY-Gated to Symlink Targets Only#
The only terminal-control-byte escaping in the codebase is render_symlink_target() in src/cli/symlink.rs. It is:
- TTY-gated: escaping fires only when stdout is an interactive terminal (
escape_control_bytesflag), preserving byte-for-byte parity with GNUfilefor captured/piped output . - Symlink-target-only: the function is called from
classify_symlink()for the"symbolic link to ..."and"broken symbolic link to ..."descriptions. Magic-rule descriptions never pass through it.
The escape set covers three families :
| Family | Characters |
|---|---|
| C0 controls + DEL | < 0x20, 0x7F |
| C1 range | 0x80–0x9F (UTF-8 terminals decode these back to 8-bit CSI/OSC) |
| Unicode bidi/format overrides | U+200E, U+200F, U+061C, U+202A–U+202E, U+2066–U+2069 |
No equivalent function guards the format_magic_message() → concatenate_messages() pipeline that produces detection output from magic rules .
Related: /w Anchor Off-By-One (Same Issue)#
Issue #498 also documents that flagged_string_bytes_consumed advances by the walked text length (after greedy whitespace consumption by /w) rather than the pattern length . GNU file advances by pattern length. This is a separate bug filed under the same issue and affects relative-offset children of /w-flagged string rules.
Affected Output Path#
file content
→ read_string / read_pattern_match (scan-window bound: max_string_length)
→ format_magic_message() [src/output/format.rs] ← no escaping
→ concatenate_messages() [src/lib.rs] ← no escaping
→ stdout ← raw control bytes
The symlink path has escaping; the magic-rule path does not.
Current Status#
Issue #498 is open and unfixed as of 2026-09-21. The fix plan is documented in docs/plans/2026-09-21-1127-fix-bounded-inert-magic-descriptions-plan.md (not yet found in the main branch at time of writing). Three defects need to be addressed together :
- Apply a 127-byte/first-newline render cap separate from the scan-window cap.
- Fix
flagged_string_bytes_consumedto advance bypattern.len(), not the walked length. - Apply TTY-gated control-byte escaping to magic-rule descriptions, mirroring
render_symlink_target().
Related tracking: #382 (ascmagic text-class append), #379 (strength ordering), #304 (prior max_string_length enforcement gap).
Key Source References#
| File | Relevance |
|---|---|
src/cli/symlink.rs | TTY-gated escaping implementation (render_symlink_target, is_terminal_control) |
src/lib.rs | concatenate_messages() — no render cap or escaping |
src/output/format.rs | format_magic_message() — printf substitution, no escaping |
| GOTCHAS.md §17.3 | Documents the TTY-gated symlink escape design and the three-family escape set |
| Issue #498 | Full evidence, repro steps, and scope |