如何在模糊测试中对输入设置约束?

huangapple go评论68阅读模式
英文:

How to set constraint on input for fuzzing?

问题

假设我有以下结构体:

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

我有两个函数MarshalUnmarshal,用于将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:

  • Unmarshal input 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)
	}
}

huangapple
  • 本文由 发表于 2022年4月1日 03:46:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/71698351.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定