mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
150 lines
4.6 KiB
Go
150 lines
4.6 KiB
Go
package api
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"encoding/binary"
|
||
|
|
"image"
|
||
|
|
"image/color"
|
||
|
|
"image/gif"
|
||
|
|
"image/jpeg"
|
||
|
|
"image/png"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// fuzzWebPVP8L builds a minimal RIFF/WEBP/VP8L container of the given size.
|
||
|
|
func fuzzWebPVP8L(w, h int) []byte {
|
||
|
|
buf := make([]byte, 30)
|
||
|
|
copy(buf[0:4], "RIFF")
|
||
|
|
binary.LittleEndian.PutUint32(buf[4:8], uint32(len(buf)-8))
|
||
|
|
copy(buf[8:12], "WEBP")
|
||
|
|
copy(buf[12:16], "VP8L")
|
||
|
|
binary.LittleEndian.PutUint32(buf[16:20], uint32(len(buf)-20))
|
||
|
|
buf[20] = 0x2F
|
||
|
|
bits := uint32(w-1) | uint32(h-1)<<14
|
||
|
|
binary.LittleEndian.PutUint32(buf[21:25], bits)
|
||
|
|
return buf
|
||
|
|
}
|
||
|
|
|
||
|
|
// fuzzWebPVP8 builds a minimal RIFF/WEBP/VP8 (lossy) container.
|
||
|
|
func fuzzWebPVP8(w, h int) []byte {
|
||
|
|
buf := make([]byte, 30)
|
||
|
|
copy(buf[0:4], "RIFF")
|
||
|
|
copy(buf[8:12], "WEBP")
|
||
|
|
copy(buf[12:16], "VP8 ")
|
||
|
|
buf[23], buf[24], buf[25] = 0x9d, 0x01, 0x2a
|
||
|
|
binary.LittleEndian.PutUint16(buf[26:28], uint16(w)&0x3FFF)
|
||
|
|
binary.LittleEndian.PutUint16(buf[28:30], uint16(h)&0x3FFF)
|
||
|
|
return buf
|
||
|
|
}
|
||
|
|
|
||
|
|
// fuzzWebPVP8X builds a minimal RIFF/WEBP/VP8X (extended) container.
|
||
|
|
func fuzzWebPVP8X(w, h int) []byte {
|
||
|
|
buf := make([]byte, 30)
|
||
|
|
copy(buf[0:4], "RIFF")
|
||
|
|
copy(buf[8:12], "WEBP")
|
||
|
|
copy(buf[12:16], "VP8X")
|
||
|
|
w1, h1 := uint32(w-1), uint32(h-1)
|
||
|
|
buf[24], buf[25], buf[26] = byte(w1), byte(w1>>8), byte(w1>>16)
|
||
|
|
buf[27], buf[28], buf[29] = byte(h1), byte(h1>>8), byte(h1>>16)
|
||
|
|
return buf
|
||
|
|
}
|
||
|
|
|
||
|
|
func fuzzPNGBytes(w, h int) []byte {
|
||
|
|
img := image.NewRGBA(image.Rect(0, 0, w, h))
|
||
|
|
img.Set(0, 0, color.RGBA{R: 255, A: 255})
|
||
|
|
var buf bytes.Buffer
|
||
|
|
_ = png.Encode(&buf, img)
|
||
|
|
return buf.Bytes()
|
||
|
|
}
|
||
|
|
|
||
|
|
func fuzzGIFBytes(w, h int) []byte {
|
||
|
|
img := image.NewPaletted(image.Rect(0, 0, w, h), color.Palette{color.Black, color.White})
|
||
|
|
var buf bytes.Buffer
|
||
|
|
_ = gif.Encode(&buf, img, nil)
|
||
|
|
return buf.Bytes()
|
||
|
|
}
|
||
|
|
|
||
|
|
func fuzzJPEGBytes(w, h int) []byte {
|
||
|
|
img := image.NewRGBA(image.Rect(0, 0, w, h))
|
||
|
|
var buf bytes.Buffer
|
||
|
|
_ = jpeg.Encode(&buf, img, nil)
|
||
|
|
return buf.Bytes()
|
||
|
|
}
|
||
|
|
|
||
|
|
// FuzzImageDimensions is the prime crash target: hand-rolled and stdlib
|
||
|
|
// header parsing over completely untrusted bytes, exactly what a malicious
|
||
|
|
// emoji/avatar upload delivers. Every corpus entry is run through all four
|
||
|
|
// supported mime types (not just the one it happens to be valid for) so a
|
||
|
|
// truncated PNG is also thrown at the JPEG/GIF/WebP paths and vice versa.
|
||
|
|
//
|
||
|
|
// The only allowed outcomes are: an error, or a (width, height) that is
|
||
|
|
// strictly positive. Anything else -- a panic, a hang, or a non-positive
|
||
|
|
// dimension slipping past as "success" -- is a bug: the caller in
|
||
|
|
// emoji_handler.go trusts a non-error result enough to compare it against
|
||
|
|
// maxEmojiDimension without re-validating its sign.
|
||
|
|
func FuzzImageDimensions(f *testing.F) {
|
||
|
|
seeds := [][]byte{
|
||
|
|
nil,
|
||
|
|
{},
|
||
|
|
{0},
|
||
|
|
{0, 0, 0, 0},
|
||
|
|
[]byte("RIFF"),
|
||
|
|
[]byte("RIFFxxxxWEBP"),
|
||
|
|
[]byte("RIFFxxxxWEBPVP8 "),
|
||
|
|
[]byte("RIFFxxxxWEBPVP8L"),
|
||
|
|
[]byte("RIFFxxxxWEBPVP8X"),
|
||
|
|
[]byte("RIFFxxxxWEBPXXXX"),
|
||
|
|
fuzzPNGBytes(1, 1),
|
||
|
|
fuzzPNGBytes(128, 128),
|
||
|
|
fuzzGIFBytes(1, 1),
|
||
|
|
fuzzGIFBytes(128, 128),
|
||
|
|
fuzzJPEGBytes(1, 1),
|
||
|
|
fuzzJPEGBytes(128, 128),
|
||
|
|
fuzzWebPVP8(100, 80),
|
||
|
|
fuzzWebPVP8(16383, 16383), // max 14-bit dimension
|
||
|
|
fuzzWebPVP8L(100, 80),
|
||
|
|
fuzzWebPVP8L(16384, 16384), // max 14-bit+1 dimension
|
||
|
|
fuzzWebPVP8X(100, 80),
|
||
|
|
fuzzWebPVP8X(16777216, 16777216), // max 24-bit+1 dimension
|
||
|
|
}
|
||
|
|
// Truncate every seed at each prefix length up to 32 bytes (where every
|
||
|
|
// format's fixed header lives) and then more coarsely beyond that: the
|
||
|
|
// classic "header parser unchecked slice index" crasher lives in exactly
|
||
|
|
// these cuts, but walking every single offset of a 128x128 PNG bloats the
|
||
|
|
// corpus enough to stall the mutation phase for no extra coverage.
|
||
|
|
for _, s := range seeds {
|
||
|
|
f.Add(append([]byte(nil), s...))
|
||
|
|
for cut := 0; cut < len(s) && cut <= 32; cut++ {
|
||
|
|
f.Add(append([]byte(nil), s[:cut]...))
|
||
|
|
}
|
||
|
|
for cut := 40; cut < len(s); cut += 8 {
|
||
|
|
f.Add(append([]byte(nil), s[:cut]...))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
mimeTypes := []string{"image/png", "image/jpeg", "image/gif", "image/webp"}
|
||
|
|
|
||
|
|
f.Fuzz(func(t *testing.T, raw []byte) {
|
||
|
|
done := make(chan struct{})
|
||
|
|
go func() {
|
||
|
|
defer close(done)
|
||
|
|
for _, mt := range mimeTypes {
|
||
|
|
w, h, err := imageDimensions(raw, mt)
|
||
|
|
if err == nil && (w <= 0 || h <= 0) {
|
||
|
|
t.Errorf("imageDimensions(%d bytes, %s) returned non-positive size %dx%d with no error", len(raw), mt, w, h)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
w, h, err := webpDimensions(raw)
|
||
|
|
if err == nil && (w <= 0 || h <= 0) {
|
||
|
|
t.Errorf("webpDimensions(%d bytes) returned non-positive size %dx%d with no error", len(raw), w, h)
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
select {
|
||
|
|
case <-done:
|
||
|
|
case <-time.After(5 * time.Second):
|
||
|
|
t.Fatalf("imageDimensions/webpDimensions hung on %d-byte input: %x", len(raw), raw)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|