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