-
Notifications
You must be signed in to change notification settings - Fork 630
Description
Using tarball.ImageFromPath() fails against an image with 0-byte layer.tar files. Although empty layers seem to be typically represented by 1024-byte files with all NULs, 0-byte layers are produced in the wild and Moby considers 0-byte layer.tar files are considered valid.
A test case is at: https://github.com/briandealwis/go-cr-bug. This test case is using a windows image produced as a test by bazelbuild/rules_docker
$ tar tvf bazel-out/darwin-fastbuild/bin/tests/docker/basic_windows_image.tar
-rw-r--r-- 0 0 0 907 31 Dec 1969 378fb1f1c5d59102b2b942e7c5cd76ce913cb5a4b74e97f87041dc61ab14b21e.json
-rw-r--r-- 0 0 0 3 31 Dec 1969 0239ecd27132f0d70c7946a5365ef1f0a3edc7ecf6bdeeb61b4215598beb9828/VERSION
-rw-r--r-- 0 0 0 10240 31 Dec 1969 0239ecd27132f0d70c7946a5365ef1f0a3edc7ecf6bdeeb61b4215598beb9828/layer.tar
-rw-r--r-- 0 0 0 461 31 Dec 1969 0239ecd27132f0d70c7946a5365ef1f0a3edc7ecf6bdeeb61b4215598beb9828/json
-rw-r--r-- 0 0 0 3 31 Dec 1969 54625cd3a0909e177f99d40c983676e65cd2ba5e3231670bd3b3a060463e384c/VERSION
-rw-r--r-- 0 0 0 0 31 Dec 1969 54625cd3a0909e177f99d40c983676e65cd2ba5e3231670bd3b3a060463e384c/layer.tar
-rw-r--r-- 0 0 0 250 31 Dec 1969 54625cd3a0909e177f99d40c983676e65cd2ba5e3231670bd3b3a060463e384c/json
-rw-r--r-- 0 0 0 3 31 Dec 1969 fa2da5bcc17fb2e5c2025c1b76104bdd0f5b9ce64d6d4b29762e58aa11d97ee8/VERSION
-rw-r--r-- 0 0 0 0 31 Dec 1969 fa2da5bcc17fb2e5c2025c1b76104bdd0f5b9ce64d6d4b29762e58aa11d97ee8/layer.tar
-rw-r--r-- 0 0 0 178 31 Dec 1969 fa2da5bcc17fb2e5c2025c1b76104bdd0f5b9ce64d6d4b29762e58aa11d97ee8/json
-rw-r--r-- 0 0 0 115 31 Dec 1969 repositories
-rw-r--r-- 0 0 0 1037 31 Dec 1969 manifest.json
I stumbled across this bug trying to update bazelbuild/rules_docker to use container-structure-test from v1.4.0 to v1.7.0, which led to test failures. container-structure-test switched out to use use go-containerregistry with v1.6.0.
The issue is that tarball.ImageFromPath calls into go-containerregistry to open the Image, which checks if the image layers are compressed. This is determined by checking if the top-most layer IsGzipped.
go-containerregistry/pkg/v1/v1util/zip.go
Lines 73 to 80 in 70c1146
| // IsGzipped detects whether the input stream is compressed. | |
| func IsGzipped(r io.Reader) (bool, error) { | |
| magicHeader := make([]byte, 2) | |
| if _, err := r.Read(magicHeader); err != nil { | |
| return false, err | |
| } | |
| return bytes.Equal(magicHeader, gzipMagicHeader), nil | |
| } |
The io.Reader's Read call returns (0, io.EOF) and this error is cascaded back to the callers, even though this is a normal return.
The following fix just treats a 0-byte file as not being compressed. But given that empty layers are usually represented as 1024-byte NUL files, I suppose it's possible that there could be a 1-byte or 2-byte layer.tar file too?
diff --git a/pkg/v1/v1util/zip.go b/pkg/v1/v1util/zip.go
index 57514a4..2b0f24f 100644
--- a/pkg/v1/v1util/zip.go
+++ b/pkg/v1/v1util/zip.go
@@ -73,7 +73,11 @@ func GunzipReadCloser(r io.ReadCloser) (io.ReadCloser, error) {
// IsGzipped detects whether the input stream is compressed.
func IsGzipped(r io.Reader) (bool, error) {
magicHeader := make([]byte, 2)
- if _, err := r.Read(magicHeader); err != nil {
+ n, err := r.Read(magicHeader)
+ if n == 0 && err == io.EOF {
+ return false, nil
+ }
+ if err != nil {
return false, err
}
return bytes.Equal(magicHeader, gzipMagicHeader), nil