项目一览
zplgfa
这个项目是将 PNG、JPEG 和 GIF 编码的图形文件转换为 ZPL 兼容的 ^GF 元素,然后作者给出了实例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package main import ( "simonwaldherr.de/go/zplgfa" "fmt" "image" _ "image/gif" _ "image/jpeg" _ "image/png" "log" "os" ) func main() { // open file file, err := os.Open("label.png") if err != nil { log.Printf("Warning: could not open the file: %s\n", err) return } defer file.Close() // load image head information config, format, err := image.DecodeConfig(file) if err != nil { log.Printf("Warning: image not compatible, format: %s, config: %v, error: %s\n", format, config, err) } // reset file pointer to the beginning of the file file.Seek(0, 0) // load and decode image img, _, err := image.Decode(file) if err != nil { log.Printf("Warning: could not decode the file, %s\n", err) return } // flatten image flat := zplgfa.FlattenImage(img) // convert image to zpl compatible type gfimg := zplgfa.ConvertToZPL(flat, zplgfa.CompressedASCII) // output zpl with graphic field data to stdout fmt.Println(gfimg) }
整体跑一遍基本就能明白他在干什么。把代码改成适合 fuzz 的格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package test import ( "bytes" "fmt" "image/gif" "testing" "simonwaldherr.de/go/zplgfa" ) func FuzzReverse(f *testing.F) { f.Fuzz(func(a *testing.T, data []byte) { //接收参数 // load and decode image img, err := gif.Decode(bytes.NewReader(data)) if err != nil { return } // flatten image flat := zplgfa.FlattenImage(img) // wid := flat.Bounds().Size().X / 8 // panic(wid) // convert image to zpl compatible type gfimg := zplgfa.ConvertToZPL(flat, zplgfa.CompressedASCII) // output zpl with graphic field data to stdout fmt.Println(gfimg) }) }
跑一遍就有 bug 了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 ❯ go test -fuzz=Fuzz fuzz: elapsed: 2s, gathering baseline coverage: 0/1541 completed fuzz: minimizing 220-byte failing input file fuzz: elapsed: 2s, gathering baseline coverage: 434/1541 completed --- FAIL: FuzzReverse (1.93s) --- FAIL: FuzzReverse (0.00s) testing.go:1485: panic: runtime error: index out of range [0] with length 0 goroutine 1254 [running]: runtime/debug.Stack() /usr/local/go/src/runtime/debug/stack.go:24 +0xbc testing.tRunner.func1() /usr/local/go/src/testing/testing.go:1485 +0x264 panic({0x102b9e940, 0x14000016318}) /usr/local/go/src/runtime/panic.go:884 +0x204 simonwaldherr.de/go/zplgfa.ConvertToGraphicField({0x102bb0de8, 0x140000a8a00}, 0x2) /Users/*/go/pkg/mod/simonwaldherr.de/go/zplgfa@v1.1.1/zplgfa.go:160 +0xb44 simonwaldherr.de/go/zplgfa.ConvertToZPL({0x102bb0de8?, 0x140000a8a00?}, 0x140000d9748?) /Users/*/go/pkg/mod/simonwaldherr.de/go/zplgfa@v1.1.1/zplgfa.go:31 +0x50 test.FuzzReverse.func1(0x140000d9718?, {0x14000026700, 0x1f, 0x80}) /Users/*/Desktop/src/cve/m_test.go:27 +0x130 reflect.Value.call({0x102b7c560?, 0x102baeca0?, 0x14000544e38?}, {0x102b27556, 0x4}, {0x1400009b8f0, 0x2, 0x0?}) /usr/local/go/src/reflect/value.go:586 +0x87c reflect.Value.Call({0x102b7c560?, 0x102baeca0?, 0x0?}, {0x1400009b8f0?, 0x102bade20?, 0x102c781f8?}) /usr/local/go/src/reflect/value.go:370 +0x90 testing.(*F).Fuzz.func1.1(0x0?) /usr/local/go/src/testing/fuzz.go:335 +0x360 testing.tRunner(0x1400010b040, 0x1400007ad80) /usr/local/go/src/testing/testing.go:1576 +0x10c created by testing.(*F).Fuzz.func1 /usr/local/go/src/testing/fuzz.go:322 +0x4c4 Failing input written to testdata/fuzz/FuzzReverse/3e80f54c43de28a6 To re-run: go test -run=FuzzReverse/3e80f54c43de28a6 FAIL exit status 1 FAIL test 2.559s
进代码里看看:发现在 ConvertToGraphicField
函数中,使用 image
库的 Bounds().Size()
获取了图片的长宽信息,但是事实上,这里是可以伪造的
宽度可以恶意构造为 0,之后顺着代码往下读,发现在下边的 for 循环中,line 是根据宽度 make 出来的数组,而之后求 currentByte 时候更是直接求了索引为 0 的 byte,这显然是个索引越界
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func ConvertToGraphicField(source image.Image, graphicType GraphicType) string { var gfType string var lastLine string size := source.Bounds().Size() width := size.X / 8 height := size.Y if size.Y%8 != 0 { width = width + 1 } var GraphicFieldData string for y := 0; y < size.Y; y++ { line := make([]uint8, width) lineIndex := 0 index := uint8(0) currentByte := line[lineIndex]//line[0]!
修复 我的修复思路就是越早处理越好,顺着调用链找,看看哪里返回是最合适的:其实这里就可以直接返回了(我认为
1 2 3 4 5 6 7 func ConvertToZPL(img image.Image, graphicType GraphicType) string { wid := img.Bounds().Size().X / 8 if wid == 0 { return "" } return fmt.Sprintf("^XA,^FS\n^FO0,0\n%s^FS,^XZ\n", ConvertToGraphicField(img, graphicType)) }
因为宽度为零的话事实上已经可以直接把空字符串返回了,之所以没有返回一个 err 是因为这个师傅写的代码,整体都没用到 err,变参数对整体影响太大了,而且返回""
也是合理的,因为图片本身宽度就是 0,0 确实可以代表没有图片信息。
师傅果断进行了合并
https://github.com/SimonWaldherr/zplgfa/pull/6