| @@ -0,0 +1,93 @@ | |||
| 1 | + | # Remaining fuzzing findings for Mitra | |
| 2 | + | ||
| 3 | + | Session date: 2026-08-02. Tested at `8b5c382b` (Mitra v5.8.0) with | |
| 4 | + | `cargo-fuzz`, libFuzzer, and AddressSanitizer. | |
| 5 | + | ||
| 6 | + | The previously reported `clean_title` byte/character mismatch is intentionally | |
| 7 | + | not included here. Its fix is being handled in PR #254. The two observations | |
| 8 | + | below remain; neither produced an escaping bypass or memory-safety issue in the | |
| 9 | + | tests performed. | |
| 10 | + | ||
| 11 | + | | # | Finding | Severity | Introduced | First released | | |
| 12 | + | | --- | --- | --- | --- | --- | | |
| 13 | + | | 1 | `clean_title` truncation splits HTML entities | Low | `7b2b46d5` | v2.14.0 | | |
| 14 | + | | 2 | `clean_remote_content` is not idempotent after one pass | Informational | dependency behaviour | n/a | | |
| 15 | + | ||
| 16 | + | ## 1. `clean_title` truncation splits HTML entities | |
| 17 | + | ||
| 18 | + | **Impact.** A truncated title can contain a partial HTML entity and render as | |
| 19 | + | corrupted text. Cleaning is not idempotent. No escaping bypass was observed; | |
| 20 | + | the fragment remains escaped. | |
| 21 | + | ||
| 22 | + | **Mechanism.** `clean_title` first calls `clean_html_all`, which escapes `<` as | |
| 23 | + | `<`, and then truncates the resulting string by characters. The truncation | |
| 24 | + | can land inside an entity, leaving a suffix such as `&l`. A second cleaning pass | |
| 25 | + | escapes that bare `&` as `&`, so the result changes again. | |
| 26 | + | ||
| 27 | + | Reproducer: | |
| 28 | + | ||
| 29 | + | ```rust | |
| 30 | + | use mitra_validators::posts::clean_title; | |
| 31 | + | ||
| 32 | + | let input = format!("aaa{}", "<".repeat(75)); | |
| 33 | + | let once = clean_title(&input); | |
| 34 | + | let twice = clean_title(&once); | |
| 35 | + | ||
| 36 | + | assert_ne!(once, twice); | |
| 37 | + | ``` | |
| 38 | + | ||
| 39 | + | Observed tails: | |
| 40 | + | ||
| 41 | + | ```text | |
| 42 | + | pass 1: ...lt;<&l... | |
| 43 | + | pass 2: ...lt;<&a... | |
| 44 | + | ``` | |
| 45 | + | ||
| 46 | + | **History.** Commit `7b2b46d5` (2024-03-23, v2.14.0), "Remove HTML tags from | |
| 47 | + | post title", changed the first step from `title.trim()` to | |
| 48 | + | `clean_html_all(title).trim().to_owned()`. This placed escaped entities in the | |
| 49 | + | string that was already being truncated by character count. | |
| 50 | + | ||
| 51 | + | **Possible handling.** Ensure truncation cannot cut an `&...;` sequence, or | |
| 52 | + | truncate before HTML escaping in a way that preserves the sanitizer's output | |
| 53 | + | guarantees. The character-count change from PR #254 does not address this | |
| 54 | + | separate behavior. | |
| 55 | + | ||
| 56 | + | ## 2. `clean_remote_content` is not idempotent after one pass | |
| 57 | + | ||
| 58 | + | **Impact.** Informational. The observed input converges by the third pass and no | |
| 59 | + | dangerous markup survives. This is recorded because sanitizer output that | |
| 60 | + | re-parses into a different tree has the same general shape as mutation-XSS | |
| 61 | + | issues, even though no exploit was found here. | |
| 62 | + | ||
| 63 | + | **Mechanism.** An unknown tag leaves the HTML parser in a state where a | |
| 64 | + | following heading is re-nested on the next pass. | |
| 65 | + | ||
| 66 | + | Input: | |
| 67 | + | ||
| 68 | + | ```text | |
| 69 | + | v555<h4><hh4><h3>55\t | |
| 70 | + | ``` | |
| 71 | + | ||
| 72 | + | Output over repeated calls to `clean_remote_content`: | |
| 73 | + | ||
| 74 | + | ```text | |
| 75 | + | pass 1: v555<h4><h3>55\t</h3></h4> | |
| 76 | + | pass 2: v555<h4></h4><h3>55\t</h3> | |
| 77 | + | pass 3: v555<h4></h4><h3>55\t</h3> | |
| 78 | + | ``` | |
| 79 | + | ||
| 80 | + | `clean_remote_content` uses `clean_html`, which relies on ammonia's default tag | |
| 81 | + | allowlist. The `h3` and `h4` tags are not from Mitra's `CONTENT_ALLOWED_TAGS`; | |
| 82 | + | that list applies only to the strict local-content path. | |
| 83 | + | ||
| 84 | + | **History.** This was not traced to a Mitra defect. It is html5ever's reparse | |
| 85 | + | behavior for invalid heading nesting, exposed through ammonia. The tested | |
| 86 | + | versions were `ammonia 4.1.4` and `html5ever 0.39.0`. | |
| 87 | + | `clean_remote_content` itself dates to `d7750442` (2025-02-26, v3.18.0), | |
| 88 | + | "Refactor content cleaners", while the underlying parser behavior predates it. | |
| 89 | + | ||
| 90 | + | **Current fuzz-harness handling.** The `html_sanitize` target asserts | |
| 91 | + | convergence within eight passes and checks that no executable tags survive, | |
| 92 | + | rather than requiring one-pass idempotence. This keeps the target useful while | |
| 93 | + | retaining coverage of the known behavior. | |
nak / mitra vibe fuzz
Last active 2 hours ago
Findings from a vibe fuzzing session I can PR some fix attempts here too if you want, but wanted to at least pass the remaining 2 findings upstream
nak revised this gist 2 hours ago · d512e53
1 file changed, 93 insertions