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 }