go image/png: 添加选项以容忍辅助数据块中的无效校验和

gmol1639  于 22天前  发布在  Go
关注(0)|答案(7)|浏览(50)

你正在使用哪个版本的Go( go version )?

$ go version
go version go1.15.6 darwin/amd64

这个问题在最新版本中是否会重现?

是的。

你正在使用什么操作系统和处理器架构( go env )?

go env 输出

$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/stanhu/Library/Caches/go-build"
GOENV="/Users/stanhu/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/stanhu/.gvm/pkgsets/go1.15.6/global/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/stanhu/.gvm/pkgsets/go1.15.6/global"
GOPRIVATE=""
GOPROXY="https://goproxy.io,direct"
GOROOT="/Users/stanhu/.gvm/gos/go1.15.6"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/Users/stanhu/.gvm/gos/go1.15.6/pkg/tool/darwin_amd64"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/stanhu/gdk-ee/gitlab-workhorse/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/6v/0wvd8sg951l59wy4242m3thh0000gn/T/go-build329049724=/tmp/go-build -gno-record-gcc-switches -fno-common"

你做了什么?

解码附加的图像,该图像具有有效的数据校验和,但无效的ICCP校验和会导致完全解码失败。

大多数图像程序忽略无效的ICC配置文件,但是调用 image.Decode 无法加载此图像:

decode: png: invalid format: invalid checksum

pngcheck 确认iCCP块中存在无效的校验和:

$ pngcheck -v /tmp/openconnect2.png
File: /tmp/openconnect2.png (67451 bytes)
  chunk IHDR at offset 0x0000c, length 13
    400 x 400 image, 32-bit RGB+alpha, non-interlaced
  chunk zTXt at offset 0x00025, length 13615, keyword: Raw profile type exif
  chunk iCCP at offset 0x03560, length 389
    profile name = ICC PROFILE, compression method = 0 (deflate)
    compressed profile = 376 bytes
  CRC error in chunk iCCP (computed f1449d51, expected c8439de9)
ERRORS DETECTED in /tmp/openconnect2.png

由于ICC配置文件并非渲染PNG所必需的,我们应该能够检测到这种错误,但不至于彻底失败。
这与 #10447 类似,并涉及到 #27830

你期望看到什么?

图像已解码,但跳过了ICC配置文件。

你实际上看到了什么?

硬性故障。

k5hmc34c

k5hmc34c2#

http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html 说:
辅助数据块并非严格必要,以便有意义地显示数据流的内容,例如时间数据块(tIME)。遇到未知的数据块类型时,其中辅助比特为1的解码器可以安全地忽略该数据块并继续显示图像。
我已经更新了问题描述,以反映我们可以忽略辅助数据块(即以小写字母开头的那些)。以下补丁解决了这个问题:

diff --git a/src/image/png/reader.go b/src/image/png/reader.go
index 910520bd4b..734bf4757b 100644
--- a/src/image/png/reader.go
+++ b/src/image/png/reader.go
@@ -926,6 +926,13 @@ func (d *decoder) parseChunk() error {
 		d.crc.Write(ignored[:n])
 		length -= uint32(n)
 	}
+
+	// If this is an ancillary chunk, we can ignore the checksum since it's not required
+	if d.tmp[4] >> 5 & 0x1 == 1 {
+		d.verifyChecksum()
+		return nil
+	}
+
 	return d.verifyChecksum()
 }
xam8gpfp

xam8gpfp3#

辅助块可以忽略,但仍然,无效的校验和意味着它是一个无效的PNG文件。如果你打算忽略一些校验和(如“在接受什么方面要宽容”),简单地忽略所有校验和可能更容易。虽然这是可能的(一个支持的论点是附加的文件似乎确实在Chrome网络浏览器中渲染),但我尽量坚持“在接受什么方面要宽容”的原则。

问题(不仅与PNG有关,还与文件格式总体上有关)在于不同的实现决定以不同、不兼容、未记录且可能不安全的方式“在接受什么方面要宽容”。

