Skip to main content

fuzz_tls/tests/fuzzing/
mod.rs

1//! A std-only mutation fuzzer for the TLS record layer, the handshake message parsers and the
2//! X.509 and key parsers (plan option F2, without coverage feedback). Seeds are real handshake
3//! transcripts; mutations are bit flips, byte and length-field overwrites with boundary values,
4//! insertions, deletions, duplication, truncation and splicing. The invariant is "no panic, no
5//! hang, bounded buffers"; any panic is a bug.
6//!
7//! Used by `tests/fuzz.rs` (short run) and `examples/fuzz_tls.rs` (long runs).
8
9#![allow(dead_code)]
10
11use simoom_tls::certgen::{self, CertParams};
12use simoom_tls::keys::{EcCurve, PrivateKey};
13use simoom_tls::quic::{Level, QuicConnection};
14use simoom_tls::x509::{self, Certificate, RootStore};
15use simoom_tls::{CertifiedKey, ClientConfig, ClientConnection, ServerConfig, ServerConnection};
16use std::sync::Arc;
17
18/// xorshift64* generator.
19pub struct Rng(pub u64);
20
21impl Rng {
22    pub fn next(&mut self) -> u64 {
23        self.0 ^= self.0 >> 12;
24        self.0 ^= self.0 << 25;
25        self.0 ^= self.0 >> 27;
26        self.0.wrapping_mul(0x2545_f491_4f6c_dd1d)
27    }
28    pub fn below(&mut self, n: usize) -> usize {
29        if n == 0 {
30            0
31        } else {
32            (self.next() % n as u64) as usize
33        }
34    }
35}
36
37const INTERESTING: [u16; 10] = [0, 1, 2, 0x7f, 0x80, 0xff, 0x100, 0x3fff, 0x4000, 0xffff];
38
39/// One to four stacked mutations of `input`.
40pub fn mutate(rng: &mut Rng, input: &[u8], others: &[Vec<u8>]) -> Vec<u8> {
41    let mut d = input.to_vec();
42    for _ in 0..1 + rng.below(4) {
43        let n = d.len();
44        match rng.below(10) {
45            0 if n > 0 => {
46                let i = rng.below(n);
47                d[i] ^= 1 << rng.below(8);
48            }
49            1 if n > 0 => {
50                let i = rng.below(n);
51                d[i] = rng.next() as u8;
52            }
53            2 if n > 1 => {
54                // a 16-bit field set to a boundary value (lengths are everywhere in TLS)
55                let i = rng.below(n - 1);
56                let v = INTERESTING[rng.below(INTERESTING.len())].to_be_bytes();
57                d[i..i + 2].copy_from_slice(&v);
58            }
59            3 => {
60                let i = rng.below(n + 1);
61                let k = 1 + rng.below(16);
62                for _ in 0..k {
63                    d.insert(i, rng.next() as u8);
64                }
65            }
66            4 if n > 0 => {
67                let i = rng.below(n);
68                let k = 1 + rng.below((n - i).min(32));
69                d.drain(i..i + k);
70            }
71            5 if n > 0 => {
72                let i = rng.below(n);
73                let k = 1 + rng.below((n - i).min(64));
74                let chunk = d[i..i + k].to_vec();
75                let j = rng.below(n + 1);
76                d.splice(j..j, chunk);
77            }
78            6 if n > 0 => d.truncate(rng.below(n)),
79            7 if !others.is_empty() => {
80                let o = &others[rng.below(others.len())];
81                if !o.is_empty() {
82                    let i = rng.below(o.len());
83                    let k = 1 + rng.below((o.len() - i).min(64));
84                    let j = rng.below(n + 1);
85                    d.splice(j..j, o[i..i + k].iter().copied());
86                }
87            }
88            8 if n > 0 => {
89                let i = rng.below(n);
90                d[i] = d[i].wrapping_add(1 + rng.below(3) as u8);
91            }
92            _ if n > 2 => {
93                // 24-bit handshake length field
94                let i = rng.below(n - 2);
95                let v = (rng.next() as u32 & 0x01ff_ffff).to_be_bytes();
96                d[i..i + 3].copy_from_slice(&v[1..]);
97            }
98            _ => {}
99        }
100        if d.len() > 1 << 17 {
101            d.truncate(1 << 17);
102        }
103    }
104    d
105}
106
107/// Seeds and configurations shared by the targets.
108pub struct Corpus {
109    pub scfg: Arc<ServerConfig>,
110    pub ccfg: Arc<ClientConfig>,
111    /// TLS byte streams client -> server: ClientHello record, ClientHello + CCS + garbage.
112    pub to_server: Vec<Vec<u8>>,
113    /// Server -> client first flight (SH, CCS, encrypted records).
114    pub to_client: Vec<Vec<u8>>,
115    /// Raw handshake messages: ClientHello (QUIC flavour), server Handshake-level flight.
116    pub ch_msg: Vec<u8>,
117    pub server_hs_flight: Vec<u8>,
118    pub cert_der: Vec<u8>,
119    pub key_der: Vec<u8>,
120}
121
122pub fn corpus() -> Corpus {
123    let ss = certgen::issue(&CertParams::server(&["localhost"]), PrivateKey::generate_ec(EcCurve::P256), None);
124    let mut roots = RootStore::empty();
125    roots.add_der(&ss.cert_der).unwrap();
126    let scfg = Arc::new(ServerConfig::new(CertifiedKey::new(vec![ss.cert_der.clone()], ss.key.clone()).unwrap()).with_alpn(&[b"h3", b"http/1.1"]));
127    let mut cc = ClientConfig::new(roots).with_alpn(&[b"h3", b"http/1.1"]);
128    cc.sessions = None;
129    let ccfg = Arc::new(cc);
130    // a TLS handshake transcript
131    let mut c = ClientConnection::new(ccfg.clone(), "localhost").unwrap();
132    let mut s = ServerConnection::new(scfg.clone());
133    let ch = c.tls_output().to_vec();
134    c.consume_tls_output(ch.len());
135    s.read_tls(&ch);
136    s.process_new_packets().unwrap();
137    let flight = s.tls_output().to_vec();
138    let mut ch_plus = ch.clone();
139    ch_plus.extend_from_slice(&[20, 3, 3, 0, 1, 1, 23, 3, 3, 0, 20]);
140    ch_plus.extend(std::iter::repeat_n(0xaa, 20));
141    // a QUIC handshake: the ClientHello message and the server's Handshake-level flight
142    let mut qc = QuicConnection::new_client(ccfg.clone(), "localhost", vec![1, 2, 3]).unwrap();
143    let mut qs = QuicConnection::new_server(scfg.clone(), vec![4, 5, 6]);
144    let mut ch_msg = Vec::new();
145    qc.write_handshake(&mut ch_msg);
146    qs.read_handshake(Level::Initial, &ch_msg).unwrap();
147    let mut sh = Vec::new();
148    qs.write_handshake(&mut sh);
149    let mut hs = Vec::new();
150    qs.write_handshake(&mut hs);
151    Corpus {
152        scfg,
153        ccfg,
154        to_server: vec![ch, ch_plus],
155        to_client: vec![flight],
156        ch_msg,
157        server_hs_flight: hs,
158        key_der: ss.key_der.clone(),
159        cert_der: ss.cert_der,
160    }
161}
162
163/// Feeds `data` in random-sized pieces and processes after each; checks that errors stick.
164fn feed(rng: &mut Rng, conn: &mut simoom_tls::Connection, data: &[u8]) {
165    let mut off = 0;
166    while off < data.len() {
167        let k = 1 + rng.below(data.len() - off);
168        let taken = conn.read_tls(&data[off..off + k]);
169        off += taken;
170        let r = conn.process_new_packets();
171        if let Err(e) = r {
172            assert_eq!(conn.process_new_packets().unwrap_err(), e, "errors must stick");
173            assert!(conn.tls_output().len() < 64 * 1024, "output stays bounded");
174            assert!(conn.write_plaintext(b"x").is_err(), "a failed connection refuses writes");
175            return;
176        }
177        if taken == 0 {
178            // buffer full: the application reads plaintext
179            let n = conn.plaintext().len();
180            if n == 0 {
181                return;
182            }
183            conn.consume_plaintext(n);
184        }
185    }
186}
187
188pub fn server_records(rng: &mut Rng, c: &Corpus, iters: usize) {
189    for _ in 0..iters {
190        let seed = &c.to_server[rng.below(c.to_server.len())];
191        let data = mutate(rng, seed, &c.to_client);
192        let mut s = ServerConnection::new(c.scfg.clone());
193        feed(rng, &mut s, &data);
194    }
195}
196
197pub fn client_records(rng: &mut Rng, c: &Corpus, iters: usize) {
198    for _ in 0..iters {
199        let data = mutate(rng, &c.to_client[0], &c.to_server);
200        let mut cl = ClientConnection::new(c.ccfg.clone(), "localhost").unwrap();
201        let n = cl.tls_output().len();
202        cl.consume_tls_output(n);
203        feed(rng, &mut cl, &data);
204    }
205}
206
207/// ClientHello parsing (and everything the server does with it) without the record layer.
208pub fn quic_server_messages(rng: &mut Rng, c: &Corpus, iters: usize) {
209    for _ in 0..iters {
210        let m = mutate(rng, &c.ch_msg, &[]);
211        let mut s = QuicConnection::new_server(c.scfg.clone(), vec![9]);
212        if s.read_handshake(Level::Initial, &m).is_err() {
213            assert!(s.tls_alert().is_some());
214        }
215        let mut out = Vec::new();
216        while s.write_handshake(&mut out).is_some() {}
217    }
218}
219
220/// EncryptedExtensions, Certificate (X.509), CertificateVerify and Finished parsing on the
221/// client: a real ServerHello, then a mutated Handshake-level flight.
222pub fn quic_client_messages(rng: &mut Rng, c: &Corpus, iters: usize) {
223    for _ in 0..iters {
224        let mut qc = QuicConnection::new_client(c.ccfg.clone(), "localhost", vec![1]).unwrap();
225        let mut qs = QuicConnection::new_server(c.scfg.clone(), vec![2]);
226        let mut ch = Vec::new();
227        qc.write_handshake(&mut ch);
228        qs.read_handshake(Level::Initial, &ch).unwrap();
229        let mut sh = Vec::new();
230        qs.write_handshake(&mut sh);
231        let mut hs = Vec::new();
232        qs.write_handshake(&mut hs);
233        qc.read_handshake(Level::Initial, &sh).unwrap();
234        let mut ignore = Vec::new();
235        qc.write_handshake(&mut ignore);
236        let m = mutate(rng, &hs, &[c.cert_der.clone()]);
237        let _ = qc.read_handshake(Level::Handshake, &m);
238    }
239}
240
241pub fn x509_and_keys(rng: &mut Rng, c: &Corpus, iters: usize) {
242    let mut roots = RootStore::empty();
243    roots.add_der(&c.cert_der).unwrap();
244    for _ in 0..iters {
245        let d = mutate(rng, &c.cert_der, &[]);
246        if let Ok(cert) = Certificate::from_der(&d) {
247            let _ = cert.matches_hostname("localhost");
248        }
249        let _ = x509::verify_chain(&[d], Some("localhost"), &roots, 1_800_000_000, x509::OID_EKU_SERVER_AUTH);
250        let k = mutate(rng, &c.key_der, &[]);
251        let _ = PrivateKey::from_der(&k);
252        let pem = simoom_tls::pem::encode_pem("PRIVATE KEY", &k);
253        let p = mutate(rng, pem.as_bytes(), &[]);
254        let _ = PrivateKey::from_pem(&p);
255    }
256}