英文:
How to set constraint on input for fuzzing?
问题
假设我有以下结构体:
type Hdr struct{
  Src      uint16
  Dst      uint16
  Priotity byte
  Pktcnt   byte
  Opcode   byte
  Ver      byte
}
我有两个函数Marshal和Unmarshal,用于将Hdr编码为二进制格式,并从二进制格式解码为Hdr:
 0                   1          
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|              Src              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|              Dst              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Prio |  Cnt  | Opcode|  Ver  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
我想使用Go Fuzz生成随机的有效Hdr实例,将其编码为二进制格式,解码二进制格式,并确保输出与原始输入匹配。
我遇到的主要问题是我无法弄清楚如何告诉Go Fuzz,像Priotity这样的字段不能大于15,否则在编码时会被截断(只有4位)。我该如何设置这个约束?
更新
这只是一个玩具案例。在像上面的协议中,有很多情况下,类似opcode的东西会触发次要的更复杂的解析/验证。在约束条件下,模糊测试仍然可以找到非常有用的问题(例如:如果Prio为0x00,Cnt为0x2F,次要解析器将因为分隔符为\而出错)。
英文:
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 \ ).
答案1
得分: 3
为了跳过不感兴趣的结果,在你的模糊测试函数中调用t.Skip。类似这样:
f.Fuzz(func(t *testing.T, b []byte) {
    a, err := Unmarshal(b)
    if err != nil {
        t.Skip()
        return
    }
    c, err := Marshal(a)
    if err != nil || !bytes.Equal(b, c) {
        t.Errorf("糟糕!")
    }
})
英文:
In order to skip uninteresting results, call t.Skip in your fuzzing function.  Something like this:
f.Fuzz(func(t *testing.T, b []byte) {
    a, err := Unmarshal(b)
    if err != nil {
        t.Skip()
        return
    }
    c, err := Marshal(a)
    if err != nil || !bytes.Equal(b, c) {
        t.Errorf("Eek!")
    }
})
答案2
得分: 3
EDIT
我不确定模糊测试是否适合这里。模糊测试旨在查找意外的输入:多字节的UTF8输入(有效和无效);负值;巨大的值,长长度等。这些将尝试捕捉“边缘”情况。
在这种情况下,您知道:
Unmarshal输入负载必须是6个字节(否则应该出错)- 您精确地知道您的内部“边缘”
 
因此,普通的 testing.T 测试可能更适合这里。
保持简单。
如果您不想“浪费”一个模糊输入,并且您知道代码的输入约束,您可以尝试像这样的方法:
func coerce(h *Hdr) (skip bool) {
    h.Priotity &= 0x0f // 确保优先级在0-15之间
    h.OpCode %= 20    // 确保操作码在0-19之间
    return false      // 可选择跳过此测试
}
在您的测试中,可以测试或跳过强制值(如 @jch 所示):
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)    // 检查错误
    h2, err := Unmarhsal(bs) // 检查错误
    if !cmp.Equal(h, h2) {
        t.Errorf("Marshal/Unmarshal validation failed for: %+v", h)
    }
})
英文:
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:
Unmarshalinput payload must be 6 bytes (should error otherwise)- you know precisely your internal "edges"
 
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)
	}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论