Skip to content

Commit

Permalink
validate.Layer: handle validation for layer content that is not compr…
Browse files Browse the repository at this point in the history
…essed

Fixes #1826
  • Loading branch information
nmiyake committed Jan 20, 2024
1 parent a0658aa commit 5532eb0
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 4 deletions.
21 changes: 17 additions & 4 deletions pkg/v1/validate/layer.go
Expand Up @@ -21,6 +21,8 @@ import (
"encoding/hex"
"errors"
"fmt"
"github.com/google/go-containerregistry/internal/compression"
comp "github.com/google/go-containerregistry/pkg/compression"
"io"
"strings"

Expand Down Expand Up @@ -128,21 +130,32 @@ func computeLayer(layer v1.Layer) (*computedLayer, error) {
}()

// Read the bytes through gzip.Reader to compute the DiffID.
uncompressed, err := gzip.NewReader(pr)
if err != nil {
return nil, err
var uncompressed io.ReadCloser
// if layer content is known to be not compressed, don't wrap in gzip reader
if cp, pr, err := compression.PeekCompression(pr); err == nil && cp == comp.None {
uncompressed = io.NopCloser(pr)
} else {
uncompressed, err = gzip.NewReader(pr)
if err != nil {
return nil, err
}
}
diffider := crypto.SHA256.New()
hashUncompressed := io.TeeReader(uncompressed, diffider)

// Ensure there aren't duplicate file paths.
// Ensure there aren't duplicate file paths if content is tar.
firstTarEntry := true
tarReader := tar.NewReader(hashUncompressed)
files := make(map[string]struct{})
for {
hdr, err := tarReader.Next()
if errors.Is(err, io.EOF) {
break
} else if firstTarEntry && (errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, tar.ErrHeader)) {
// if the first entry cannot be read, content is not a valid tar
break
}
firstTarEntry = false
if err != nil {
return nil, err
}
Expand Down
95 changes: 95 additions & 0 deletions pkg/v1/validate/layer_test.go
@@ -0,0 +1,95 @@
// Copyright 2019 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package validate

import (
"bytes"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/partial"
"github.com/google/go-containerregistry/pkg/v1/random"
"github.com/google/go-containerregistry/pkg/v1/types"
"io"
"testing"
)

// Tests that validation succeeds for valid layer that contains GZ-compressed tar content.
func TestValidateCompressedLayer(t *testing.T) {
layer, err := random.Layer(100, types.OCILayer)
if err != nil {
t.Error("could not create layer:", err)
}
if err := Layer(layer); err != nil {
t.Error("compressed layer failed validation:", err)
}
}

// Tests that validation succeeds for valid layer that contains uncompressed tar content.
func TestValidateLayerWithTarContentNotCompressed(t *testing.T) {
layer, err := random.Layer(100, types.DockerUncompressedLayer)
if err != nil {
t.Error("could not create layer:", err)
}
tarReadCloser, err := layer.Uncompressed()
if err != nil {
t.Error("could not get tar content:", err)
}
tarBytes, err := io.ReadAll(tarReadCloser)
if err != nil {
t.Error("could not read tar content:", err)
}
layer, err = partial.CompressedToLayer(&compressedBytesLayer{
content: tarBytes,
})
if err != nil {
t.Error("could not create layer:", err)
}
if err := Layer(layer); err != nil {
t.Error("compressed layer failed validation:", err)
}
}

// Tests that validation succeeds for valid layer that contains uncompressed arbitrary content.
func TestValidateLayerWithContentNotCompressed(t *testing.T) {
layer, err := partial.CompressedToLayer(&compressedBytesLayer{
content: []byte("test-content"),
})
if err != nil {
t.Error("could not create layer:", err)
}
if err := Layer(layer); err != nil {
t.Error("compressed layer failed validation:", err)
}
}

type compressedBytesLayer struct {
content []byte
}

func (cl *compressedBytesLayer) Digest() (v1.Hash, error) {
hash, _, err := v1.SHA256(bytes.NewReader(cl.content))
return hash, err
}

func (cl *compressedBytesLayer) Compressed() (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(cl.content)), nil
}

func (cl *compressedBytesLayer) Size() (int64, error) {
return int64(len(cl.content)), nil
}

func (cl *compressedBytesLayer) MediaType() (types.MediaType, error) {
return types.OCIContentDescriptor, nil
}

0 comments on commit 5532eb0

Please sign in to comment.