1 //! A fuzz testing oracle for roundtrip assembly-disassembly.
2 //!
3 //! This contains manual implementations of the `Arbitrary` trait for types
4 //! throughout this crate to avoid depending on the `arbitrary` crate
5 //! unconditionally (use the `fuzz` feature instead).
6 
7 use crate::{
8     AmodeOffset, AmodeOffsetPlusKnownOffset, AsReg, CodeSink, Constant, Fixed, Gpr, Inst, Label,
9     NonRspGpr, Registers, TrapCode, Xmm,
10 };
11 use arbitrary::{Arbitrary, Result, Unstructured};
12 use capstone::{Capstone, arch::BuildsCapstone, arch::BuildsCapstoneSyntax, arch::x86};
13 
14 /// Take a random assembly instruction and check its encoding and
15 /// pretty-printing against a known-good disassembler.
16 ///
17 /// # Panics
18 ///
19 /// This function panics to express failure as expected by the `arbitrary`
20 /// fuzzer infrastructure. It may fail during assembly, disassembly, or when
21 /// comparing the disassembled strings.
22 pub fn roundtrip(inst: &Inst<FuzzRegs>) {
23     // Check that we can actually assemble this instruction.
24     let assembled = assemble(inst);
25     let expected = disassemble(&assembled, inst);
26 
27     // Check that our pretty-printed output matches the known-good output. Trim
28     // off the instruction offset first.
29     let expected = expected.split_once(' ').unwrap().1;
30     let actual = inst.to_string();
31     if expected != actual && expected.trim() != fix_up(&actual) {
32         println!("> {inst}");
33         println!("  debug: {inst:x?}");
34         println!("  assembled: {}", pretty_print_hexadecimal(&assembled));
35         println!("  expected (capstone): {expected}");
36         println!("  actual (to_string):  {actual}");
37         assert_eq!(expected, &actual);
38     }
39 }
40 
41 /// Use this assembler to emit machine code into a byte buffer.
42 ///
43 /// This will skip any traps or label registrations, but this is fine for the
44 /// single-instruction disassembly we're doing here.
45 fn assemble(inst: &Inst<FuzzRegs>) -> Vec<u8> {
46     let mut sink = TestCodeSink::default();
47     let offsets: Vec<i32> = Vec::new();
48     inst.encode(&mut sink, &offsets);
49     sink.patch_labels_as_if_they_referred_to_end();
50     sink.buf
51 }
52 
53 #[derive(Default)]
54 struct TestCodeSink {
55     buf: Vec<u8>,
56     offsets_using_label: Vec<u32>,
57 }
58 
59 impl TestCodeSink {
60     /// References to labels, e.g. RIP-relative addressing, is stored with an
61     /// adjustment that takes into account the distance from the relative offset
62     /// to the end of the instruction, where the offset is relative to. That
63     /// means that to indeed make the offset relative to the end of the
64     /// instruction, which is what we pretend all labels are bound to, it's
65     /// required that this adjustment is taken into account.
66     ///
67     /// This function will iterate over all labels bound to this code sink and
68     /// pretend the label is found at the end of the `buf`. That means that the
69     /// distance from the label to the end of `buf` minus 4, which is the width
70     /// of the offset, is added to what's already present in the encoding buffer.
71     ///
72     /// This is effectively undoing the `bytes_at_end` adjustment that's part of
73     /// `Amode::RipRelative` addressing.
74     fn patch_labels_as_if_they_referred_to_end(&mut self) {
75         let len = i32::try_from(self.buf.len()).unwrap();
76         for offset in self.offsets_using_label.iter() {
77             let range = self.buf[*offset as usize..].first_chunk_mut::<4>().unwrap();
78             let offset = i32::try_from(*offset).unwrap() + 4;
79             let rel_distance = len - offset;
80             *range = (i32::from_le_bytes(*range) + rel_distance).to_le_bytes();
81         }
82     }
83 }
84 
85 impl CodeSink for TestCodeSink {
86     fn put1(&mut self, v: u8) {
87         self.buf.extend_from_slice(&[v]);
88     }
89 
90     fn put2(&mut self, v: u16) {
91         self.buf.extend_from_slice(&v.to_le_bytes());
92     }
93 
94     fn put4(&mut self, v: u32) {
95         self.buf.extend_from_slice(&v.to_le_bytes());
96     }
97 
98     fn put8(&mut self, v: u64) {
99         self.buf.extend_from_slice(&v.to_le_bytes());
100     }
101 
102     fn add_trap(&mut self, _: TrapCode) {}
103 
104     fn current_offset(&self) -> u32 {
105         self.buf.len().try_into().unwrap()
106     }
107 
108     fn use_label_at_offset(&mut self, offset: u32, _: Label) {
109         self.offsets_using_label.push(offset);
110     }
111 
112     fn get_label_for_constant(&mut self, c: Constant) -> Label {
113         Label(c.0)
114     }
115 }
116 
117 /// Building a new `Capstone` each time is suboptimal (TODO).
118 fn disassemble(assembled: &[u8], original: &Inst<FuzzRegs>) -> String {
119     let cs = Capstone::new()
120         .x86()
121         .mode(x86::ArchMode::Mode64)
122         .syntax(x86::ArchSyntax::Att)
123         .detail(true)
124         .build()
125         .expect("failed to create Capstone object");
126     let insts = cs
127         .disasm_all(assembled, 0x0)
128         .expect("failed to disassemble");
129 
130     if insts.len() != 1 {
131         println!("> {original}");
132         println!("  debug: {original:x?}");
133         println!("  assembled: {}", pretty_print_hexadecimal(&assembled));
134         assert_eq!(insts.len(), 1, "not a single instruction");
135     }
136 
137     let inst = insts.first().expect("at least one instruction");
138     if assembled.len() != inst.len() {
139         println!("> {original}");
140         println!("  debug: {original:x?}");
141         println!("  assembled: {}", pretty_print_hexadecimal(&assembled));
142         println!(
143             "  capstone-assembled: {}",
144             pretty_print_hexadecimal(inst.bytes())
145         );
146         assert_eq!(assembled.len(), inst.len(), "extra bytes not disassembled");
147     }
148 
149     inst.to_string()
150 }
151 
152 fn pretty_print_hexadecimal(hex: &[u8]) -> String {
153     use std::fmt::Write;
154     let mut s = String::with_capacity(hex.len() * 2);
155     for b in hex {
156         write!(&mut s, "{b:02X}").unwrap();
157     }
158     s
159 }
160 
161 /// See `replace_signed_immediates`.
162 macro_rules! hex_print_signed_imm {
163     ($hex:expr, $from:ty => $to:ty) => {{
164         let imm = <$from>::from_str_radix($hex, 16).unwrap() as $to;
165         let mut simm = String::new();
166         if imm < 0 {
167             simm.push_str("-");
168         }
169         let abs = match imm.checked_abs() {
170             Some(i) => i,
171             None => <$to>::MIN,
172         };
173         if imm > -10 && imm < 10 {
174             simm.push_str(&format!("{:x}", abs));
175         } else {
176             simm.push_str(&format!("0x{:x}", abs));
177         }
178         simm
179     }};
180 }
181 
182 /// Replace signed immediates in the disassembly with their unsigned hexadecimal
183 /// equivalent. This is only necessary to match `capstone`'s complex
184 /// pretty-printing rules; e.g. `capstone` will:
185 /// - omit the `0x` prefix when printing `0x0` as `0`.
186 /// - omit the `0x` prefix when print small values (less than 10)
187 /// - print negative values as `-0x...` (signed hex) instead of `0xff...`
188 ///   (normal hex)
189 /// - print `mov` immediates as base-10 instead of base-16 (?!).
190 fn replace_signed_immediates(dis: &str) -> std::borrow::Cow<'_, str> {
191     match dis.find('$') {
192         None => dis.into(),
193         Some(idx) => {
194             let (prefix, rest) = dis.split_at(idx + 1); // Skip the '$'.
195             let (_, rest) = chomp("-", rest); // Skip the '-' if it's there.
196             let (_, rest) = chomp("0x", rest); // Skip the '0x' if it's there.
197             let n = rest.chars().take_while(char::is_ascii_hexdigit).count();
198             let (hex, rest) = rest.split_at(n); // Split at next non-hex character.
199             let simm = if dis.starts_with("mov") {
200                 u64::from_str_radix(hex, 16).unwrap().to_string()
201             } else {
202                 match hex.len() {
203                     1 | 2 => hex_print_signed_imm!(hex, u8 => i8),
204                     4 => hex_print_signed_imm!(hex, u16 => i16),
205                     8 => hex_print_signed_imm!(hex, u32 => i32),
206                     16 => hex_print_signed_imm!(hex, u64 => i64),
207                     _ => panic!("unexpected length for hex: {hex}"),
208                 }
209             };
210             format!("{prefix}{simm}{rest}").into()
211         }
212     }
213 }
214 
215 // See `replace_signed_immediates`.
216 fn chomp<'a>(pat: &str, s: &'a str) -> (&'a str, &'a str) {
217     if s.starts_with(pat) {
218         s.split_at(pat.len())
219     } else {
220         ("", s)
221     }
222 }
223 
224 #[test]
225 fn replace() {
226     assert_eq!(
227         replace_signed_immediates("andl $0xffffff9a, %r11d"),
228         "andl $-0x66, %r11d"
229     );
230     assert_eq!(
231         replace_signed_immediates("xorq $0xffffffffffffffbc, 0x7f139ecc(%r9)"),
232         "xorq $-0x44, 0x7f139ecc(%r9)"
233     );
234     assert_eq!(
235         replace_signed_immediates("subl $0x3ca77a19, -0x1a030f40(%r14)"),
236         "subl $0x3ca77a19, -0x1a030f40(%r14)"
237     );
238     assert_eq!(
239         replace_signed_immediates("movq $0xffffffff864ae103, %rsi"),
240         "movq $18446744071667638531, %rsi"
241     );
242 }
243 
244 /// Remove everything after the first semicolon in the disassembly and trim any
245 /// trailing spaces. This is necessary to remove the implicit operands we end up
246 /// printing for Cranelift's sake.
247 fn remove_after_semicolon(dis: &str) -> &str {
248     match dis.find(';') {
249         None => dis,
250         Some(idx) => {
251             let (prefix, _) = dis.split_at(idx);
252             prefix.trim()
253         }
254     }
255 }
256 
257 #[test]
258 fn remove_after_parenthesis_test() {
259     assert_eq!(
260         remove_after_semicolon("imulb 0x7658eddd(%rcx) ;; implicit: %ax"),
261         "imulb 0x7658eddd(%rcx)"
262     );
263 }
264 
265 /// Run some post-processing on the disassembly to make it match Capstone.
266 fn fix_up(dis: &str) -> std::borrow::Cow<'_, str> {
267     let dis = remove_after_semicolon(dis);
268     replace_signed_immediates(&dis)
269 }
270 
271 /// Fuzz-specific registers.
272 ///
273 /// For the fuzzer, we do not need any fancy register types; see [`FuzzReg`].
274 #[derive(Arbitrary, Debug)]
275 pub struct FuzzRegs;
276 
277 impl Registers for FuzzRegs {
278     type ReadGpr = FuzzReg;
279     type ReadWriteGpr = FuzzReg;
280     type WriteGpr = FuzzReg;
281     type ReadXmm = FuzzReg;
282     type ReadWriteXmm = FuzzReg;
283     type WriteXmm = FuzzReg;
284 }
285 
286 /// A simple `u8` register type for fuzzing only.
287 #[derive(Clone, Copy, Debug, PartialEq)]
288 pub struct FuzzReg(u8);
289 
290 impl<'a> Arbitrary<'a> for FuzzReg {
291     fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
292         Ok(Self(u.int_in_range(0..=15)?))
293     }
294 }
295 
296 impl AsReg for FuzzReg {
297     fn new(enc: u8) -> Self {
298         Self(enc)
299     }
300     fn enc(&self) -> u8 {
301         self.0
302     }
303 }
304 
305 impl Arbitrary<'_> for AmodeOffsetPlusKnownOffset {
306     fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> {
307         // For now, we don't generate offsets (TODO).
308         Ok(Self {
309             simm32: AmodeOffset::arbitrary(u)?,
310             offset: None,
311         })
312     }
313 }
314 
315 impl<R: AsReg, const E: u8> Arbitrary<'_> for Fixed<R, E> {
316     fn arbitrary(_: &mut Unstructured<'_>) -> Result<Self> {
317         Ok(Self::new(E))
318     }
319 }
320 
321 impl<R: AsReg> Arbitrary<'_> for NonRspGpr<R> {
322     fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> {
323         use crate::gpr::enc::*;
324         let gpr = u.choose(&[
325             RAX, RCX, RDX, RBX, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15,
326         ])?;
327         Ok(Self::new(R::new(*gpr)))
328     }
329 }
330 impl<'a, R: AsReg> Arbitrary<'a> for Gpr<R> {
331     fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
332         Ok(Self(R::new(u.int_in_range(0..=15)?)))
333     }
334 }
335 impl<'a, R: AsReg> Arbitrary<'a> for Xmm<R> {
336     fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
337         Ok(Self(R::new(u.int_in_range(0..=15)?)))
338     }
339 }
340 
341 /// Helper trait that's used to be the same as `Registers` except with an extra
342 /// `for<'a> Arbitrary<'a>` bound on all of the associated types.
343 pub trait RegistersArbitrary:
344     Registers<
345         ReadGpr: for<'a> Arbitrary<'a>,
346         ReadWriteGpr: for<'a> Arbitrary<'a>,
347         WriteGpr: for<'a> Arbitrary<'a>,
348         ReadXmm: for<'a> Arbitrary<'a>,
349         ReadWriteXmm: for<'a> Arbitrary<'a>,
350         WriteXmm: for<'a> Arbitrary<'a>,
351     >
352 {
353 }
354 
355 impl<R> RegistersArbitrary for R
356 where
357     R: Registers,
358     R::ReadGpr: for<'a> Arbitrary<'a>,
359     R::ReadWriteGpr: for<'a> Arbitrary<'a>,
360     R::WriteGpr: for<'a> Arbitrary<'a>,
361     R::ReadXmm: for<'a> Arbitrary<'a>,
362     R::ReadWriteXmm: for<'a> Arbitrary<'a>,
363     R::WriteXmm: for<'a> Arbitrary<'a>,
364 {
365 }
366 
367 #[cfg(test)]
368 mod test {
369     use super::*;
370     use arbtest::arbtest;
371     use std::sync::atomic::{AtomicUsize, Ordering};
372 
373     #[test]
374     fn smoke() {
375         let count = AtomicUsize::new(0);
376         arbtest(|u| {
377             let inst: Inst<FuzzRegs> = u.arbitrary()?;
378             roundtrip(&inst);
379             println!("#{}: {inst}", count.fetch_add(1, Ordering::SeqCst));
380             Ok(())
381         })
382         .budget_ms(1_000);
383 
384         // This will run the `roundtrip` fuzzer for one second. To repeatably
385         // test a single input, append `.seed(0x<failing seed>)`.
386     }
387 }
388