| 1 | #![no_main] |
| 2 | //! Chains text edits on a protected section kept open under its key, now and then writing |
| 3 | //! it anew under another password: every sealed image opens with the key, reads back as the |
| 4 | //! model predicts and stores none of the new text in clear. Text typed at the end of a text |
| 5 | //! whose empty final run keeps an insertion style takes that style, which the page model |
| 6 | //! cannot show, so only its characters are predicted. |
| 7 | use libfuzzer_sys::fuzz_target; |
| 8 | use onestore::{ |
| 9 | Arena, ExGuid, Section, |
| 10 | op::{Edit, Op, PageOp, predict}, |
| 11 | page::{Page, PageObject}, |
| 12 | protected::{Key, rekey}, |
| 13 | }; |
| 14 | use std::sync::LazyLock; |
| 15 | |
| 16 | const SOURCE: &[u8] = |
| 17 | include_bytes!("../../corpus/native-encrypted/encrypted-01/notebook/synthetic.one"); |
| 18 | static KEYS: LazyLock<[Key; 2]> = LazyLock::new(|| { |
| 19 | [ |
| 20 | Key::open(SOURCE, "fictitious-only").unwrap(), |
| 21 | Key::new("another fictitious password").unwrap(), |
| 22 | ] |
| 23 | }); |
| 24 | |
| 25 | /// The first page. |
| 26 | fn page(bytes: &[u8], key: &Key) -> (ExGuid, Page) { |
| 27 | let arena = Arena::default(); |
| 28 | let mut section = Section::unlock(&arena, bytes.to_vec(), key).unwrap(); |
| 29 | let (space, ..) = section.pages().unwrap()[0]; |
| 30 | (space, section.page(space).unwrap()) |
| 31 | } |
| 32 | |
| 33 | fn texts(page: &Page) -> Vec<String> { |
| 34 | page.objects |
| 35 | .iter() |
| 36 | .filter_map(|object| match object { |
| 37 | PageObject::Outline(outline) => Some(&outline.paragraphs), |
| 38 | _ => None, |
| 39 | }) |
| 40 | .flatten() |
| 41 | .filter_map(|paragraph| Some(paragraph.text()?.text.text().to_owned())) |
| 42 | .collect() |
| 43 | } |
| 44 | |
| 45 | fuzz_target!(|data: &[u8]| { |
| 46 | let mut bytes = SOURCE.to_vec(); |
| 47 | let mut key = &KEYS[0]; |
| 48 | for (at, step) in (0..).zip(data.chunks(12).take(4)) { |
| 49 | if step.len() < 4 { |
| 50 | return; |
| 51 | } |
| 52 | if step[3] == 0xff { |
| 53 | let other = &KEYS[usize::from(std::ptr::eq(key, &KEYS[0]))]; |
| 54 | let before = page(&bytes, key).1; |
| 55 | bytes = rekey(&bytes, Some(key), Some(other)).unwrap(); |
| 56 | key = other; |
| 57 | assert_eq!(texts(&page(&bytes, key).1), texts(&before)); |
| 58 | continue; |
| 59 | } |
| 60 | let (space, mut expected) = page(&bytes, key); |
| 61 | let candidates: Vec<_> = expected |
| 62 | .objects |
| 63 | .iter() |
| 64 | .filter_map(|object| match object { |
| 65 | PageObject::Outline(outline) => Some(&outline.paragraphs), |
| 66 | _ => None, |
| 67 | }) |
| 68 | .flatten() |
| 69 | .filter_map(|paragraph| paragraph.text()) |
| 70 | .collect(); |
| 71 | let text = candidates[usize::from(step[0]) % candidates.len()]; |
| 72 | let end = text.text.utf16_offset(text.text.text().len()).unwrap(); |
| 73 | let start = u32::from(step[1]) % (end + 1); |
| 74 | let stop = (start + u32::from(step[2]) % 8).min(end); |
| 75 | // Marked so its absence from the stored bytes is checkable. |
| 76 | let inserted = format!( |
| 77 | "\u{1f512}sealed\u{1f512}{}", |
| 78 | String::from_utf8_lossy(&step[3..]).replace(['\0', '\n', '\u{fffc}'], "") |
| 79 | ); |
| 80 | let op = PageOp::Text { |
| 81 | text: text.id, |
| 82 | range: start..stop, |
| 83 | with: inserted.clone(), |
| 84 | }; |
| 85 | if predict(&mut expected, &op).is_err() { |
| 86 | return; |
| 87 | } |
| 88 | let edit = Edit { |
| 89 | at: 133_000_000_000_000_000 + at * 10_000_000, |
| 90 | ops: vec![Op::Page { space, op }], |
| 91 | }; |
| 92 | let arena = Arena::default(); |
| 93 | let mut section = Section::unlock(&arena, bytes.clone(), key).unwrap(); |
| 94 | section.apply("Fuzz", &edit).unwrap(); |
| 95 | let transaction = section.seal().unwrap().unwrap(); |
| 96 | let mut written = bytes.clone(); |
| 97 | transaction.apply(&mut written).unwrap(); |
| 98 | let clear: Vec<u8> = inserted.encode_utf16().flat_map(u16::to_le_bytes).collect(); |
| 99 | assert!( |
| 100 | !written[bytes.len()..] |
| 101 | .windows(clear.len()) |
| 102 | .any(|w| w == clear) |
| 103 | ); |
| 104 | assert_eq!(texts(&page(&written, key).1), texts(&expected)); |
| 105 | bytes = written; |
| 106 | } |
| 107 | }); |