Skip to content
This repository has been archived by the owner on Aug 2, 2023. It is now read-only.

Derives

Lander Brandt edited this page Aug 15, 2019 · 10 revisions

The following is a full list of all proc macro derives available for usage on enums and structs:

The ToPrimitive*s generate an implementation of ToPrimitive with a simple deref of the struct casted as the appropriate data type. Ensure that your enums have #[derive(Copy, Clone)] for this one.

Attributes

All fuzzer attributes will be meta items of the lain attribute. For example: #[lain(ignore) where ignore is considered a meta item.

General field attributes

  • bits = N signals that the given field is a bitfield with the corresponding number of bits. When serializing data, bitfields of the field's type will all be combined into a single field of the appropriate size. e.g. two u8 fields with bits = 4 will be combined into a single 8-bit field during serialization. When generating new structs the field's bounds will also be taken into appropriate consideration.
  • ignore will use the default field initializer (Default::default()) and will not perform mutation on the field. It will however be serialized.
  • min = N and max = N specify the field's min/max bounds. When applied to an array this attribute will **forward** the min/max constraints to each of the element's new_fuzzedormutatemethods. This is useful if you have an array of numbers which all need to be within a certain bound. When applied to aVec`, the min/max will be used to determine the min/max length of the vec.
  • weight_to = "min"|"max" will skew the RNG towards either the min or max bound.
  • initializer = "method()" | 0x00 | <other_valid_rust_tokens_as_string> will use a static initializer for initializing the field.
  • ignore_chance = N will give the field N% chance of being ignored. If it's ignored the Default::default() method is used, otherwise it's mutated the same as any other field.

Enum-specific attributes

weight = N will weight the given enum variant to the given value. When generating a variant, the algorithm is very similar to the example given in rand::distributions::weighted::WeightedIndex, and will perform similar logic.

Example:

enum Foo {
   #[lain(weight = 2)]
   A, // 50% chance of picking A
   B, // implied weight of 1, 25% chance for B
   C, // implied weight of 1, 25% chance for C
}

Object-specific attributes

Currently the only object-specific attribute is #[lain(serialized_size = N)]. When applied to a struct or enum this will override the standard SerializedSize derive with a constant value, N. If an object (or enum variant) is less than this size, the remaining space is filled with null bytes.

Example:

#[lain(serialized_size = 4)]
enum Foo {
    A(u8), // 3 null bytes will be added to the end of this data structure when serializing this variant
    B(u8),
}

NewFuzzed

Implements the NewFuzzed trait for the given struct or enum. For enums this will choose a random variant and initialize it (if it's a non-unit enum type).

For structs, the initialization is somewhat more complicated. To avoid having to implement Default for all objects, a MaybeUninit<T> is created. Next, if the object or any of its children contains variable-size objects (this is where the VariableSizeObject derive/trait come in handy), we will initialize each field in a random order. This helps when a max_size constraint is provided to try getting better distribution of data.

The following is an example of initialization for a single field

let constraints = if let Some(ref max_size) = max_size {
    let mut constraints = ::lain::types::Constraints::default();
    constraints.max_size = Some(max_size.clone());
    Some(constraints)
} else {
    None
};
let value = <u64>::new_fuzzed(mutator, constraints.as_ref());
if let Some(ref mut max_size) = max_size {
    *max_size -= value.serialized_size();
}
let field_offset = unsafe {
    ::field_offset::FieldOffset::<PacketData, _>::new(|x| {
        let PacketData { ref offset, .. } = *x;
        offset
    })
}
.get_byte_offset() as isize;
unsafe {
    let field_ptr =
        (uninit_struct_ptr as *mut u8).offset(field_offset) as *mut u64;
    std::ptr::write(field_ptr, value);
}

In this block we set up the constraints (if applicable) for this individual field, get a newly fuzzed instance of it, and write it to the appropriate offset in the data structure.

After each field is initialized, we perform fixups if appropriate:

if mutator.should_fixup() {
    initialized_struct.fixup(mutator);
}

This has a slight chance of ignoring the fixup procedure depending on the mutator mode.

This macro will emit code similar to the following

impl ::lain::traits::NewFuzzed for PacketData {
    type RangeType = u8;
    fn new_fuzzed<R: ::lain::rand::Rng>(
        mutator: &mut ::lain::mutator::Mutator<R>,
        mut constraints: Option<&::lain::types::Constraints<Self::RangeType>>,
    ) -> PacketData {
        use ::lain::rand::seq::index::sample;
        use std::any::Any;
        let mut max_size = if let Some(ref mut constraints) = constraints {
            constraints.max_size.clone()
        } else {
            None
        };
        let mut uninit_struct = std::mem::MaybeUninit::<PacketData>::uninit();
        let uninit_struct_ptr = uninit_struct.as_mut_ptr();
        let range = if Self::is_variable_size() {
            for i in sample(&mut mutator.rng, 4usize, 4usize).iter() {
                match i {
                    0usize => {
                        let constraints = if let Some(ref max_size) = max_size {
                            let mut constraints = ::lain::types::Constraints::default();
                            constraints.max_size = Some(max_size.clone());
                            Some(constraints)
                        } else {
                            None
                        };
                        let value = <UnsafeEnum<PacketType, u32>>::new_fuzzed(
                            mutator,
                            constraints.as_ref(),
                        );
                        if let Some(ref mut max_size) = max_size {
                            *max_size -= value.serialized_size();
                        }
                        let field_offset = unsafe {
                            ::field_offset::FieldOffset::<PacketData, _>::new(|x| {
                                let PacketData { ref typ, .. } = *x;
                                typ
                            })
                        }
                        .get_byte_offset() as isize;
                        unsafe {
                            let field_ptr = (uninit_struct_ptr as *mut u8).offset(field_offset)
                                as *mut UnsafeEnum<PacketType, u32>;
                            std::ptr::write(field_ptr, value);
                        }
                    }
                    1usize => {
                        let constraints = if let Some(ref max_size) = max_size {
                            let mut constraints = ::lain::types::Constraints::default();
                            constraints.max_size = Some(max_size.clone());
                            Some(constraints)
                        } else {
                            None
                        };
                        let value = <u64>::new_fuzzed(mutator, constraints.as_ref());
                        if let Some(ref mut max_size) = max_size {
                            *max_size -= value.serialized_size();
                        }
                        let field_offset = unsafe {
                            ::field_offset::FieldOffset::<PacketData, _>::new(|x| {
                                let PacketData { ref offset, .. } = *x;
                                offset
                            })
                        }
                        .get_byte_offset() as isize;
                        unsafe {
                            let field_ptr =
                                (uninit_struct_ptr as *mut u8).offset(field_offset) as *mut u64;
                            std::ptr::write(field_ptr, value);
                        }
                    }
                    2usize => {
                        let constraints = if let Some(ref max_size) = max_size {
                            let mut constraints = ::lain::types::Constraints::default();
                            constraints.max_size = Some(max_size.clone());
                            Some(constraints)
                        } else {
                            None
                        };
                        let value = <u64>::new_fuzzed(mutator, constraints.as_ref());
                        if let Some(ref mut max_size) = max_size {
                            *max_size -= value.serialized_size();
                        }
                        let field_offset = unsafe {
                            ::field_offset::FieldOffset::<PacketData, _>::new(|x| {
                                let PacketData { ref length, .. } = *x;
                                length
                            })
                        }
                        .get_byte_offset() as isize;
                        unsafe {
                            let field_ptr =
                                (uninit_struct_ptr as *mut u8).offset(field_offset) as *mut u64;
                            std::ptr::write(field_ptr, value);
                        }
                    }
                    3usize => {
                        let constraints: Option<
                            ::lain::types::Constraints<
                                <Vec<u8> as ::lain::traits::NewFuzzed>::RangeType,
                            >,
                        > = Some(Constraints {
                            min: Some(0),
                            max: Some(10),
                            weighted: ::lain::types::Weighted::None,
                            max_size: max_size.clone(),
                        });
                        let value = <Vec<u8>>::new_fuzzed(mutator, constraints.as_ref());
                        if let Some(ref mut max_size) = max_size {
                            *max_size -= value.serialized_size();
                        }
                        let field_offset = unsafe {
                            ::field_offset::FieldOffset::<PacketData, _>::new(|x| {
                                let PacketData { ref data, .. } = *x;
                                data
                            })
                        }
                        .get_byte_offset() as isize;
                        unsafe {
                            let field_ptr =
                                (uninit_struct_ptr as *mut u8).offset(field_offset) as *mut Vec<u8>;
                            std::ptr::write(field_ptr, value);
                        }
                    }
                    _ => ::std::rt::begin_panic(
                        "internal error: entered unreachable code",
                        &("example_fuzzer/src/main.rs", 31u32, 69u32),
                    ),
                }
            }
        } else {
            let constraints = if let Some(ref max_size) = max_size {
                let mut constraints = ::lain::types::Constraints::default();
                constraints.max_size = Some(max_size.clone());
                Some(constraints)
            } else {
                None
            };
            let value = <UnsafeEnum<PacketType, u32>>::new_fuzzed(mutator, constraints.as_ref());
            if let Some(ref mut max_size) = max_size {
                *max_size -= value.serialized_size();
            }
            let field_offset = unsafe {
                ::field_offset::FieldOffset::<PacketData, _>::new(|x| {
                    let PacketData { ref typ, .. } = *x;
                    typ
                })
            }
            .get_byte_offset() as isize;
            unsafe {
                let field_ptr = (uninit_struct_ptr as *mut u8).offset(field_offset)
                    as *mut UnsafeEnum<PacketType, u32>;
                std::ptr::write(field_ptr, value);
            }
            let constraints = if let Some(ref max_size) = max_size {
                let mut constraints = ::lain::types::Constraints::default();
                constraints.max_size = Some(max_size.clone());
                Some(constraints)
            } else {
                None
            };
            let value = <u64>::new_fuzzed(mutator, constraints.as_ref());
            if let Some(ref mut max_size) = max_size {
                *max_size -= value.serialized_size();
            }
            let field_offset = unsafe {
                ::field_offset::FieldOffset::<PacketData, _>::new(|x| {
                    let PacketData { ref offset, .. } = *x;
                    offset
                })
            }
            .get_byte_offset() as isize;
            unsafe {
                let field_ptr = (uninit_struct_ptr as *mut u8).offset(field_offset) as *mut u64;
                std::ptr::write(field_ptr, value);
            }
            let constraints = if let Some(ref max_size) = max_size {
                let mut constraints = ::lain::types::Constraints::default();
                constraints.max_size = Some(max_size.clone());
                Some(constraints)
            } else {
                None
            };
            let value = <u64>::new_fuzzed(mutator, constraints.as_ref());
            if let Some(ref mut max_size) = max_size {
                *max_size -= value.serialized_size();
            }
            let field_offset = unsafe {
                ::field_offset::FieldOffset::<PacketData, _>::new(|x| {
                    let PacketData { ref length, .. } = *x;
                    length
                })
            }
            .get_byte_offset() as isize;
            unsafe {
                let field_ptr = (uninit_struct_ptr as *mut u8).offset(field_offset) as *mut u64;
                std::ptr::write(field_ptr, value);
            }
            let constraints: Option<
                ::lain::types::Constraints<<Vec<u8> as ::lain::traits::NewFuzzed>::RangeType>,
            > = Some(Constraints {
                min: Some(0),
                max: Some(10),
                weighted: ::lain::types::Weighted::None,
                max_size: max_size.clone(),
            });
            let value = <Vec<u8>>::new_fuzzed(mutator, constraints.as_ref());
            if let Some(ref mut max_size) = max_size {
                *max_size -= value.serialized_size();
            }
            let field_offset = unsafe {
                ::field_offset::FieldOffset::<PacketData, _>::new(|x| {
                    let PacketData { ref data, .. } = *x;
                    data
                })
            }
            .get_byte_offset() as isize;
            unsafe {
                let field_ptr = (uninit_struct_ptr as *mut u8).offset(field_offset) as *mut Vec<u8>;
                std::ptr::write(field_ptr, value);
            }
        };
        let mut initialized_struct = unsafe { uninit_struct.assume_init() };
        if mutator.should_fixup() {
            initialized_struct.fixup(mutator);
        }
        initialized_struct
    }
}

Mutatable

This generates an implementation of Mutatable for the given struct/enum and performs in-place mutations of the the object's fields.

The following is an example of code generated from this macro:

impl ::lain::traits::Mutatable for PacketData {
    #[allow(unused)]
    fn mutate<R: ::lain::rand::Rng>(
        &mut self,
        mutator: &mut ::lain::mutator::Mutator<R>,
        constraints: Option<&Constraints<u8>>,
    ) {
        <UnsafeEnum<PacketType, u32>>::mutate(&mut self.typ, mutator, constraints);
        if mutator.should_early_bail_mutation() {
            if mutator.should_fixup() {
                <UnsafeEnum<PacketType, u32>>::fixup(&mut self.typ, mutator);
            }
            return;
        }
        <u64>::mutate(&mut self.offset, mutator, constraints);
        if mutator.should_early_bail_mutation() {
            if mutator.should_fixup() {
                <u64>::fixup(&mut self.offset, mutator);
            }
            return;
        }
        <u64>::mutate(&mut self.length, mutator, constraints);
        if mutator.should_early_bail_mutation() {
            if mutator.should_fixup() {
                <u64>::fixup(&mut self.length, mutator);
            }
            return;
        }
        <Vec<u8>>::mutate(&mut self.data, mutator, constraints);
        if mutator.should_early_bail_mutation() {
            if mutator.should_fixup() {
                <Vec<u8>>::fixup(&mut self.data, mutator);
            }
            return;
        }
        if mutator.should_fixup() {
            self.fixup(mutator);
        }
    }
}

Note the consistent checks for should_early_bail_mutation. In some mutator modes it may be useful to only perform fuzzing of a single field. This allows us to perform bailing out early if we've mutated the desired field already.

BinarySerialize

Generates an implementation of BinarySerialize that will serialize the given object to a slice, as well as generating an implementation of SerializedSize. The coupling is strange, but there was never a scenario encountered where you would want one and not the either, so we opted to couple them together.

The following is an example of code generated from this macro:

impl ::lain::traits::BinarySerialize for PacketData {
    fn binary_serialize<W: std::io::Write, E: ::lain::byteorder::ByteOrder>(&self, buffer: &mut W) {
        use ::lain::byteorder::{BigEndian, LittleEndian, WriteBytesExt};
        use ::lain::traits::SerializedSize;
        let mut bitfield: u64 = 0;
        self.typ.binary_serialize::<_, E>(buffer);
        self.offset.binary_serialize::<_, E>(buffer);
        self.length.binary_serialize::<_, E>(buffer);
        self.data.binary_serialize::<_, E>(buffer);
    }
}

For bitfields, we would use the bitfield variable and push it once the corresponding bitfield has been properly set.

VariableSizeObject

Specializes the implementation of VariableSizeObject for enums/structs. This is useful for the NewFuzzed implementation.

impl ::lain::traits::VariableSizeObject for PacketData {
    fn is_variable_size() -> bool {
        false
            || <UnsafeEnum<PacketType, u32>>::is_variable_size()
            || <u64>::is_variable_size()
            || <u64>::is_variable_size()
            || <Vec<u8>>::is_variable_size()
    }
}