max / makenotwork
| 1 | //! Structured fuzz over the custom-pages CSS sanitizer. |
| 2 | //! |
| 3 | //! Row 2 of `astra-soak-overview`, and the other half of the same trust |
| 4 | //! boundary as the `html` target: creator-authored input rendered on a public |
| 5 | //! page. What CSS can do that HTML cannot is escape the user canvas and restyle |
| 6 | //! platform chrome, so scoping is asserted here alongside the safety floor. |
| 7 | //! |
| 8 | //! ## The oracle lives in the crate, not here |
| 9 | //! |
| 10 | //! Everything asserted is `custom_pages::oracle::check_css`, the same function |
| 11 | //! `tests/regressions.rs` replays on stable. A crash found here becomes a |
| 12 | //! permanent test by copying one file into `fuzz/regressions/`, and neither |
| 13 | //! side can drift into checking less than the other. |
| 14 | //! |
| 15 | //! It covers BOTH scoping entry points. The doors were counted before this was |
| 16 | //! written, as the task instructs: nine call sites, all in MNW server, but two |
| 17 | //! entry points -- `sanitize_css` for a profile or project page and |
| 18 | //! `sanitize_item_css` for the item pages that wear the parent project's |
| 19 | //! styling re-scoped to `.item-canvas#ic-`. Only the first had an oracle over |
| 20 | //! it until this target was built. |
| 21 | //! |
| 22 | //! ## It parses the output rather than scanning it |
| 23 | //! |
| 24 | //! Worth knowing before reading a finding from this target. The oracle it |
| 25 | //! replaced matched substrings over the whole stylesheet and had four false |
| 26 | //! positives, the plainest of which was a two-step `@keyframes`: the step |
| 27 | //! `100%` read as a selector that had escaped the canvas. Ordinary creator CSS, |
| 28 | //! reported as a security finding. What ships reparses the printed sheet and |
| 29 | //! walks the AST, so `@import` is forbidden as a rule rather than as a string |
| 30 | //! and a `content: "@import"` is what it is -- text. |
| 31 | |
| 32 | |
| 33 | |
| 34 | use fuzz_target; |
| 35 | use LazyLock; |
| 36 | |
| 37 | /// The owner scope woven into the canvas selector, and the one |
| 38 | /// `tests/regressions.rs` replays with, so a crash reproduces there unchanged. |
| 39 | /// It must stay id-safe: the sanitizer refuses a scope that is not, and fuzzing |
| 40 | /// that refusal would only measure `is_id_safe`. |
| 41 | const OWNER_SCOPE: &str = "11111111-1111-1111-1111-111111111111"; |
| 42 | |
| 43 | static POLICY: = new |
| 44 | new |
| 45 | "https://u.makenot.work/alice/proj", |
| 46 | |
| 47 | "makenot.work".to_string, |
| 48 | "u.makenot.work".to_string, |
| 49 | "cdn.makenot.work".to_string, |
| 50 | ], |
| 51 | |
| 52 | .expect |
| 53 | ; |
| 54 | |
| 55 | /// The oracle runs on its own thread, with a large stack. |
| 56 | /// |
| 57 | /// Not a workaround for a defect in the crate: the SANITIZER is fine on a 2 MB |
| 58 | /// stack, which is what tokio gives a worker, and that was measured against the |
| 59 | /// input that prompted this (243 bytes in, 381 KB of flattened CSS out, survives |
| 60 | /// at 2 MB). What needs the room is the ORACLE's own reparse of that output, |
| 61 | /// which recurses per nesting level in a build where AddressSanitizer makes |
| 62 | /// every frame several times its normal size. |
| 63 | /// |
| 64 | /// The alternative was to stop checking large outputs, which would have blinded |
| 65 | /// the target on exactly the inputs most worth checking -- the ones that |
| 66 | /// amplify. Stack is cheaper than coverage. |
| 67 | |
| 68 | let owned = input.to_string; |
| 69 | let handle = new |
| 70 | .stack_size |
| 71 | .spawn |
| 72 | .expect; |
| 73 | // Propagate a panic rather than swallowing it: the panic IS the finding, and |
| 74 | // a target that joined and ignored the result would report clean forever. |
| 75 | if let Err = handle.join |
| 76 | resume_unwind; |
| 77 | |
| 78 | |
| 79 | |
| 80 | fuzz_target! |
| 81 | checked; |
| 82 | ; |
| 83 |