顺便说一下,规范在这个项目中没有提到“辅助”(尽管在兄弟项目中讨论了辅助和关键块):

PNG签名不匹配、CRC不匹配或意外的流结束表示数据流已损坏,可能被视为致命错误。

关于“添加选项”的部分,说起来容易做起来难。这是一项始于Go 1.0的API设计失败,但png.Decode函数没有任何方法传递选项,我们由于向后兼容性的承诺而无法改变这一点。我们可以设想添加一个png.DecodeWithOptions函数,但如果我们这样做,我们实际上只有一次机会来修复image/png API,为这个和所有未来潜在的解码选项,理想情况下要在其他图像编解码包(如image/gifimage/jpeg等)中保持一致性和可重用性。这是一个比仅仅修补六行代码更难的API设计问题,正如你链接的问题(#10447#27830)所示。

对于立即需要解决的问题,即(无效的)PNG文件被拒绝的情况,png.Decode函数接受一个io.Reader。一种解决方法是传递一个 Package 器io.Reader,该 Package 器只需删除iCCP块(Go的image/png包无论如何都不对这些块做任何处理)。块级别的PNG文件格式非常简单:8字节的魔数,接着是一连串的块。每个块是4字节 LENGTH,4字节块名, LENGTH字节有效负载和4字节校验和。 Package 器io.Reader只需检测并删除任何名为 iCCP 的块的 N+12 字节即可。或者,你的 Package 器io.Reader可以替代地删除任何辅助块,或者具有不匹配校验和的辅助块。

ukqbszuj

ukqbszuj4#

部分问题(PNG以及文件格式总体上也是如此)在于不同的实现已经决定以不同、不兼容、未记录且可能不安全的方式“对你接受的内容保持开放”。
感谢反馈。这是一个很好的观点。
这不仅仅是一个六行补丁的API设计问题,正如你链接的问题(#10447#27830 )所示。
是的,我意识到这是一个复杂的问题。
一种解决方法是传递一个 Package 器io.Reader,它只是去除iCCP块(Go的image/png包无论如何都不对它们做任何处理)。
谢谢。根据我们的日志,我们看到很多用户上传的带有PNG解码错误的头像,通常是由于无效的iCCP配置文件和错误的校验和。我不太确定为什么一开始会发生这种情况。我的同事在https://gitlab.com/gitlab-org/gitlab-workhorse/-/merge_requests/673/diffs中实现了这样一个 Package 器。要做到这一点有点棘手。

uoifb46i

uoifb46i5#

我们进行了一些分析,发现所有损坏的iCCP块都是由GIMP 2.10.x版本保存的,这是最新稳定版本。我们怀疑这些问题是相关的:

  1. Patch for broken ICC profile in PNG files Exiv2/exiv2#597
  2. https://gitlab.gnome.org/GNOME/gimp/-/issues/2111
mxg2im7a

mxg2im7a6#

我后来了解到,libpng默认将无效的校验和视为关键数据块的错误,但将其视为辅助数据块的警告。在实践中,无效的辅助数据块校验和会被忽略。
https://github.com/glennrp/libpng/blob/dbe3e0c43e549a1602286144d94b0666549b18e6/png.h#L1436
我对Go的image/png持开放态度,因此可以完全忽略辅助数据块的校验和(Go并不真正支持警告)。显然,这种行为变化需要等到我们摆脱开发冻结期(https://github.com/golang/go/wiki/Go-Release-Cycle)才能实现。

f1tvaqid

f1tvaqid7#

一种解决方法是传递一个 Package 器io.Reader,它只是去除iCCP块(Go的image/png包无论如何都不对它们做任何处理)。
以下是我的实现:
https://github.com/google/wuffs/blob/414a011491ff513b86d8694c5d71800f3cb5a715/script/strip-png-ancillary-chunks.go
请随意复制粘贴。

相关问题