github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/pkg/hardening/verified_reader_test.go (about)

     1  /*
     2   * umoci: Umoci Modifies Open Containers' Images
     3   * Copyright (C) 2016-2020 SUSE LLC
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *    http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  package hardening
    19  
    20  import (
    21  	"bytes"
    22  	"crypto/rand"
    23  	"fmt"
    24  	"io"
    25  	"io/ioutil"
    26  	"testing"
    27  
    28  	// Needed for digest.
    29  	_ "crypto/sha256"
    30  
    31  	"github.com/opencontainers/go-digest"
    32  	"github.com/pkg/errors"
    33  )
    34  
    35  func TestValid(t *testing.T) {
    36  	for size := 1; size <= 16384; size *= 2 {
    37  		t.Run(fmt.Sprintf("size:%d", size), func(t *testing.T) {
    38  			// Fill buffer with random data.
    39  			buffer := new(bytes.Buffer)
    40  			if _, err := io.CopyN(buffer, rand.Reader, int64(size)); err != nil {
    41  				t.Fatalf("getting random data for buffer failed: %v", err)
    42  			}
    43  
    44  			// Get expected hash.
    45  			expectedDigest := digest.SHA256.FromBytes(buffer.Bytes())
    46  			verifiedReader := &VerifiedReadCloser{
    47  				Reader:         ioutil.NopCloser(buffer),
    48  				ExpectedDigest: expectedDigest,
    49  				ExpectedSize:   int64(size),
    50  			}
    51  
    52  			// Make sure everything if we copy-to-EOF we get no errors.
    53  			if _, err := io.Copy(ioutil.Discard, verifiedReader); err != nil {
    54  				t.Errorf("expected digest+size to be correct on EOF: got an error: %v", err)
    55  			}
    56  
    57  			// And on close we shouldn't get an error either.
    58  			if err := verifiedReader.Close(); err != nil {
    59  				t.Errorf("expected digest+size to be correct on Close: got an error: %v", err)
    60  			}
    61  		})
    62  	}
    63  }
    64  
    65  func TestValidIgnoreLength(t *testing.T) {
    66  	for size := 1; size <= 16384; size *= 2 {
    67  		t.Run(fmt.Sprintf("size:%d", size), func(t *testing.T) {
    68  			// Fill buffer with random data.
    69  			buffer := new(bytes.Buffer)
    70  			if _, err := io.CopyN(buffer, rand.Reader, int64(size)); err != nil {
    71  				t.Fatalf("getting random data for buffer failed: %v", err)
    72  			}
    73  
    74  			// Get expected hash.
    75  			expectedDigest := digest.SHA256.FromBytes(buffer.Bytes())
    76  			verifiedReader := &VerifiedReadCloser{
    77  				Reader:         ioutil.NopCloser(buffer),
    78  				ExpectedDigest: expectedDigest,
    79  				ExpectedSize:   int64(-1),
    80  			}
    81  
    82  			// Make sure everything if we copy-to-EOF we get no errors.
    83  			if _, err := io.Copy(ioutil.Discard, verifiedReader); err != nil {
    84  				t.Errorf("expected digest+size to be correct on EOF: got an error: %v", err)
    85  			}
    86  
    87  			// And on close we shouldn't get an error either.
    88  			if err := verifiedReader.Close(); err != nil {
    89  				t.Errorf("expected digest+size to be correct on Close: got an error: %v", err)
    90  			}
    91  		})
    92  	}
    93  }
    94  
    95  func TestValidTrailing(t *testing.T) {
    96  	for size := 1; size <= 16384; size *= 2 {
    97  		t.Run(fmt.Sprintf("size:%d", size), func(t *testing.T) {
    98  			// Fill buffer with random data.
    99  			buffer := new(bytes.Buffer)
   100  			if _, err := io.CopyN(buffer, rand.Reader, int64(size)); err != nil {
   101  				t.Fatalf("getting random data for buffer failed: %v", err)
   102  			}
   103  
   104  			// Get expected hash.
   105  			expectedDigest := digest.SHA256.FromBytes(buffer.Bytes())
   106  			verifiedReader := &VerifiedReadCloser{
   107  				Reader:         ioutil.NopCloser(buffer),
   108  				ExpectedDigest: expectedDigest,
   109  				ExpectedSize:   int64(-1),
   110  			}
   111  
   112  			// Read *half* of the bytes, leaving some remaining. We should get
   113  			// no errors.
   114  			if _, err := io.CopyN(ioutil.Discard, verifiedReader, int64(size/2)); err != nil {
   115  				t.Errorf("expected no error after reading only %d bytes: got an error: %v", size/2, err)
   116  			}
   117  
   118  			// And on close we shouldn't get an error either.
   119  			if err := verifiedReader.Close(); err != nil {
   120  				t.Errorf("expected digest+size to be correct on Close: got an error: %v", err)
   121  			}
   122  		})
   123  	}
   124  }
   125  
   126  func TestInvalidDigest(t *testing.T) {
   127  	for size := 1; size <= 16384; size *= 2 {
   128  		t.Run(fmt.Sprintf("size:%d", size), func(t *testing.T) {
   129  			// Fill buffer with random data.
   130  			buffer := new(bytes.Buffer)
   131  			if _, err := io.CopyN(buffer, rand.Reader, int64(size)); err != nil {
   132  				t.Fatalf("getting random data for buffer failed: %v", err)
   133  			}
   134  
   135  			// Generate an *incorrect* hash.
   136  			fakeBytes := append(buffer.Bytes()[1:], 0x80)
   137  			expectedDigest := digest.SHA256.FromBytes(fakeBytes)
   138  			verifiedReader := &VerifiedReadCloser{
   139  				Reader:         ioutil.NopCloser(buffer),
   140  				ExpectedDigest: expectedDigest,
   141  				ExpectedSize:   int64(size),
   142  			}
   143  
   144  			// Make sure everything if we copy-to-EOF we get the right error.
   145  			if _, err := io.Copy(ioutil.Discard, verifiedReader); errors.Cause(err) != ErrDigestMismatch {
   146  				t.Errorf("expected digest to be invalid on EOF: got wrong error: %v", err)
   147  			}
   148  
   149  			// And on close we should get the error.
   150  			if err := verifiedReader.Close(); errors.Cause(err) != ErrDigestMismatch {
   151  				t.Errorf("expected digest to be invalid on Close: got wrong error: %v", err)
   152  			}
   153  		})
   154  	}
   155  }
   156  
   157  func TestInvalidDigest_Trailing(t *testing.T) {
   158  	for size := 1; size <= 16384; size *= 2 {
   159  		for delta := 1; delta-1 <= size/2; delta *= 2 {
   160  			t.Run(fmt.Sprintf("size:%d_delta:%d", size, delta), func(t *testing.T) {
   161  				// Fill buffer with random data.
   162  				buffer := new(bytes.Buffer)
   163  				if _, err := io.CopyN(buffer, rand.Reader, int64(size)); err != nil {
   164  					t.Fatalf("getting random data for buffer failed: %v", err)
   165  				}
   166  
   167  				// Generate a correct hash (for a shorter buffer), but don't
   168  				// verify the size -- this is to make sure that we actually
   169  				// read all the bytes.
   170  				shortBuffer := buffer.Bytes()[:size-delta]
   171  				expectedDigest := digest.SHA256.FromBytes(shortBuffer)
   172  				verifiedReader := &VerifiedReadCloser{
   173  					Reader:         ioutil.NopCloser(buffer),
   174  					ExpectedDigest: expectedDigest,
   175  					ExpectedSize:   -1,
   176  				}
   177  
   178  				// Make sure everything if we copy-to-EOF we get the right error.
   179  				if _, err := io.CopyN(ioutil.Discard, verifiedReader, int64(size-delta)); err != nil {
   180  					t.Errorf("expected no errors after reading N bytes: got error: %v", err)
   181  				}
   182  
   183  				// Check that the digest does actually match right now.
   184  				verifiedReader.init()
   185  				if err := verifiedReader.verify(nil); err != nil {
   186  					t.Errorf("expected no errors in verify before Close: got error: %v", err)
   187  				}
   188  
   189  				// And on close we should get the error.
   190  				if err := verifiedReader.Close(); errors.Cause(err) != ErrDigestMismatch {
   191  					t.Errorf("expected digest to be invalid on Close: got wrong error: %v", err)
   192  				}
   193  			})
   194  
   195  		}
   196  	}
   197  }
   198  
   199  func TestInvalidSize_Short(t *testing.T) {
   200  	for size := 1; size <= 16384; size *= 2 {
   201  		for delta := 1; delta-1 <= size/2; delta *= 2 {
   202  			t.Run(fmt.Sprintf("size:%d_delta:%d", size, delta), func(t *testing.T) {
   203  				// Fill buffer with random data.
   204  				buffer := new(bytes.Buffer)
   205  				if _, err := io.CopyN(buffer, rand.Reader, int64(size)); err != nil {
   206  					t.Fatalf("getting random data for buffer failed: %v", err)
   207  				}
   208  
   209  				// Generate a correct hash (for a shorter buffer), but limit the
   210  				// size to be smaller.
   211  				shortBuffer := buffer.Bytes()[:buffer.Len()-delta]
   212  				expectedDigest := digest.SHA256.FromBytes(shortBuffer)
   213  				verifiedReader := &VerifiedReadCloser{
   214  					Reader:         ioutil.NopCloser(buffer),
   215  					ExpectedDigest: expectedDigest,
   216  					ExpectedSize:   int64(size - delta),
   217  				}
   218  
   219  				// Make sure everything if we copy-to-EOF we get the right error.
   220  				if _, err := io.Copy(ioutil.Discard, verifiedReader); errors.Cause(err) != ErrSizeMismatch {
   221  					t.Errorf("expected size to be invalid on EOF: got wrong error: %v", err)
   222  				}
   223  
   224  				// And on close we should get the error.
   225  				if err := verifiedReader.Close(); errors.Cause(err) != ErrSizeMismatch {
   226  					t.Errorf("expected size to be invalid on Close: got wrong error: %v", err)
   227  				}
   228  			})
   229  
   230  		}
   231  	}
   232  }
   233  
   234  func TestInvalidSize_LongBuffer(t *testing.T) {
   235  	for size := 1; size <= 16384; size *= 2 {
   236  		for delta := 1; delta-1 <= size/2; delta *= 2 {
   237  			t.Run(fmt.Sprintf("size:%d_delta:%d", size, delta), func(t *testing.T) {
   238  				// Fill buffer with random data.
   239  				buffer := new(bytes.Buffer)
   240  				if _, err := io.CopyN(buffer, rand.Reader, int64(size)); err != nil {
   241  					t.Fatalf("getting random data for buffer failed: %v", err)
   242  				}
   243  
   244  				// Generate a correct hash (for the full buffer), but limit the
   245  				// size to be smaller (so that we ensure we don't allow such
   246  				// actions).
   247  				shortBuffer := buffer.Bytes()[:size-delta]
   248  				expectedDigest := digest.SHA256.FromBytes(shortBuffer)
   249  				verifiedReader := &VerifiedReadCloser{
   250  					Reader:         ioutil.NopCloser(buffer),
   251  					ExpectedDigest: expectedDigest,
   252  					ExpectedSize:   int64(size - delta),
   253  				}
   254  
   255  				// Make sure everything if we copy-to-EOF we get the right error.
   256  				if _, err := io.Copy(ioutil.Discard, verifiedReader); errors.Cause(err) != ErrSizeMismatch {
   257  					t.Errorf("expected size to be invalid on EOF: got wrong error: %v", err)
   258  				}
   259  
   260  				// And on close we should get the error.
   261  				if err := verifiedReader.Close(); errors.Cause(err) != ErrSizeMismatch {
   262  					t.Errorf("expected size to be invalid on Close: got wrong error: %v", err)
   263  				}
   264  			})
   265  		}
   266  	}
   267  }
   268  
   269  func TestInvalidSize_Long(t *testing.T) {
   270  	for size := 1; size <= 16384; size *= 2 {
   271  		for delta := 1; delta-1 <= size/2; delta *= 2 {
   272  			t.Run(fmt.Sprintf("size:%d_delta:%d", size, delta), func(t *testing.T) {
   273  				// Fill buffer with random data.
   274  				buffer := new(bytes.Buffer)
   275  				if _, err := io.CopyN(buffer, rand.Reader, int64(size)); err != nil {
   276  					t.Fatalf("getting random data for buffer failed: %v", err)
   277  				}
   278  
   279  				// Generate a correct hash, but set the size to be larger.
   280  				expectedDigest := digest.SHA256.FromBytes(buffer.Bytes())
   281  				verifiedReader := &VerifiedReadCloser{
   282  					Reader:         ioutil.NopCloser(buffer),
   283  					ExpectedDigest: expectedDigest,
   284  					ExpectedSize:   int64(size + delta),
   285  				}
   286  
   287  				// Make sure everything if we copy-to-EOF we get the right error.
   288  				if _, err := io.Copy(ioutil.Discard, verifiedReader); errors.Cause(err) != ErrSizeMismatch {
   289  					t.Errorf("expected size to be invalid on EOF: got wrong error: %v", err)
   290  				}
   291  
   292  				// And on close we should get the error.
   293  				if err := verifiedReader.Close(); errors.Cause(err) != ErrSizeMismatch {
   294  					t.Errorf("expected size to be invalid on Close: got wrong error: %v", err)
   295  				}
   296  			})
   297  		}
   298  	}
   299  }
   300  
   301  func TestNoop(t *testing.T) {
   302  	// Fill buffer with random data.
   303  	buffer := new(bytes.Buffer)
   304  	size := 256
   305  	if _, err := io.CopyN(buffer, rand.Reader, int64(size)); err != nil {
   306  		t.Fatalf("getting random data for buffer failed: %v", err)
   307  	}
   308  
   309  	// Get expected hash.
   310  	expectedDigest := digest.SHA256.FromBytes(buffer.Bytes())
   311  	verifiedReader := &VerifiedReadCloser{
   312  		Reader:         ioutil.NopCloser(buffer),
   313  		ExpectedDigest: expectedDigest,
   314  		ExpectedSize:   int64(size),
   315  	}
   316  
   317  	// And make an additional wrapper with the same digest+size ...
   318  	wrappedReader := &VerifiedReadCloser{
   319  		Reader:         verifiedReader,
   320  		ExpectedDigest: verifiedReader.ExpectedDigest,
   321  		ExpectedSize:   verifiedReader.ExpectedSize,
   322  	}
   323  
   324  	// ... and a different digest.
   325  	doubleWrappedReader := &VerifiedReadCloser{
   326  		Reader:         wrappedReader,
   327  		ExpectedDigest: digest.SHA256.FromString("foo"),
   328  		ExpectedSize:   wrappedReader.ExpectedSize,
   329  	}
   330  
   331  	// ... and a different size.
   332  	tripleWrappedReader := &VerifiedReadCloser{
   333  		Reader:         doubleWrappedReader,
   334  		ExpectedDigest: doubleWrappedReader.ExpectedDigest,
   335  		ExpectedSize:   doubleWrappedReader.ExpectedSize - 1,
   336  	}
   337  
   338  	// Read from the uppermost wrapper, ignoring all errors.
   339  	_, _ = io.Copy(ioutil.Discard, tripleWrappedReader)
   340  	_ = tripleWrappedReader.Close()
   341  
   342  	// Bottom-most wrapper should've been hit.
   343  	if verifiedReader.digester == nil {
   344  		t.Errorf("verifiedReader didn't digest input")
   345  	}
   346  	// Middle wrapper (identical to lowest) is a noop.
   347  	if wrappedReader.digester != nil {
   348  		t.Errorf("wrappedReader wasn't noop'd out")
   349  	}
   350  	// Different-digest wrapper is *not* a noop.
   351  	if doubleWrappedReader.digester == nil {
   352  		t.Errorf("doubleWrappedReader was incorrectly noop'd out")
   353  	}
   354  	// Different-size wrapper is *not* a noop.
   355  	if tripleWrappedReader.digester == nil {
   356  		t.Errorf("tripleWrappedReader was incorrectly noop'd out")
   357  	}
   358  }