build(deps): bump github.com/kovidgoyal/imaging from 1.8.21 to 1.8.22

Bumps [github.com/kovidgoyal/imaging](https://github.com/kovidgoyal/imaging) from 1.8.21 to 1.8.22.
- [Release notes](https://github.com/kovidgoyal/imaging/releases)
- [Commits](https://github.com/kovidgoyal/imaging/compare/v1.8.21...v1.8.22)

---
updated-dependencies:
- dependency-name: github.com/kovidgoyal/imaging
  dependency-version: 1.8.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2026-07-02 14:43:43 +00:00
committed by Ralf Haferkamp
parent 08bb6fe003
commit 2683b2db6f
13 changed files with 257 additions and 332 deletions

4
go.mod
View File

@@ -49,7 +49,7 @@ require (
github.com/jellydator/ttlcache/v3 v3.4.1
github.com/jinzhu/now v1.1.5
github.com/justinas/alice v1.2.0
github.com/kovidgoyal/imaging v1.8.21
github.com/kovidgoyal/imaging v1.8.22
github.com/leonelquinteros/gotext v1.7.3-0.20260422134830-b012b4ccae69
github.com/libregraph/idm v0.5.0
github.com/libregraph/lico v0.67.0
@@ -104,7 +104,7 @@ require (
go.opentelemetry.io/otel/trace v1.44.0
golang.org/x/crypto v0.53.0
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
golang.org/x/image v0.40.0
golang.org/x/image v0.43.0
golang.org/x/net v0.56.0
golang.org/x/oauth2 v0.36.0
golang.org/x/sync v0.21.0

8
go.sum
View File

@@ -734,8 +734,8 @@ github.com/kovidgoyal/go-parallel v1.1.1 h1:1OzpNjtrUkBPq3UaqrnvOoB2F9RttSt811ui
github.com/kovidgoyal/go-parallel v1.1.1/go.mod h1:BJNIbe6+hxyFWv7n6oEDPj3PA5qSw5OCtf0hcVxWJiw=
github.com/kovidgoyal/go-shm v1.0.0 h1:HJEel9D1F9YhULvClEHJLawoRSj/1u/EDV7MJbBPgQo=
github.com/kovidgoyal/go-shm v1.0.0/go.mod h1:Yzb80Xf9L3kaoB2RGok9hHwMIt7Oif61kT6t3+VnZds=
github.com/kovidgoyal/imaging v1.8.21 h1:95S2+dowTeKJJHNpf6lnScvIennTr2H0zQotu+ptNQw=
github.com/kovidgoyal/imaging v1.8.21/go.mod h1:976F+zjiQeZ7sd87Pxlm0a64S/w9bImSIWg3sSk1rdQ=
github.com/kovidgoyal/imaging v1.8.22 h1:CtpoRXQpS79xxJsKu8+LUJJE/0i4FLquJZy0QH+QNlM=
github.com/kovidgoyal/imaging v1.8.22/go.mod h1:y8wo4JTv4D+skbtQf6fHg8nA1qtagvCcn8J2Nu5k2Jg=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -1376,8 +1376,8 @@ golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJk
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8=
golang.org/x/image v0.40.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
golang.org/x/image v0.43.0 h1:FLxcP4ec2350nTfOC8ysKtqYSIFbk/QGjw1ZHNP4tsY=
golang.org/x/image v0.43.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

View File

@@ -5,7 +5,7 @@ import os
import subprocess
VERSION = "1.8.21"
VERSION = "1.8.22"
def run(*args: str):

View File

@@ -92,6 +92,9 @@ func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) {
if err != nil {
return nil, image.Config{}, err
}
if seenVP8X && (fh.Width != int(widthMinusOne)+1 || fh.Height != int(heightMinusOne)+1) {
return nil, image.Config{}, errInvalidFormat
}
if configOnly {
return nil, image.Config{
ColorModel: color.YCbCrModel,
@@ -121,8 +124,16 @@ func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) {
return nil, c, err
}
m, err := vp8l.Decode(chunkData)
return m, image.Config{}, err
if err != nil {
return nil, image.Config{}, err
}
if seenVP8X {
bounds := m.Bounds()
if bounds.Dx() != int(widthMinusOne)+1 || bounds.Dy() != int(heightMinusOne)+1 {
return nil, image.Config{}, errInvalidFormat
}
}
return m, image.Config{}, nil
case fccVP8X:
if seenVP8X {
return nil, image.Config{}, errInvalidFormat

View File

@@ -1,277 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package webp
import (
"bytes"
"errors"
"image"
"image/color"
"io"
"golang.org/x/image/riff"
"golang.org/x/image/vp8"
"golang.org/x/image/vp8l"
)
var errInvalidFormat = errors.New("webp: invalid format")
var (
fccALPH = riff.FourCC{'A', 'L', 'P', 'H'}
fccVP8 = riff.FourCC{'V', 'P', '8', ' '}
fccVP8L = riff.FourCC{'V', 'P', '8', 'L'}
fccVP8X = riff.FourCC{'V', 'P', '8', 'X'}
fccWEBP = riff.FourCC{'W', 'E', 'B', 'P'}
)
func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) {
formType, riffReader, err := riff.NewReader(r)
if err != nil {
return nil, image.Config{}, err
}
if formType != fccWEBP {
return nil, image.Config{}, errInvalidFormat
}
var (
alpha []byte
alphaStride int
wantAlpha bool
seenVP8X bool
widthMinusOne uint32
heightMinusOne uint32
buf [10]byte
)
for {
chunkID, chunkLen, chunkData, err := riffReader.Next()
if err == io.EOF {
err = errInvalidFormat
}
if err != nil {
return nil, image.Config{}, err
}
switch chunkID {
case fccALPH:
if !wantAlpha {
return nil, image.Config{}, errInvalidFormat
}
wantAlpha = false
// Read the Pre-processing | Filter | Compression byte.
if _, err := io.ReadFull(chunkData, buf[:1]); err != nil {
if err == io.EOF {
err = errInvalidFormat
}
return nil, image.Config{}, err
}
alpha, alphaStride, err = readAlpha(chunkData, widthMinusOne, heightMinusOne, buf[0]&0x03)
if err != nil {
return nil, image.Config{}, err
}
unfilterAlpha(alpha, alphaStride, (buf[0]>>2)&0x03)
case fccVP8:
if wantAlpha || int32(chunkLen) < 0 {
return nil, image.Config{}, errInvalidFormat
}
d := vp8.NewDecoder()
d.Init(chunkData, int(chunkLen))
fh, err := d.DecodeFrameHeader()
if err != nil {
return nil, image.Config{}, err
}
if configOnly {
return nil, image.Config{
ColorModel: color.YCbCrModel,
Width: fh.Width,
Height: fh.Height,
}, nil
}
m, err := d.DecodeFrame()
if err != nil {
return nil, image.Config{}, err
}
if alpha != nil {
return &image.NYCbCrA{
YCbCr: *m,
A: alpha,
AStride: alphaStride,
}, image.Config{}, nil
}
return m, image.Config{}, nil
case fccVP8L:
if wantAlpha || alpha != nil {
return nil, image.Config{}, errInvalidFormat
}
if configOnly {
c, err := vp8l.DecodeConfig(chunkData)
return nil, c, err
}
m, err := vp8l.Decode(chunkData)
return m, image.Config{}, err
case fccVP8X:
if seenVP8X {
return nil, image.Config{}, errInvalidFormat
}
seenVP8X = true
if chunkLen != 10 {
return nil, image.Config{}, errInvalidFormat
}
if _, err := io.ReadFull(chunkData, buf[:10]); err != nil {
return nil, image.Config{}, err
}
const (
animationBit = 1 << 1
xmpMetadataBit = 1 << 2
exifMetadataBit = 1 << 3
alphaBit = 1 << 4
iccProfileBit = 1 << 5
)
wantAlpha = (buf[0] & alphaBit) != 0
widthMinusOne = uint32(buf[4]) | uint32(buf[5])<<8 | uint32(buf[6])<<16
heightMinusOne = uint32(buf[7]) | uint32(buf[8])<<8 | uint32(buf[9])<<16
if configOnly {
if wantAlpha {
return nil, image.Config{
ColorModel: color.NYCbCrAModel,
Width: int(widthMinusOne) + 1,
Height: int(heightMinusOne) + 1,
}, nil
}
return nil, image.Config{
ColorModel: color.YCbCrModel,
Width: int(widthMinusOne) + 1,
Height: int(heightMinusOne) + 1,
}, nil
}
}
}
}
func readAlpha(chunkData io.Reader, widthMinusOne, heightMinusOne uint32, compression byte) (
alpha []byte, alphaStride int, err error) {
switch compression {
case 0:
w := int(widthMinusOne) + 1
h := int(heightMinusOne) + 1
alpha = make([]byte, w*h)
if _, err := io.ReadFull(chunkData, alpha); err != nil {
return nil, 0, err
}
return alpha, w, nil
case 1:
// Read the VP8L-compressed alpha values. First, synthesize a 5-byte VP8L header:
// a 1-byte magic number, a 14-bit widthMinusOne, a 14-bit heightMinusOne,
// a 1-bit (ignored, zero) alphaIsUsed and a 3-bit (zero) version.
// TODO(nigeltao): be more efficient than decoding an *image.NRGBA just to
// extract the green values to a separately allocated []byte. Fixing this
// will require changes to the vp8l package's API.
if widthMinusOne > 0x3fff || heightMinusOne > 0x3fff {
return nil, 0, errors.New("webp: invalid format")
}
alphaImage, err := vp8l.Decode(io.MultiReader(
bytes.NewReader([]byte{
0x2f, // VP8L magic number.
uint8(widthMinusOne),
uint8(widthMinusOne>>8) | uint8(heightMinusOne<<6),
uint8(heightMinusOne >> 2),
uint8(heightMinusOne >> 10),
}),
chunkData,
))
if err != nil {
return nil, 0, err
}
// The green values of the inner NRGBA image are the alpha values of the
// outer NYCbCrA image.
pix := alphaImage.(*image.NRGBA).Pix
alpha = make([]byte, len(pix)/4)
for i := range alpha {
alpha[i] = pix[4*i+1]
}
return alpha, int(widthMinusOne) + 1, nil
}
return nil, 0, errInvalidFormat
}
func unfilterAlpha(alpha []byte, alphaStride int, filter byte) {
if len(alpha) == 0 || alphaStride == 0 {
return
}
switch filter {
case 1: // Horizontal filter.
for i := 1; i < alphaStride; i++ {
alpha[i] += alpha[i-1]
}
for i := alphaStride; i < len(alpha); i += alphaStride {
// The first column is equivalent to the vertical filter.
alpha[i] += alpha[i-alphaStride]
for j := 1; j < alphaStride; j++ {
alpha[i+j] += alpha[i+j-1]
}
}
case 2: // Vertical filter.
// The first row is equivalent to the horizontal filter.
for i := 1; i < alphaStride; i++ {
alpha[i] += alpha[i-1]
}
for i := alphaStride; i < len(alpha); i++ {
alpha[i] += alpha[i-alphaStride]
}
case 3: // Gradient filter.
// The first row is equivalent to the horizontal filter.
for i := 1; i < alphaStride; i++ {
alpha[i] += alpha[i-1]
}
for i := alphaStride; i < len(alpha); i += alphaStride {
// The first column is equivalent to the vertical filter.
alpha[i] += alpha[i-alphaStride]
// The interior is predicted on the three top/left pixels.
for j := 1; j < alphaStride; j++ {
c := int(alpha[i+j-alphaStride-1])
b := int(alpha[i+j-alphaStride])
a := int(alpha[i+j-1])
x := a + b - c
if x < 0 {
x = 0
} else if x > 255 {
x = 255
}
alpha[i+j] += uint8(x)
}
}
}
}
// Decode reads a WEBP image from r and returns it as an image.Image.
func Decode(r io.Reader) (image.Image, error) {
m, _, err := decode(r, false)
if err != nil {
return nil, err
}
return m, nil
}
// DecodeConfig returns the color model and dimensions of a WEBP image without
// decoding the entire image.
func DecodeConfig(r io.Reader) (image.Config, error) {
_, c, err := decode(r, true)
return c, err
}
func init() {
image.RegisterFormat("webp", "RIFF????WEBPVP8", Decode, DecodeConfig)
}

View File

@@ -12,12 +12,16 @@ import (
"image"
"image/color"
"io"
"golang.org/x/image/internal/safemath"
)
// ErrUnsupported means that the input BMP image uses a valid but unsupported
// feature.
var ErrUnsupported = errors.New("bmp: unsupported BMP image")
var errInvalidPaletteIndex = errors.New("bmp: invalid palette index")
func readUint16(b []byte) uint16 {
return uint16(b[0]) | uint16(b[1])<<8
}
@@ -29,7 +33,8 @@ func readUint32(b []byte) uint32 {
// decodePaletted reads a 1, 2, 4 or 8 bit-per-pixel BMP image from r.
// If topDown is false, the image rows will be read bottom-up.
func decodePaletted(r io.Reader, c image.Config, topDown bool, bpp int) (image.Image, error) {
paletted := image.NewPaletted(image.Rect(0, 0, c.Width, c.Height), c.ColorModel.(color.Palette))
palette := c.ColorModel.(color.Palette)
paletted := image.NewPaletted(image.Rect(0, 0, c.Width, c.Height), palette)
if c.Width == 0 || c.Height == 0 {
return paletted, nil
}
@@ -51,7 +56,11 @@ func decodePaletted(r io.Reader, c image.Config, topDown bool, bpp int) (image.I
byteIndex, bitIndex, mask := 0, 8, byte((1<<bpp)-1)
for pixIndex := 0; pixIndex < c.Width; pixIndex++ {
bitIndex -= bpp
p[pixIndex] = (b[byteIndex]) >> bitIndex & mask
paletteIndex := (b[byteIndex]) >> bitIndex & mask
if int(paletteIndex) >= len(palette) {
return nil, errInvalidPaletteIndex
}
p[pixIndex] = paletteIndex
if bitIndex == 0 {
byteIndex++
bitIndex = 8
@@ -183,6 +192,16 @@ func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown b
if width < 0 || height < 0 {
return image.Config{}, 0, false, false, ErrUnsupported
}
if (width == 0) != (height == 0) {
// We'll take 0x0, but Nx0 or 0xN is suspicious.
return image.Config{}, 0, false, false, ErrUnsupported
}
// Check that the image fits in memory.
// This conservatively assumes 4 bytes per pixel,
// rather than using the actual pixel size.
if _, ok := safemath.Mul3(width, height, 4); !ok {
return image.Config{}, 0, false, false, ErrUnsupported
}
// We only support 1 plane and 8, 24 or 32 bits per pixel and no
// compression.
planes, bpp, compression := readUint16(b[26:28]), readUint16(b[28:30]), readUint32(b[30:34])

View File

@@ -348,6 +348,9 @@ func makeCachedPairPosGlyph(cov indexLookupFunc, num int, buf []byte) kernFunc {
count := int(u16(glyphs[offset:]))
for i := 0; i < count; i++ {
if offset+2+i*4+4 > len(glyphs) {
return 0, errInvalidGPOSTable
}
secondGlyphIndex := GlyphIndex(int(u16(glyphs[offset+2+i*4:])))
if secondGlyphIndex == b {
return int16(u16(glyphs[offset+2+i*4+2:])), nil
@@ -372,6 +375,9 @@ func makeCachedPairPosClass(cov indexLookupFunc, num1, num2 int, cdef1, cdef2 cl
}
idxa := cdef1(a)
idxb := cdef2(b)
if idxa < 0 || idxa >= num1 || idxb < 0 || idxb >= num2 {
return 0, errInvalidGPOSTable
}
return int16(u16(glyphs[(idxb+idxa*num2)*2:])), nil
}
}

View File

@@ -0,0 +1,31 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package safemath contains overflow-safe math functions.
package safemath
import "math/bits"
// Mul3 returns (x * y * z), unless at least one argument is negative or if the
// computation overflows the int type.
func Mul3(x, y, z int) (_ int, ok bool) {
// The following is copied from image/geom.go in std,
// but returns an explicit ok/not-ok rather than using -1 to indicate an error.
if x < 0 || y < 0 || z < 0 {
return -1, false
}
hi, lo := bits.Mul64(uint64(x), uint64(y))
if hi != 0 {
return -1, false
}
hi, lo = bits.Mul64(lo, uint64(z))
if hi != 0 {
return -1, false
}
a := int(lo)
if a < 0 || uint64(a) != lo {
return -1, false
}
return a, true
}

View File

@@ -5,7 +5,9 @@
package tiff
import (
"fmt"
"io"
"math"
"slices"
)
@@ -35,22 +37,34 @@ func (b *buffer) fill(end int) error {
}
func (b *buffer) ReadAt(p []byte, off int64) (int, error) {
o := int(off)
end := o + len(p)
if int64(end) != off+int64(len(p)) {
if off < 0 {
// Impossible in correct usage, but check for safety.
return 0, fmt.Errorf("invalid ReadAt offset %v (bug)", off)
}
end64 := off + int64(len(p))
if end64 < off || end64 > math.MaxInt {
return 0, io.ErrUnexpectedEOF
}
end := int(end64)
err := b.fill(end)
end = min(end, len(b.buf))
return copy(p, b.buf[min(o, end):end]), err
return copy(p, b.buf[min(int(off), end):end]), err
}
// Slice returns a slice of the underlying buffer. The slice contains
// n bytes starting at offset off.
func (b *buffer) Slice(off, n int) ([]byte, error) {
func (b *buffer) Slice(off, n int64) ([]byte, error) {
if off < 0 || n < 0 {
// Impossible in correct usage, but check for safety.
return nil, fmt.Errorf("invalid negative input to Slice(%v, %v) (bug)", off, n)
}
end := off + n
if err := b.fill(end); err != nil {
if end < 0 || end > math.MaxInt {
// end is too large. Treat this as a read error.
return nil, io.ErrUnexpectedEOF
}
if err := b.fill(int(end)); err != nil {
return nil, err
}
return b.buf[off:end], nil

View File

@@ -15,11 +15,12 @@ type byteReader interface {
}
// unpackBits decodes the PackBits-compressed data in src and returns the
// uncompressed data.
// uncompressed data. The output size is limited to lim bytes to prevent
// decompression bombs.
//
// The PackBits compression format is described in section 9 (p. 42)
// of the TIFF spec.
func unpackBits(r io.Reader) ([]byte, error) {
func unpackBits(r io.Reader, lim int64) ([]byte, error) {
buf := make([]byte, 128)
dst := make([]byte, 0, 1024)
br, ok := r.(byteReader)
@@ -54,5 +55,8 @@ func unpackBits(r io.Reader) ([]byte, error) {
}
dst = append(dst, buf[:1-code]...)
}
if int64(len(dst)) > lim {
return nil, FormatError("PackBits: decompressed data too large")
}
}
}

View File

@@ -11,7 +11,6 @@ import (
"bytes"
"compress/zlib"
"encoding/binary"
"errors"
"fmt"
"image"
"image/color"
@@ -19,6 +18,7 @@ import (
"math"
"golang.org/x/image/ccitt"
"golang.org/x/image/internal/safemath"
"golang.org/x/image/tiff/lzw"
)
@@ -95,6 +95,7 @@ type decoder struct {
mode imageMode
bpp uint
features map[int][]uint
ifd map[int][ifdLen]byte
palette []color.Color
buf []byte
@@ -113,9 +114,23 @@ func (d *decoder) firstVal(tag int) uint {
return f[0]
}
// firstIntVal returns the first int of the features entry with the given tag,
// or 0 if the tag does not exist.
// If the tag overflows an int, it returns an error.
func (d *decoder) firstIntVal(tag int) (int, error) {
v := d.firstVal(tag)
if v > math.MaxInt {
return 0, FormatError("IFD value too large")
}
return int(v), nil
}
// ifdUint decodes the IFD entry in p, which must be of the Byte, Short
// or Long type, and returns the decoded uint values.
func (d *decoder) ifdUint(p []byte) (u []uint, err error) {
//
// maxCount limits the number of values.
// If the entry contains more than maxCount values, only the first maxCount are parsed.
func (d *decoder) ifdUint(p []byte, maxCount int) (u []uint, err error) {
var raw []byte
if len(p) < ifdLen {
return nil, FormatError("bad IFD entry")
@@ -130,9 +145,11 @@ func (d *decoder) ifdUint(p []byte) (u []uint, err error) {
if count > math.MaxInt32/lengths[datatype] {
return nil, FormatError("IFD data too large")
}
truncatedCount := min(int(count), maxCount)
if datalen := lengths[datatype] * count; datalen > 4 {
truncatedLen := uint64(lengths[datatype]) * uint64(truncatedCount)
// The IFD contains a pointer to the real value.
raw, err = safeReadAt(d.r, uint64(datalen), int64(d.byteOrder.Uint32(p[8:12])))
raw, err = safeReadAt(d.r, truncatedLen, int64(d.byteOrder.Uint32(p[8:12])))
} else {
raw = p[8 : 8+datalen]
}
@@ -140,18 +157,18 @@ func (d *decoder) ifdUint(p []byte) (u []uint, err error) {
return nil, err
}
u = make([]uint, count)
u = make([]uint, truncatedCount)
switch datatype {
case dtByte:
for i := uint32(0); i < count; i++ {
for i := range u {
u[i] = uint(raw[i])
}
case dtShort:
for i := uint32(0); i < count; i++ {
for i := range u {
u[i] = uint(d.byteOrder.Uint16(raw[2*i : 2*(i+1)]))
}
case dtLong:
for i := uint32(0); i < count; i++ {
for i := range u {
u[i] = uint(d.byteOrder.Uint32(raw[4*i : 4*(i+1)]))
}
default:
@@ -160,10 +177,30 @@ func (d *decoder) ifdUint(p []byte) (u []uint, err error) {
return u, nil
}
// parseIFDOffsets parses an IFD entry stored in d.ifd using ifdUint.
// For IFD entries which can be large, we delay reading and parsing the entry
// until we know the image size, which lets us set reasonable bounds on the IFD entry.
func (d *decoder) parseIFDOffsets(tag int, maxCount int) (u []uint, err error) {
p, ok := d.ifd[tag]
if !ok {
return nil, nil
}
return d.ifdUint(p[:], maxCount)
}
// parseIFD decides whether the IFD entry in p is "interesting" and
// stows away the data in the decoder. It returns the tag number of the
// entry and an error, if any.
func (d *decoder) parseIFD(p []byte) (int, error) {
// smallEntryMaxCount is the limit to use for parsed IFD entries that
// don't scale with the image size.
//
// We could be more precise and limit the number of entries to exactly the
// correct limit (for example, tTileWidth should always have exactly one value),
// but 16 is small enough to prevent excessive allocation and large enough to
// allow for any multi-value entry.
const smallEntryMaxCount = 16
tag := d.byteOrder.Uint16(p[0:2])
switch tag {
case tBitsPerSample,
@@ -171,30 +208,36 @@ func (d *decoder) parseIFD(p []byte) (int, error) {
tPhotometricInterpretation,
tCompression,
tPredictor,
tStripOffsets,
tStripByteCounts,
tRowsPerStrip,
tTileWidth,
tTileLength,
tTileOffsets,
tTileByteCounts,
tImageLength,
tImageWidth,
tFillOrder,
tT4Options,
tT6Options:
val, err := d.ifdUint(p)
val, err := d.ifdUint(p, smallEntryMaxCount)
if err != nil {
return 0, err
}
d.features[int(tag)] = val
case tStripOffsets,
tStripByteCounts,
tTileOffsets,
tTileByteCounts:
// These keys may contain many values.
// Stash the IFD entry for later parsing.
var v [ifdLen]byte
copy(v[:], p)
d.ifd[int(tag)] = v
case tColorMap:
val, err := d.ifdUint(p)
const maxColors = 256
val, err := d.ifdUint(p, (3*maxColors)+1)
if err != nil {
return 0, err
}
numcolors := len(val) / 3
if len(val)%3 != 0 || numcolors <= 0 || numcolors > 256 {
if len(val)%3 != 0 || numcolors <= 0 || numcolors > maxColors {
return 0, FormatError("bad ColorMap length")
}
d.palette = make([]color.Color, numcolors)
@@ -211,7 +254,7 @@ func (d *decoder) parseIFD(p []byte) (int, error) {
// the value is not 1 [= unsigned integer data], a Baseline
// TIFF reader that cannot handle the SampleFormat value
// must terminate the import process gracefully.
val, err := d.ifdUint(p)
val, err := d.ifdUint(p, smallEntryMaxCount)
if err != nil {
return 0, err
}
@@ -450,10 +493,15 @@ func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
return nil
}
// maxBytesPerPixel is the maximum possible bytes-per-pixel,
// used for conservative bounds checking.
const maxBytesPerPixel = 8
func newDecoder(r io.Reader) (*decoder, error) {
d := &decoder{
r: newReaderAt(r),
features: make(map[int][]uint),
ifd: make(map[int][ifdLen]byte),
}
p := make([]byte, 8)
@@ -499,10 +547,20 @@ func newDecoder(r io.Reader) (*decoder, error) {
prevTag = tag
}
d.config.Width = int(d.firstVal(tImageWidth))
d.config.Height = int(d.firstVal(tImageLength))
if d.config.Width, err = d.firstIntVal(tImageWidth); err != nil {
return nil, err
}
if d.config.Height, err = d.firstIntVal(tImageLength); err != nil {
return nil, err
}
if d.config.Width == 0 || d.config.Height == 0 {
return nil, errors.New("tiff: zero-size image")
return nil, FormatError("zero-size image")
}
// Check that the image fits in memory.
// This conservatively assumes 8 bytes per pixel,
// rather than using the actual pixel size.
if _, ok := safemath.Mul3(d.config.Width, d.config.Height, maxBytesPerPixel); !ok {
return nil, FormatError("image too large")
}
if _, ok := d.features[tBitsPerSample]; !ok {
@@ -640,11 +698,15 @@ func Decode(r io.Reader) (img image.Image, err error) {
var blockOffsets, blockCounts []uint
if int(d.firstVal(tTileWidth)) != 0 {
if d.firstVal(tTileWidth) != 0 {
blockPadding = true
blockWidth = int(d.firstVal(tTileWidth))
blockHeight = int(d.firstVal(tTileLength))
if blockWidth, err = d.firstIntVal(tTileWidth); err != nil {
return nil, err
}
if blockHeight, err = d.firstIntVal(tTileLength); err != nil {
return nil, err
}
// The specification says that tile widths and lengths must be a multiple of 16.
// We currently permit invalid sizes, but reject anything too small to limit the
@@ -652,7 +714,30 @@ func Decode(r io.Reader) (img image.Image, err error) {
if blockWidth < 8 || blockHeight < 8 {
return nil, FormatError("tile size is too small")
}
// Same conservative assumption on bytes-per-pixel as for the image dimensions.
if _, ok := safemath.Mul3(blockWidth, blockHeight, maxBytesPerPixel); !ok {
return nil, FormatError("tile size is too large")
}
if blockWidth-d.config.Width > 16 || blockHeight-d.config.Height > 16 {
// Tiles may be padded to the nearest multiple of 16, but one of
// the dimensions of the tile exceeds the image dimension by more
// than padding would require.
//
// Typical TIFF tiles are 256x256, but 1024x1024 appears to be
// in occasional use. If the tile is both larger than the image
// and has more than 1024 pixels in one dimension, it's
// probably malicious input.
//
// Note that this still permits very large tiles,
// so long as the tile is smaller than the image dimensions.
// A gigapixel image with a 2048x2048 tile size
// (used in some GIS applications)
// will be valid because the image dimensions are much larger
// than the tile size.
if blockWidth > 1024 || blockHeight > 1024 {
return nil, FormatError("tile size exceeds image size")
}
}
if blockWidth != 0 {
blocksAcross = (d.config.Width + blockWidth - 1) / blockWidth
}
@@ -660,20 +745,34 @@ func Decode(r io.Reader) (img image.Image, err error) {
blocksDown = (d.config.Height + blockHeight - 1) / blockHeight
}
blockCounts = d.features[tTileByteCounts]
blockOffsets = d.features[tTileOffsets]
blockOffsets, err = d.parseIFDOffsets(tTileOffsets, blocksAcross*blocksDown)
if err != nil {
return nil, err
}
blockCounts, err = d.parseIFDOffsets(tTileByteCounts, blocksAcross*blocksDown)
if err != nil {
return nil, err
}
} else {
if int(d.firstVal(tRowsPerStrip)) != 0 {
blockHeight = int(d.firstVal(tRowsPerStrip))
if v := d.firstVal(tRowsPerStrip); v > 0 && v < uint(blockHeight) {
blockHeight = int(v)
}
if blockHeight != 0 {
// This can't overflow: We require that w*h*8 not overflow and
// blockHeight is no more than d.config.Height.
blocksDown = (d.config.Height + blockHeight - 1) / blockHeight
}
blockOffsets = d.features[tStripOffsets]
blockCounts = d.features[tStripByteCounts]
blockOffsets, err = d.parseIFDOffsets(tStripOffsets, blocksDown)
if err != nil {
return nil, err
}
blockCounts, err = d.parseIFDOffsets(tStripByteCounts, blocksDown)
if err != nil {
return nil, err
}
}
// Check if we have the right number of strips/tiles, offsets and counts.
@@ -728,8 +827,11 @@ func Decode(r io.Reader) (img image.Image, err error) {
// but some tools interpret a missing Compression value as none, so we do
// the same.
case cNone, 0:
if n > blockMaxDataSize {
return nil, FormatError("block data size too large")
}
if b, ok := d.r.(*buffer); ok {
d.buf, err = b.Slice(int(offset), int(n))
d.buf, err = b.Slice(offset, n)
} else {
d.buf, err = safeReadAt(d.r, uint64(n), offset)
}
@@ -756,7 +858,7 @@ func Decode(r io.Reader) (img image.Image, err error) {
d.buf, err = readBuf(r, d.buf, blockMaxDataSize)
r.Close()
case cPackBits:
d.buf, err = unpackBits(io.NewSectionReader(d.r, offset, n))
d.buf, err = unpackBits(io.NewSectionReader(d.r, offset, n), blockMaxDataSize)
default:
err = UnsupportedError(fmt.Sprintf("compression value %d", d.firstVal(tCompression)))
}

View File

@@ -82,6 +82,9 @@ func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) {
if err != nil {
return nil, image.Config{}, err
}
if seenVP8X && (fh.Width != int(widthMinusOne)+1 || fh.Height != int(heightMinusOne)+1) {
return nil, image.Config{}, errInvalidFormat
}
if configOnly {
return nil, image.Config{
ColorModel: color.YCbCrModel,
@@ -111,7 +114,16 @@ func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) {
return nil, c, err
}
m, err := vp8l.Decode(chunkData)
return m, image.Config{}, err
if err != nil {
return nil, image.Config{}, err
}
if seenVP8X {
bounds := m.Bounds()
if bounds.Dx() != int(widthMinusOne)+1 || bounds.Dy() != int(heightMinusOne)+1 {
return nil, image.Config{}, errInvalidFormat
}
}
return m, image.Config{}, nil
case fccVP8X:
if seenVP8X {
@@ -134,10 +146,12 @@ func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) {
wantAlpha = (buf[0] & alphaBit) != 0
widthMinusOne = uint32(buf[4]) | uint32(buf[5])<<8 | uint32(buf[6])<<16
heightMinusOne = uint32(buf[7]) | uint32(buf[8])<<8 | uint32(buf[9])<<16
if uint64(widthMinusOne+1)*uint64(heightMinusOne+1) > 1<<32-1 {
w := uint64(widthMinusOne) + 1
h := uint64(heightMinusOne) + 1
if w*h > 1<<31-1 {
// The product of _Canvas Width_ and _Canvas Height_ MUST be
// at most 2^32 - 1.
// https://www.rfc-editor.org/rfc/rfc9649.html#section-2.7-12
// But it also needs to fit in an int, so limit it to MaxInt32.
return nil, image.Config{}, errInvalidFormat
}
if configOnly {

5
vendor/modules.txt vendored
View File

@@ -872,7 +872,7 @@ github.com/kovidgoyal/go-parallel
# github.com/kovidgoyal/go-shm v1.0.0
## explicit; go 1.24.0
github.com/kovidgoyal/go-shm
# github.com/kovidgoyal/imaging v1.8.21
# github.com/kovidgoyal/imaging v1.8.22
## explicit; go 1.25.0
github.com/kovidgoyal/imaging
github.com/kovidgoyal/imaging/apng
@@ -2444,7 +2444,7 @@ golang.org/x/exp/slices
golang.org/x/exp/slog
golang.org/x/exp/slog/internal
golang.org/x/exp/slog/internal/buffer
# golang.org/x/image v0.40.0
# golang.org/x/image v0.43.0
## explicit; go 1.25.0
golang.org/x/image/bmp
golang.org/x/image/ccitt
@@ -2452,6 +2452,7 @@ golang.org/x/image/font
golang.org/x/image/font/gofont/goregular
golang.org/x/image/font/opentype
golang.org/x/image/font/sfnt
golang.org/x/image/internal/safemath
golang.org/x/image/math/fixed
golang.org/x/image/riff
golang.org/x/image/tiff