gofuzzing

How to set constraint on input for fuzzing?


Assume I have the following structure

type Hdr struct{
  Src      uint16
  Dst      uint16
  Priotity byte
  Pktcnt   byte
  Opcode   byte
  Ver      byte
}

I have two functions Marshal and Unmarshal that encode Hdr to and from a binary format of:

 0                   1          
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|              Src              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|              Dst              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Prio |  Cnt  | Opcode|  Ver  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

I'd like to use Go Fuzz to make random, valid Hdr instances, Marshal then to binary, Unmarshal the binary and make sure the output matches the original input.

The main issue I am having is that I cannot figure out how to tell Go Fuzz that fields like Priotity cannot be greater than 15 otherwise they will get truncated when they are marshalled (only 4 bits). How do I set this constraint?

Update

This is just a toy case. There are many times with protocols like the above where something like the opcode would trigger secondary more complex parsing/vetting. Fuzzing could still find very useful issues within a constraint (IE: if Prio 0x00 and Cnt 0x2F secondary parser will error because delimiter is \ ).


Solution

  • EDIT

    I'm not sure Fuzzing is a good fit here. Fuzzing is designed to find unexpected inputs: multi-byte UTF8 inputs (valid and non-valid); negative values; huge values, long lengths etc. These will try to catch "edge" cases.

    In your case here, you know the:

    so vanilla testing.T tests may be a better fit here.


    Keep it simple.

    If you don't want to "waste" a Fuzz input & you know the input constraints of your code, you can try something like this:

    func coerce(h *Hdr) (skip bool) {
    
        h.Priotity &= 0x0f // ensure priority is 0-15
        h.OpCode %= 20     // ensure opcode is 0-19 
    
        return false       // optionally skip this test
    }
    

    and in your test - the coerced value can be tested - or skipped (as @jch showed):

    import "github.com/google/go-cmp/cmp"
    
    f.Fuzz(func(t *testing.T, src, dst uint16, pri, count, op, ver byte) {
        h := Hdr{src, dst, pri, count, op, ver}
    
        if coerce(&h) {
            t.Skip()
            return
        }
    
        bs, err := Marshal(h)     // check err
    
        h2, err := Unmarhsal(bs)  // check err
    
        if !cmp.Equal(h, h2) {
            t.Errorf("Marshal/Unmarshal validation failed for: %+v", h)
        }
    }