From 7186a7d8b58277e0081d4a32113e4bbaacaaab14 Mon Sep 17 00:00:00 2001 From: vinayak sharma Date: Fri, 6 Mar 2026 23:08:32 +0530 Subject: [PATCH] feat: implement 128-bit comparison logging (AVX/SIMD) --- crates/libafl/src/mutators/token_mutations.rs | 106 ++++++++++++++++++ crates/libafl/src/observers/cmp.rs | 74 +++++++++--- crates/libafl_targets/src/cmps/mod.rs | 63 ++++++++--- .../src/cmps/observers/aflpp.rs | 4 +- 4 files changed, 213 insertions(+), 34 deletions(-) diff --git a/crates/libafl/src/mutators/token_mutations.rs b/crates/libafl/src/mutators/token_mutations.rs index bcde4c1f49..1ef2278f23 100644 --- a/crates/libafl/src/mutators/token_mutations.rs +++ b/crates/libafl/src/mutators/token_mutations.rs @@ -577,6 +577,35 @@ where } } } + CmpValues::U128((v1, v2, v1_is_const)) => { + if len >= size_of::() { + for i in off..=len - size_of::() { + let val = + u128::from_ne_bytes(bytes[i..i + size_of::()].try_into().unwrap()); + if !v1_is_const && val == *v1 { + let new_bytes = v2.to_ne_bytes(); + bytes[i..i + size_of::()].copy_from_slice(&new_bytes); + result = MutationResult::Mutated; + break; + } else if !v1_is_const && val.swap_bytes() == *v1 { + let new_bytes = v2.swap_bytes().to_ne_bytes(); + bytes[i..i + size_of::()].copy_from_slice(&new_bytes); + result = MutationResult::Mutated; + break; + } else if val == *v2 { + let new_bytes = v1.to_ne_bytes(); + bytes[i..i + size_of::()].copy_from_slice(&new_bytes); + result = MutationResult::Mutated; + break; + } else if val.swap_bytes() == *v2 { + let new_bytes = v1.swap_bytes().to_ne_bytes(); + bytes[i..i + size_of::()].copy_from_slice(&new_bytes); + result = MutationResult::Mutated; + break; + } + } + } + } CmpValues::Bytes(v) => { 'outer: for i in off..len { let mut size = core::cmp::min(v.0.len(), len - i); @@ -789,6 +818,39 @@ where } } } + CmpValues::U128(v) => { + let cmp_size = random_slice_size::<{ size_of::() }, S>(state); + + if len >= cmp_size { + for i in off..(len - (cmp_size - 1)) { + let mut val_bytes = [0; size_of::()]; + val_bytes[..cmp_size].copy_from_slice(&bytes[i..i + cmp_size]); + let val = u128::from_ne_bytes(val_bytes); + + if val == v.0 { + let new_bytes = &v.1.to_ne_bytes()[..cmp_size]; + bytes[i..i + cmp_size].copy_from_slice(new_bytes); + result = MutationResult::Mutated; + break; + } else if val == v.1 { + let new_bytes = &v.0.to_ne_bytes()[..cmp_size]; + bytes[i..i + cmp_size].copy_from_slice(new_bytes); + result = MutationResult::Mutated; + break; + } else if val.swap_bytes() == v.0 { + let new_bytes = v.1.swap_bytes().to_ne_bytes(); + bytes[i..i + cmp_size].copy_from_slice(&new_bytes[..cmp_size]); + result = MutationResult::Mutated; + break; + } else if val.swap_bytes() == v.1 { + let new_bytes = v.0.swap_bytes().to_ne_bytes(); + bytes[i..i + cmp_size].copy_from_slice(&new_bytes[..cmp_size]); + result = MutationResult::Mutated; + break; + } + } + } + } CmpValues::Bytes(v) => { 'outer: for i in off..len { let mut size = core::cmp::min(v.0.len(), len - i); @@ -1798,6 +1860,50 @@ where } } } + // U128 comparisons: pass only the low 64 bits to cmp_extend_encoding + // (the existing API is u64-based; 128-bit magic values are still useful + // as autotoken candidates even without full extend-encoding support) + (CmpValues::U128(orig), CmpValues::U128(new)) => { + let orig_v0 = orig.0 as u64; + let orig_v1 = orig.1 as u64; + let new_v0 = new.0 as u64; + let new_v1 = new.1 as u64; + let attribute = header.attribute().value(); + + if new_v0 != orig_v0 && orig_v0 != orig_v1 { + self.cmp_extend_encoding( + orig_v0, + orig_v1, + new_v0, + new_v1, + attribute, + new_bytes, + orig_bytes, + cmp_buf_idx, + taint_len, + input_len, + hshape, + &mut ret, + )?; + } + + if new_v1 != orig_v1 && orig_v0 != orig_v1 { + self.cmp_extend_encoding( + orig_v1, + orig_v0, + new_v1, + new_v0, + Self::swapa(attribute), + new_bytes, + orig_bytes, + cmp_buf_idx, + taint_len, + input_len, + hshape, + &mut ret, + )?; + } + } (CmpValues::Bytes(orig), CmpValues::Bytes(new)) => { let (orig_v0, orig_v1, new_v0, new_v1) = (&orig.0, &orig.1, &new.0, &new.1); diff --git a/crates/libafl/src/observers/cmp.rs b/crates/libafl/src/observers/cmp.rs index 084eee922c..4d415dc2a1 100644 --- a/crates/libafl/src/observers/cmp.rs +++ b/crates/libafl/src/observers/cmp.rs @@ -13,19 +13,19 @@ use serde::{Deserialize, Serialize}; use crate::{Error, HasMetadata, executors::ExitKind, observers::Observer}; -/// A bytes string for cmplog with up to 32 elements. -#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq)] +/// A bytes string for cmplog with up to 64 elements. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct CmplogBytes { - buf: [u8; 32], + buf: [u8; 64], len: u8, } impl CmplogBytes { /// Creates a new [`CmplogBytes`] object from the provided buf and length. - /// Lengths above 32 are illegal but will be ignored. + /// Lengths above 64 are illegal but will be ignored. #[must_use] - pub fn from_buf_and_len(buf: [u8; 32], len: u8) -> Self { - debug_assert!(len <= 32, "Len too big: {len}, max: 32"); + pub fn from_buf_and_len(buf: [u8; 64], len: u8) -> Self { + debug_assert!(len <= 64, "Len too big: {len}, max: 64"); CmplogBytes { buf, len } } } @@ -46,6 +46,49 @@ impl HasLen for CmplogBytes { } } +impl Serialize for CmplogBytes { + fn serialize(&self, serializer: S) -> Result { + use serde::ser::SerializeStruct; + let mut s = serializer.serialize_struct("CmplogBytes", 2)?; + s.serialize_field("buf", &self.buf.as_slice())?; + s.serialize_field("len", &self.len)?; + s.end() + } +} + +impl<'de> Deserialize<'de> for CmplogBytes { + fn deserialize>(deserializer: D) -> Result { + use serde::de::{self, MapAccess, Visitor}; + use core::fmt; + struct CmplogBytesVisitor; + impl<'de> Visitor<'de> for CmplogBytesVisitor { + type Value = CmplogBytes; + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct CmplogBytes") + } + fn visit_map>(self, mut map: V) -> Result { + let mut buf_vec: Option> = None; + let mut len: Option = None; + while let Some(key) = map.next_key::()? { + match key.as_str() { + "buf" => { buf_vec = Some(map.next_value()?); } + "len" => { len = Some(map.next_value()?); } + _ => { let _ = map.next_value::()?; } + } + } + let buf_vec = buf_vec.ok_or_else(|| de::Error::missing_field("buf"))?; + let len = len.ok_or_else(|| de::Error::missing_field("len"))?; + let mut buf = [0u8; 64]; + let copy_len = buf_vec.len().min(64); + buf[..copy_len].copy_from_slice(&buf_vec[..copy_len]); + Ok(CmplogBytes { buf, len }) + } + } + const FIELDS: &[&str] = &["buf", "len"]; + deserializer.deserialize_struct("CmplogBytes", FIELDS, CmplogBytesVisitor) + } +} + /// Compare values collected during a run #[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)] pub enum CmpValues { @@ -57,6 +100,8 @@ pub enum CmpValues { U32((u32, u32, bool)), /// (side 1 of comparison, side 2 of comparison, side 1 value is const) U64((u64, u64, bool)), + /// (side 1 of comparison, side 2 of comparison, side 1 value is const) + U128((u128, u128, bool)), /// Two vecs of u8 values/byte Bytes((CmplogBytes, CmplogBytes)), } @@ -67,18 +112,19 @@ impl CmpValues { pub fn is_numeric(&self) -> bool { matches!( self, - CmpValues::U8(_) | CmpValues::U16(_) | CmpValues::U32(_) | CmpValues::U64(_) + CmpValues::U8(_) | CmpValues::U16(_) | CmpValues::U32(_) | CmpValues::U64(_) | CmpValues::U128(_) ) } /// Converts the value to a u64 tuple #[must_use] - pub fn to_u64_tuple(&self) -> Option<(u64, u64, bool)> { + pub fn to_u128_tuple(&self) -> Option<(u128, u128, bool)> { match self { - CmpValues::U8(t) => Some((u64::from(t.0), u64::from(t.1), t.2)), - CmpValues::U16(t) => Some((u64::from(t.0), u64::from(t.1), t.2)), - CmpValues::U32(t) => Some((u64::from(t.0), u64::from(t.1), t.2)), - CmpValues::U64(t) => Some(*t), + CmpValues::U8(t) => Some((u128::from(t.0), u128::from(t.1), t.2)), + CmpValues::U16(t) => Some((u128::from(t.0), u128::from(t.1), t.2)), + CmpValues::U32(t) => Some((u128::from(t.0), u128::from(t.1), t.2)), + CmpValues::U64(t) => Some((u128::from(t.0), u128::from(t.1), t.2)), + CmpValues::U128(t) => Some(*t), CmpValues::Bytes(_) => None, } } @@ -140,8 +186,8 @@ impl CmpValuesMetadata { let mut last: Option = None; for j in 0..execs { if let Some(val) = cmp_map.values_of(i, j) { - if let Some(l) = last.and_then(|x| x.to_u64_tuple()) - && let Some(v) = val.to_u64_tuple() + if let Some(l) = last.and_then(|x| x.to_u128_tuple()) + && let Some(v) = val.to_u128_tuple() { if l.0.wrapping_add(1) == v.0 { increasing_v0 += 1; diff --git a/crates/libafl_targets/src/cmps/mod.rs b/crates/libafl_targets/src/cmps/mod.rs index b52eec3a68..c03f294553 100644 --- a/crates/libafl_targets/src/cmps/mod.rs +++ b/crates/libafl_targets/src/cmps/mod.rs @@ -422,23 +422,34 @@ impl CmpMap for CmpLogMap { self.vals.operands[idx][execution].1, self.vals.operands[idx][execution].2 == 1, ))), - // TODO handle 128 bits & 256 bits & 512 bits cmps - 15 | 31 | 63 => None, + // 128-bit: reconstruct u128 from lo and hi 64-bit halves + 15 => { + let v0_lo = self.vals.operands[idx][execution].0; + let v0_hi = self.vals.operands[idx][execution].1; + let v0 = (v0_hi as u128) << 64 | v0_lo as u128; + let v1_lo = self.vals.operands[idx][execution].1; + let v1_hi = self.vals.operands[idx][execution].0; + let v1 = (v1_hi as u128) << 64 | v1_lo as u128; + let is_const = self.vals.operands[idx][execution].2 == 1; + Some(CmpValues::U128((v0, v1, is_const))) + } + // TODO handle 256 bits & 512 bits cmps + 31 | 63 => None, _ => panic!("Invalid CmpLog shape {shape}"), } } } else { unsafe { - Some(CmpValues::Bytes(( - CmplogBytes::from_buf_and_len( - self.vals.routines[idx][execution].0, - CMPLOG_RTN_LEN as u8, - ), - CmplogBytes::from_buf_and_len( - self.vals.routines[idx][execution].1, - CMPLOG_RTN_LEN as u8, - ), - ))) + Some(CmpValues::Bytes({ + let mut buf0 = [0u8; 64]; + let mut buf1 = [0u8; 64]; + buf0[..CMPLOG_RTN_LEN].copy_from_slice(&self.vals.routines[idx][execution].0); + buf1[..CMPLOG_RTN_LEN].copy_from_slice(&self.vals.routines[idx][execution].1); + ( + CmplogBytes::from_buf_and_len(buf0, CMPLOG_RTN_LEN as u8), + CmplogBytes::from_buf_and_len(buf1, CMPLOG_RTN_LEN as u8), + ) + })) } } } @@ -628,8 +639,18 @@ impl CmpMap for AflppCmpLogMap { self.vals.operands[idx][execution].v1, false, ))), - // TODO handle 128 bits & 256 bits & 512 bits cmps - 15 | 31 | 63 => None, + // 128-bit: reconstruct u128 from v0/v0_128 and v1/v1_128 fields + 15 => { + let v0_lo = self.vals.operands[idx][execution].v0; + let v0_hi = self.vals.operands[idx][execution].v0_128; + let v0 = (v0_hi as u128) << 64 | v0_lo as u128; + let v1_lo = self.vals.operands[idx][execution].v1; + let v1_hi = self.vals.operands[idx][execution].v1_128; + let v1 = (v1_hi as u128) << 64 | v1_lo as u128; + Some(CmpValues::U128((v0, v1, false))) + } + // TODO handle 256 bits & 512 bits cmps + 31 | 63 => None, _ => panic!("Invalid CmpLog shape {shape}"), } } @@ -637,10 +658,16 @@ impl CmpMap for AflppCmpLogMap { unsafe { let v0_len = self.vals.fn_operands[idx][execution].v0_len & (0x80 - 1); let v1_len = self.vals.fn_operands[idx][execution].v1_len & (0x80 - 1); - Some(CmpValues::Bytes(( - CmplogBytes::from_buf_and_len(self.vals.fn_operands[idx][execution].v0, v0_len), - CmplogBytes::from_buf_and_len(self.vals.fn_operands[idx][execution].v1, v1_len), - ))) + Some(CmpValues::Bytes({ + let mut buf0 = [0u8; 64]; + let mut buf1 = [0u8; 64]; + buf0[..32].copy_from_slice(&self.vals.fn_operands[idx][execution].v0); + buf1[..32].copy_from_slice(&self.vals.fn_operands[idx][execution].v1); + ( + CmplogBytes::from_buf_and_len(buf0, v0_len), + CmplogBytes::from_buf_and_len(buf1, v1_len), + ) + })) } } } diff --git a/crates/libafl_targets/src/cmps/observers/aflpp.rs b/crates/libafl_targets/src/cmps/observers/aflpp.rs index 46c3e5be99..56937f5239 100644 --- a/crates/libafl_targets/src/cmps/observers/aflpp.rs +++ b/crates/libafl_targets/src/cmps/observers/aflpp.rs @@ -229,8 +229,8 @@ pub fn add_to_aflpp_cmp_metadata( let mut last: Option = None; for j in 0..execs { if let Some(val) = cmp_map.values_of(i, j) { - if let Some(l) = last.and_then(|x| x.to_u64_tuple()) - && let Some(v) = val.to_u64_tuple() + if let Some(l) = last.and_then(|x| x.to_u128_tuple()) + && let Some(v) = val.to_u128_tuple() { if l.0.wrapping_add(1) == v.0 { increasing_v0 += 1;