github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/merkletree/merkletree_test.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package merkletree 16 17 import ( 18 "bytes" 19 "errors" 20 "fmt" 21 "io" 22 "math/rand" 23 "testing" 24 "time" 25 26 "github.com/SagerNet/gvisor/pkg/abi/linux" 27 28 "github.com/SagerNet/gvisor/pkg/hostarch" 29 ) 30 31 func TestLayout(t *testing.T) { 32 testCases := []struct { 33 name string 34 dataSize int64 35 hashAlgorithms int 36 dataAndTreeInSameFile bool 37 expectedDigestSize int64 38 expectedLevelOffset []int64 39 }{ 40 { 41 name: "SmallSizeSHA256SeparateFile", 42 dataSize: 100, 43 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, 44 dataAndTreeInSameFile: false, 45 expectedDigestSize: 32, 46 expectedLevelOffset: []int64{0}, 47 }, 48 { 49 name: "SmallSizeSHA512SeparateFile", 50 dataSize: 100, 51 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, 52 dataAndTreeInSameFile: false, 53 expectedDigestSize: 64, 54 expectedLevelOffset: []int64{0}, 55 }, 56 { 57 name: "SmallSizeSHA256SameFile", 58 dataSize: 100, 59 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, 60 dataAndTreeInSameFile: true, 61 expectedDigestSize: 32, 62 expectedLevelOffset: []int64{hostarch.PageSize}, 63 }, 64 { 65 name: "SmallSizeSHA512SameFile", 66 dataSize: 100, 67 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, 68 dataAndTreeInSameFile: true, 69 expectedDigestSize: 64, 70 expectedLevelOffset: []int64{hostarch.PageSize}, 71 }, 72 { 73 name: "MiddleSizeSHA256SeparateFile", 74 dataSize: 1000000, 75 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, 76 dataAndTreeInSameFile: false, 77 expectedDigestSize: 32, 78 expectedLevelOffset: []int64{0, 2 * hostarch.PageSize, 3 * hostarch.PageSize}, 79 }, 80 { 81 name: "MiddleSizeSHA512SeparateFile", 82 dataSize: 1000000, 83 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, 84 dataAndTreeInSameFile: false, 85 expectedDigestSize: 64, 86 expectedLevelOffset: []int64{0, 4 * hostarch.PageSize, 5 * hostarch.PageSize}, 87 }, 88 { 89 name: "MiddleSizeSHA256SameFile", 90 dataSize: 1000000, 91 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, 92 dataAndTreeInSameFile: true, 93 expectedDigestSize: 32, 94 expectedLevelOffset: []int64{245 * hostarch.PageSize, 247 * hostarch.PageSize, 248 * hostarch.PageSize}, 95 }, 96 { 97 name: "MiddleSizeSHA512SameFile", 98 dataSize: 1000000, 99 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, 100 dataAndTreeInSameFile: true, 101 expectedDigestSize: 64, 102 expectedLevelOffset: []int64{245 * hostarch.PageSize, 249 * hostarch.PageSize, 250 * hostarch.PageSize}, 103 }, 104 { 105 name: "LargeSizeSHA256SeparateFile", 106 dataSize: 4096 * int64(hostarch.PageSize), 107 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, 108 dataAndTreeInSameFile: false, 109 expectedDigestSize: 32, 110 expectedLevelOffset: []int64{0, 32 * hostarch.PageSize, 33 * hostarch.PageSize}, 111 }, 112 { 113 name: "LargeSizeSHA512SeparateFile", 114 dataSize: 4096 * int64(hostarch.PageSize), 115 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, 116 dataAndTreeInSameFile: false, 117 expectedDigestSize: 64, 118 expectedLevelOffset: []int64{0, 64 * hostarch.PageSize, 65 * hostarch.PageSize}, 119 }, 120 { 121 name: "LargeSizeSHA256SameFile", 122 dataSize: 4096 * int64(hostarch.PageSize), 123 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, 124 dataAndTreeInSameFile: true, 125 expectedDigestSize: 32, 126 expectedLevelOffset: []int64{4096 * hostarch.PageSize, 4128 * hostarch.PageSize, 4129 * hostarch.PageSize}, 127 }, 128 { 129 name: "LargeSizeSHA512SameFile", 130 dataSize: 4096 * int64(hostarch.PageSize), 131 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, 132 dataAndTreeInSameFile: true, 133 expectedDigestSize: 64, 134 expectedLevelOffset: []int64{4096 * hostarch.PageSize, 4160 * hostarch.PageSize, 4161 * hostarch.PageSize}, 135 }, 136 } 137 138 for _, tc := range testCases { 139 t.Run(tc.name, func(t *testing.T) { 140 l, err := InitLayout(tc.dataSize, tc.hashAlgorithms, tc.dataAndTreeInSameFile) 141 if err != nil { 142 t.Fatalf("Failed to InitLayout: %v", err) 143 } 144 if l.blockSize != int64(hostarch.PageSize) { 145 t.Errorf("Got blockSize %d, want %d", l.blockSize, hostarch.PageSize) 146 } 147 if l.digestSize != tc.expectedDigestSize { 148 t.Errorf("Got digestSize %d, want %d", l.digestSize, sha256DigestSize) 149 } 150 if l.numLevels() != len(tc.expectedLevelOffset) { 151 t.Errorf("Got levels %d, want %d", l.numLevels(), len(tc.expectedLevelOffset)) 152 } 153 for i := 0; i < l.numLevels() && i < len(tc.expectedLevelOffset); i++ { 154 if l.levelOffset[i] != tc.expectedLevelOffset[i] { 155 t.Errorf("Got levelStart[%d] %d, want %d", i, l.levelOffset[i], tc.expectedLevelOffset[i]) 156 } 157 } 158 }) 159 } 160 } 161 162 const ( 163 defaultName = "merkle_test" 164 defaultMode = 0644 165 defaultUID = 0 166 defaultGID = 0 167 defaultSymlinkPath = "merkle_test_link" 168 defaultHashAlgorithm = linux.FS_VERITY_HASH_ALG_SHA256 169 ) 170 171 // bytesReadWriter is used to read from/write to/seek in a byte array. Unlike 172 // bytes.Buffer, it keeps the whole buffer during read so that it can be reused. 173 type bytesReadWriter struct { 174 // bytes contains the underlying byte array. 175 bytes []byte 176 // readPos is the currently location for Read. Write always appends to 177 // the end of the array. 178 readPos int 179 } 180 181 func (brw *bytesReadWriter) Write(p []byte) (int, error) { 182 brw.bytes = append(brw.bytes, p...) 183 return len(p), nil 184 } 185 186 func (brw *bytesReadWriter) ReadAt(p []byte, off int64) (int, error) { 187 bytesRead := copy(p, brw.bytes[off:]) 188 if bytesRead == 0 { 189 return bytesRead, io.EOF 190 } 191 return bytesRead, nil 192 } 193 194 func TestGenerate(t *testing.T) { 195 // The input data has size dataSize. It starts with the data in startWith, 196 // and all other bytes are zeroes. 197 testCases := []struct { 198 name string 199 data []byte 200 hashAlgorithms int 201 dataAndTreeInSameFile bool 202 expectedHash []byte 203 }{ 204 { 205 name: "OnePageZeroesSHA256SeparateFile", 206 data: bytes.Repeat([]byte{0}, hostarch.PageSize), 207 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, 208 dataAndTreeInSameFile: false, 209 expectedHash: []byte{78, 38, 225, 107, 61, 246, 26, 6, 71, 163, 254, 97, 112, 200, 87, 232, 190, 87, 231, 160, 119, 124, 61, 229, 49, 126, 90, 223, 134, 51, 77, 182}, 210 }, 211 { 212 name: "OnePageZeroesSHA256SameFile", 213 data: bytes.Repeat([]byte{0}, hostarch.PageSize), 214 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, 215 dataAndTreeInSameFile: true, 216 expectedHash: []byte{78, 38, 225, 107, 61, 246, 26, 6, 71, 163, 254, 97, 112, 200, 87, 232, 190, 87, 231, 160, 119, 124, 61, 229, 49, 126, 90, 223, 134, 51, 77, 182}, 217 }, 218 { 219 name: "OnePageZeroesSHA512SeparateFile", 220 data: bytes.Repeat([]byte{0}, hostarch.PageSize), 221 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, 222 dataAndTreeInSameFile: false, 223 expectedHash: []byte{221, 45, 182, 132, 61, 212, 227, 145, 150, 131, 98, 221, 195, 5, 89, 21, 188, 36, 250, 101, 85, 78, 197, 253, 193, 23, 74, 219, 28, 108, 77, 47, 65, 79, 123, 144, 50, 245, 109, 72, 71, 80, 24, 77, 158, 95, 242, 185, 109, 163, 105, 183, 67, 106, 55, 194, 223, 46, 12, 242, 165, 203, 172, 254}, 224 }, 225 { 226 name: "OnePageZeroesSHA512SameFile", 227 data: bytes.Repeat([]byte{0}, hostarch.PageSize), 228 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, 229 dataAndTreeInSameFile: true, 230 expectedHash: []byte{221, 45, 182, 132, 61, 212, 227, 145, 150, 131, 98, 221, 195, 5, 89, 21, 188, 36, 250, 101, 85, 78, 197, 253, 193, 23, 74, 219, 28, 108, 77, 47, 65, 79, 123, 144, 50, 245, 109, 72, 71, 80, 24, 77, 158, 95, 242, 185, 109, 163, 105, 183, 67, 106, 55, 194, 223, 46, 12, 242, 165, 203, 172, 254}, 231 }, 232 { 233 name: "MultiplePageZeroesSHA256SeparateFile", 234 data: bytes.Repeat([]byte{0}, 128*hostarch.PageSize+1), 235 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, 236 dataAndTreeInSameFile: false, 237 expectedHash: []byte{131, 122, 73, 143, 4, 202, 193, 156, 218, 169, 196, 223, 70, 100, 117, 191, 241, 113, 134, 11, 229, 231, 105, 157, 156, 0, 66, 213, 122, 145, 174, 8}, 238 }, 239 { 240 name: "MultiplePageZeroesSHA256SameFile", 241 data: bytes.Repeat([]byte{0}, 128*hostarch.PageSize+1), 242 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, 243 dataAndTreeInSameFile: true, 244 expectedHash: []byte{131, 122, 73, 143, 4, 202, 193, 156, 218, 169, 196, 223, 70, 100, 117, 191, 241, 113, 134, 11, 229, 231, 105, 157, 156, 0, 66, 213, 122, 145, 174, 8}, 245 }, 246 { 247 name: "MultiplePageZeroesSHA512SeparateFile", 248 data: bytes.Repeat([]byte{0}, 128*hostarch.PageSize+1), 249 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, 250 dataAndTreeInSameFile: false, 251 expectedHash: []byte{211, 48, 232, 110, 240, 51, 99, 241, 123, 138, 42, 76, 94, 86, 59, 200, 3, 246, 137, 148, 189, 226, 111, 103, 146, 29, 12, 218, 40, 182, 33, 99, 193, 163, 238, 26, 184, 13, 165, 187, 68, 173, 139, 9, 208, 59, 0, 192, 180, 50, 221, 35, 43, 119, 194, 16, 64, 84, 116, 63, 158, 195, 194, 226}, 252 }, 253 { 254 name: "MultiplePageZeroesSHA512SameFile", 255 data: bytes.Repeat([]byte{0}, 128*hostarch.PageSize+1), 256 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, 257 dataAndTreeInSameFile: true, 258 expectedHash: []byte{211, 48, 232, 110, 240, 51, 99, 241, 123, 138, 42, 76, 94, 86, 59, 200, 3, 246, 137, 148, 189, 226, 111, 103, 146, 29, 12, 218, 40, 182, 33, 99, 193, 163, 238, 26, 184, 13, 165, 187, 68, 173, 139, 9, 208, 59, 0, 192, 180, 50, 221, 35, 43, 119, 194, 16, 64, 84, 116, 63, 158, 195, 194, 226}, 259 }, 260 { 261 name: "SingleASHA256SeparateFile", 262 data: []byte{'a'}, 263 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, 264 dataAndTreeInSameFile: false, 265 expectedHash: []byte{26, 47, 238, 138, 235, 244, 140, 231, 129, 240, 155, 252, 219, 44, 46, 72, 57, 249, 139, 88, 132, 238, 86, 108, 181, 115, 96, 72, 99, 210, 134, 47}, 266 }, 267 { 268 name: "SingleASHA256SameFile", 269 data: []byte{'a'}, 270 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, 271 dataAndTreeInSameFile: true, 272 expectedHash: []byte{26, 47, 238, 138, 235, 244, 140, 231, 129, 240, 155, 252, 219, 44, 46, 72, 57, 249, 139, 88, 132, 238, 86, 108, 181, 115, 96, 72, 99, 210, 134, 47}, 273 }, 274 { 275 name: "SingleASHA512SeparateFile", 276 data: []byte{'a'}, 277 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, 278 dataAndTreeInSameFile: false, 279 expectedHash: []byte{44, 30, 224, 12, 102, 119, 163, 171, 119, 175, 212, 121, 231, 188, 125, 171, 79, 28, 144, 234, 75, 122, 44, 75, 15, 101, 173, 92, 233, 109, 234, 60, 173, 148, 125, 85, 94, 234, 95, 91, 16, 196, 88, 175, 23, 129, 226, 110, 24, 238, 5, 49, 186, 128, 72, 188, 193, 180, 207, 193, 203, 119, 40, 191}, 280 }, 281 { 282 name: "SingleASHA512SameFile", 283 data: []byte{'a'}, 284 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, 285 dataAndTreeInSameFile: true, 286 expectedHash: []byte{44, 30, 224, 12, 102, 119, 163, 171, 119, 175, 212, 121, 231, 188, 125, 171, 79, 28, 144, 234, 75, 122, 44, 75, 15, 101, 173, 92, 233, 109, 234, 60, 173, 148, 125, 85, 94, 234, 95, 91, 16, 196, 88, 175, 23, 129, 226, 110, 24, 238, 5, 49, 186, 128, 72, 188, 193, 180, 207, 193, 203, 119, 40, 191}, 287 }, 288 { 289 name: "OnePageASHA256SeparateFile", 290 data: bytes.Repeat([]byte{'a'}, hostarch.PageSize), 291 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, 292 dataAndTreeInSameFile: false, 293 expectedHash: []byte{166, 254, 83, 46, 241, 111, 18, 47, 79, 6, 181, 197, 176, 143, 211, 204, 53, 5, 245, 134, 172, 95, 97, 131, 236, 132, 197, 138, 123, 78, 43, 13}, 294 }, 295 { 296 name: "OnePageASHA256SameFile", 297 data: bytes.Repeat([]byte{'a'}, hostarch.PageSize), 298 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, 299 dataAndTreeInSameFile: true, 300 expectedHash: []byte{166, 254, 83, 46, 241, 111, 18, 47, 79, 6, 181, 197, 176, 143, 211, 204, 53, 5, 245, 134, 172, 95, 97, 131, 236, 132, 197, 138, 123, 78, 43, 13}, 301 }, 302 { 303 name: "OnePageASHA512SeparateFile", 304 data: bytes.Repeat([]byte{'a'}, hostarch.PageSize), 305 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, 306 dataAndTreeInSameFile: false, 307 expectedHash: []byte{23, 69, 6, 79, 39, 232, 90, 246, 62, 55, 4, 229, 47, 36, 230, 24, 233, 47, 55, 36, 26, 139, 196, 78, 242, 12, 194, 77, 109, 81, 151, 188, 63, 201, 127, 235, 81, 214, 91, 200, 19, 232, 240, 14, 197, 1, 99, 224, 18, 213, 203, 242, 44, 102, 25, 62, 90, 189, 106, 107, 129, 61, 115, 39}, 308 }, 309 { 310 name: "OnePageASHA512SameFile", 311 data: bytes.Repeat([]byte{'a'}, hostarch.PageSize), 312 hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, 313 dataAndTreeInSameFile: true, 314 expectedHash: []byte{23, 69, 6, 79, 39, 232, 90, 246, 62, 55, 4, 229, 47, 36, 230, 24, 233, 47, 55, 36, 26, 139, 196, 78, 242, 12, 194, 77, 109, 81, 151, 188, 63, 201, 127, 235, 81, 214, 91, 200, 19, 232, 240, 14, 197, 1, 99, 224, 18, 213, 203, 242, 44, 102, 25, 62, 90, 189, 106, 107, 129, 61, 115, 39}, 315 }, 316 } 317 318 for _, tc := range testCases { 319 t.Run(fmt.Sprintf(tc.name), func(t *testing.T) { 320 var tree bytesReadWriter 321 params := GenerateParams{ 322 Size: int64(len(tc.data)), 323 Name: defaultName, 324 Mode: defaultMode, 325 UID: defaultUID, 326 GID: defaultGID, 327 Children: []string{}, 328 HashAlgorithms: tc.hashAlgorithms, 329 TreeReader: &tree, 330 TreeWriter: &tree, 331 DataAndTreeInSameFile: tc.dataAndTreeInSameFile, 332 } 333 if tc.dataAndTreeInSameFile { 334 tree.Write(tc.data) 335 params.File = &tree 336 } else { 337 params.File = &bytesReadWriter{ 338 bytes: tc.data, 339 } 340 } 341 hash, err := Generate(¶ms) 342 if err != nil { 343 t.Fatalf("Got err: %v, want nil", err) 344 } 345 if !bytes.Equal(hash, tc.expectedHash) { 346 t.Errorf("Got hash: %v, want %v", hash, tc.expectedHash) 347 } 348 }) 349 } 350 } 351 352 // prepareVerify generates test data and corresponding Merkle tree, and returns 353 // the prepared VerifyParams. 354 // The test data has size dataSize. The data is hashed with hashAlgorithm. The 355 // portion to be verified is the range [verifyStart, verifyStart + verifySize). 356 func prepareVerify(t *testing.T, dataSize int64, hashAlgorithm int, dataAndTreeInSameFile, isSymlink bool, verifyStart, verifySize int64, out io.Writer) ([]byte, VerifyParams) { 357 t.Helper() 358 data := make([]byte, dataSize) 359 // Generate random bytes in data. 360 rand.Read(data) 361 362 var tree bytesReadWriter 363 genParams := GenerateParams{ 364 Size: int64(len(data)), 365 Name: defaultName, 366 Mode: defaultMode, 367 UID: defaultUID, 368 GID: defaultGID, 369 Children: []string{}, 370 HashAlgorithms: hashAlgorithm, 371 TreeReader: &tree, 372 TreeWriter: &tree, 373 DataAndTreeInSameFile: dataAndTreeInSameFile, 374 } 375 if dataAndTreeInSameFile { 376 tree.Write(data) 377 genParams.File = &tree 378 } else { 379 genParams.File = &bytesReadWriter{ 380 bytes: data, 381 } 382 } 383 384 if isSymlink { 385 genParams.SymlinkTarget = defaultSymlinkPath 386 } 387 hash, err := Generate(&genParams) 388 if err != nil { 389 t.Fatalf("could not generate Merkle tree:%v", err) 390 } 391 392 return data, VerifyParams{ 393 Out: out, 394 File: bytes.NewReader(data), 395 Tree: &tree, 396 Size: dataSize, 397 Name: defaultName, 398 Mode: defaultMode, 399 UID: defaultUID, 400 GID: defaultGID, 401 Children: []string{}, 402 HashAlgorithms: hashAlgorithm, 403 ReadOffset: verifyStart, 404 ReadSize: verifySize, 405 Expected: hash, 406 DataAndTreeInSameFile: dataAndTreeInSameFile, 407 } 408 } 409 410 func TestVerifyInvalidRange(t *testing.T) { 411 testCases := []struct { 412 name string 413 verifyStart int64 414 verifySize int64 415 }{ 416 // Verify range starts outside data range. 417 { 418 name: "StartOutsideRange", 419 verifyStart: hostarch.PageSize, 420 verifySize: 1, 421 }, 422 // Verify range ends outside data range. 423 { 424 name: "EndOutsideRange", 425 verifyStart: 0, 426 verifySize: 2 * hostarch.PageSize, 427 }, 428 // Verify range with negative size. 429 { 430 name: "NegativeSize", 431 verifyStart: 1, 432 verifySize: -1, 433 }, 434 } 435 for _, tc := range testCases { 436 t.Run(tc.name, func(t *testing.T) { 437 var buf bytes.Buffer 438 _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, false /* dataAndTreeInSameFile */, false /* isSymlink */, tc.verifyStart, tc.verifySize, &buf) 439 if _, err := Verify(¶ms); errors.Is(err, nil) { 440 t.Errorf("Verification succeeded when expected to fail") 441 } 442 }) 443 } 444 } 445 446 func TestVerifyUnmodifiedMetadata(t *testing.T) { 447 testCases := []struct { 448 name string 449 dataAndTreeInSameFile bool 450 isSymlink bool 451 }{ 452 { 453 name: "SeparateFile", 454 dataAndTreeInSameFile: false, 455 isSymlink: true, 456 }, 457 { 458 name: "SameFile", 459 dataAndTreeInSameFile: true, 460 isSymlink: false, 461 }, 462 { 463 name: "SameFileSymlink", 464 dataAndTreeInSameFile: true, 465 isSymlink: true, 466 }, 467 } 468 for _, tc := range testCases { 469 t.Run(tc.name, func(t *testing.T) { 470 var buf bytes.Buffer 471 _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, tc.dataAndTreeInSameFile, tc.isSymlink, 0 /* verifyStart */, 0 /* verifySize */, &buf) 472 if tc.isSymlink { 473 params.SymlinkTarget = defaultSymlinkPath 474 } 475 if _, err := Verify(¶ms); !errors.Is(err, nil) { 476 t.Errorf("Verification failed when expected to succeed: %v", err) 477 } 478 }) 479 } 480 } 481 482 func TestVerifyModifiedName(t *testing.T) { 483 testCases := []struct { 484 name string 485 dataAndTreeInSameFile bool 486 }{ 487 { 488 name: "SeparateFile", 489 dataAndTreeInSameFile: false, 490 }, 491 { 492 name: "SameFile", 493 dataAndTreeInSameFile: true, 494 }, 495 } 496 for _, tc := range testCases { 497 t.Run(tc.name, func(t *testing.T) { 498 var buf bytes.Buffer 499 _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, 0 /* verifyStart */, 0 /* verifySize */, &buf) 500 params.Name += "abc" 501 if _, err := Verify(¶ms); errors.Is(err, nil) { 502 t.Errorf("Verification succeeded when expected to fail") 503 } 504 }) 505 } 506 } 507 508 func TestVerifyModifiedSize(t *testing.T) { 509 testCases := []struct { 510 name string 511 dataAndTreeInSameFile bool 512 }{ 513 { 514 name: "SeparateFile", 515 dataAndTreeInSameFile: false, 516 }, 517 { 518 name: "SameFile", 519 dataAndTreeInSameFile: true, 520 }, 521 } 522 for _, tc := range testCases { 523 t.Run(tc.name, func(t *testing.T) { 524 var buf bytes.Buffer 525 _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, 0 /* verifyStart */, 0 /* verifySize */, &buf) 526 params.Size-- 527 if _, err := Verify(¶ms); errors.Is(err, nil) { 528 t.Errorf("Verification succeeded when expected to fail") 529 } 530 }) 531 } 532 } 533 534 func TestVerifyModifiedMode(t *testing.T) { 535 testCases := []struct { 536 name string 537 dataAndTreeInSameFile bool 538 }{ 539 { 540 name: "SeparateFile", 541 dataAndTreeInSameFile: false, 542 }, 543 { 544 name: "SameFile", 545 dataAndTreeInSameFile: true, 546 }, 547 } 548 for _, tc := range testCases { 549 t.Run(tc.name, func(t *testing.T) { 550 var buf bytes.Buffer 551 _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, 0 /* verifyStart */, 0 /* verifySize */, &buf) 552 params.Mode++ 553 if _, err := Verify(¶ms); errors.Is(err, nil) { 554 t.Errorf("Verification succeeded when expected to fail") 555 } 556 }) 557 } 558 } 559 560 func TestVerifyModifiedUID(t *testing.T) { 561 testCases := []struct { 562 name string 563 dataAndTreeInSameFile bool 564 }{ 565 { 566 name: "SeparateFile", 567 dataAndTreeInSameFile: false, 568 }, 569 { 570 name: "SameFile", 571 dataAndTreeInSameFile: true, 572 }, 573 } 574 for _, tc := range testCases { 575 t.Run(tc.name, func(t *testing.T) { 576 var buf bytes.Buffer 577 _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, 0 /* verifyStart */, 0 /* verifySize */, &buf) 578 params.UID++ 579 if _, err := Verify(¶ms); errors.Is(err, nil) { 580 t.Errorf("Verification succeeded when expected to fail") 581 } 582 }) 583 } 584 } 585 586 func TestVerifyModifiedGID(t *testing.T) { 587 testCases := []struct { 588 name string 589 dataAndTreeInSameFile bool 590 }{ 591 { 592 name: "SeparateFile", 593 dataAndTreeInSameFile: false, 594 }, 595 { 596 name: "SameFile", 597 dataAndTreeInSameFile: true, 598 }, 599 } 600 for _, tc := range testCases { 601 t.Run(tc.name, func(t *testing.T) { 602 var buf bytes.Buffer 603 _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, 0 /* verifyStart */, 0 /* verifySize */, &buf) 604 params.GID++ 605 if _, err := Verify(¶ms); errors.Is(err, nil) { 606 t.Errorf("Verification succeeded when expected to fail") 607 } 608 }) 609 } 610 } 611 612 func TestVerifyModifiedChildren(t *testing.T) { 613 testCases := []struct { 614 name string 615 dataAndTreeInSameFile bool 616 }{ 617 { 618 name: "SeparateFile", 619 dataAndTreeInSameFile: false, 620 }, 621 { 622 name: "SameFile", 623 dataAndTreeInSameFile: true, 624 }, 625 } 626 for _, tc := range testCases { 627 t.Run(tc.name, func(t *testing.T) { 628 var buf bytes.Buffer 629 _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, 0 /* verifyStart */, 0 /* verifySize */, &buf) 630 params.Children = append(params.Children, "abc") 631 if _, err := Verify(¶ms); errors.Is(err, nil) { 632 t.Errorf("Verification succeeded when expected to fail") 633 } 634 }) 635 } 636 } 637 638 func TestVerifyModifiedSymlink(t *testing.T) { 639 var buf bytes.Buffer 640 _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, false /* dataAndTreeInSameFile */, true /* isSymlink */, 0 /* verifyStart */, 0 /* verifySize */, &buf) 641 params.SymlinkTarget = "merkle_modified_test_link" 642 if _, err := Verify(¶ms); err == nil { 643 t.Errorf("Verification succeeded when expected to fail") 644 } 645 } 646 647 func TestModifyOutsideVerifyRange(t *testing.T) { 648 testCases := []struct { 649 name string 650 // The byte with index modifyByte is modified. 651 modifyByte int64 652 dataAndTreeInSameFile bool 653 }{ 654 { 655 name: "BeforeRangeSeparateFile", 656 modifyByte: 4*hostarch.PageSize - 1, 657 dataAndTreeInSameFile: false, 658 }, 659 { 660 name: "BeforeRangeSameFile", 661 modifyByte: 4*hostarch.PageSize - 1, 662 dataAndTreeInSameFile: true, 663 }, 664 { 665 name: "AfterRangeSeparateFile", 666 modifyByte: 5 * hostarch.PageSize, 667 dataAndTreeInSameFile: false, 668 }, 669 { 670 name: "AfterRangeSameFile", 671 modifyByte: 5 * hostarch.PageSize, 672 dataAndTreeInSameFile: true, 673 }, 674 } 675 for _, tc := range testCases { 676 t.Run(tc.name, func(t *testing.T) { 677 dataSize := int64(8 * hostarch.PageSize) 678 verifyStart := int64(4 * hostarch.PageSize) 679 verifySize := int64(hostarch.PageSize) 680 var buf bytes.Buffer 681 // Modified byte is outside verify range. Verify should succeed. 682 data, params := prepareVerify(t, dataSize, defaultHashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, verifyStart, verifySize, &buf) 683 // Flip a bit in data and checks Verify results. 684 data[tc.modifyByte] ^= 1 685 n, err := Verify(¶ms) 686 if !errors.Is(err, nil) { 687 t.Errorf("Verification failed when expected to succeed: %v", err) 688 } 689 if n != verifySize { 690 t.Errorf("Got Verify output size %d, want %d", n, verifySize) 691 } 692 if int64(buf.Len()) != verifySize { 693 t.Errorf("Got Verify output buf size %d, want %d,", buf.Len(), verifySize) 694 } 695 if !bytes.Equal(data[verifyStart:verifyStart+verifySize], buf.Bytes()) { 696 t.Errorf("Incorrect output buf from Verify") 697 } 698 }) 699 } 700 } 701 702 func TestModifyInsideVerifyRange(t *testing.T) { 703 testCases := []struct { 704 name string 705 verifyStart int64 706 verifySize int64 707 // The byte with index modifyByte is modified. 708 modifyByte int64 709 dataAndTreeInSameFile bool 710 }{ 711 // Test a block-aligned verify range. 712 // Modifying a byte in the verified range should cause verify 713 // to fail. 714 { 715 name: "BlockAlignedRangeSeparateFile", 716 verifyStart: 4 * hostarch.PageSize, 717 verifySize: hostarch.PageSize, 718 modifyByte: 4 * hostarch.PageSize, 719 dataAndTreeInSameFile: false, 720 }, 721 { 722 name: "BlockAlignedRangeSameFile", 723 verifyStart: 4 * hostarch.PageSize, 724 verifySize: hostarch.PageSize, 725 modifyByte: 4 * hostarch.PageSize, 726 dataAndTreeInSameFile: true, 727 }, 728 // The tests below use a non-block-aligned verify range. 729 // Modifying a byte at strat of verify range should cause 730 // verify to fail. 731 { 732 name: "ModifyStartSeparateFile", 733 verifyStart: 4*hostarch.PageSize + 123, 734 verifySize: 2 * hostarch.PageSize, 735 modifyByte: 4*hostarch.PageSize + 123, 736 dataAndTreeInSameFile: false, 737 }, 738 { 739 name: "ModifyStartSameFile", 740 verifyStart: 4*hostarch.PageSize + 123, 741 verifySize: 2 * hostarch.PageSize, 742 modifyByte: 4*hostarch.PageSize + 123, 743 dataAndTreeInSameFile: true, 744 }, 745 // Modifying a byte at the end of verify range should cause 746 // verify to fail. 747 { 748 name: "ModifyEndSeparateFile", 749 verifyStart: 4*hostarch.PageSize + 123, 750 verifySize: 2 * hostarch.PageSize, 751 modifyByte: 6*hostarch.PageSize + 123, 752 dataAndTreeInSameFile: false, 753 }, 754 { 755 name: "ModifyEndSameFile", 756 verifyStart: 4*hostarch.PageSize + 123, 757 verifySize: 2 * hostarch.PageSize, 758 modifyByte: 6*hostarch.PageSize + 123, 759 dataAndTreeInSameFile: true, 760 }, 761 // Modifying a byte in the middle verified block should cause 762 // verify to fail. 763 { 764 name: "ModifyMiddleSeparateFile", 765 verifyStart: 4*hostarch.PageSize + 123, 766 verifySize: 2 * hostarch.PageSize, 767 modifyByte: 5*hostarch.PageSize + 123, 768 dataAndTreeInSameFile: false, 769 }, 770 { 771 name: "ModifyMiddleSameFile", 772 verifyStart: 4*hostarch.PageSize + 123, 773 verifySize: 2 * hostarch.PageSize, 774 modifyByte: 5*hostarch.PageSize + 123, 775 dataAndTreeInSameFile: true, 776 }, 777 // Modifying a byte in the first block in the verified range 778 // should cause verify to fail, even the modified bit itself is 779 // out of verify range. 780 { 781 name: "ModifyFirstBlockSeparateFile", 782 verifyStart: 4*hostarch.PageSize + 123, 783 verifySize: 2 * hostarch.PageSize, 784 modifyByte: 4*hostarch.PageSize + 122, 785 dataAndTreeInSameFile: false, 786 }, 787 { 788 name: "ModifyFirstBlockSameFile", 789 verifyStart: 4*hostarch.PageSize + 123, 790 verifySize: 2 * hostarch.PageSize, 791 modifyByte: 4*hostarch.PageSize + 122, 792 dataAndTreeInSameFile: true, 793 }, 794 // Modifying a byte in the last block in the verified range 795 // should cause verify to fail, even the modified bit itself is 796 // out of verify range. 797 { 798 name: "ModifyLastBlockSeparateFile", 799 verifyStart: 4*hostarch.PageSize + 123, 800 verifySize: 2 * hostarch.PageSize, 801 modifyByte: 6*hostarch.PageSize + 124, 802 dataAndTreeInSameFile: false, 803 }, 804 { 805 name: "ModifyLastBlockSameFile", 806 verifyStart: 4*hostarch.PageSize + 123, 807 verifySize: 2 * hostarch.PageSize, 808 modifyByte: 6*hostarch.PageSize + 124, 809 dataAndTreeInSameFile: true, 810 }, 811 } 812 for _, tc := range testCases { 813 t.Run(tc.name, func(t *testing.T) { 814 dataSize := int64(8 * hostarch.PageSize) 815 var buf bytes.Buffer 816 data, params := prepareVerify(t, dataSize, defaultHashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, tc.verifyStart, tc.verifySize, &buf) 817 // Flip a bit in data and checks Verify results. 818 data[tc.modifyByte] ^= 1 819 if _, err := Verify(¶ms); errors.Is(err, nil) { 820 t.Errorf("Verification succeeded when expected to fail") 821 } 822 }) 823 } 824 } 825 826 func TestVerifyRandom(t *testing.T) { 827 testCases := []struct { 828 name string 829 hashAlgorithm int 830 dataAndTreeInSameFile bool 831 }{ 832 { 833 name: "SHA256SeparateFile", 834 hashAlgorithm: linux.FS_VERITY_HASH_ALG_SHA256, 835 dataAndTreeInSameFile: false, 836 }, 837 { 838 name: "SHA512SeparateFile", 839 hashAlgorithm: linux.FS_VERITY_HASH_ALG_SHA512, 840 dataAndTreeInSameFile: false, 841 }, 842 { 843 name: "SHA256SameFile", 844 hashAlgorithm: linux.FS_VERITY_HASH_ALG_SHA256, 845 dataAndTreeInSameFile: true, 846 }, 847 { 848 name: "SHA512SameFile", 849 hashAlgorithm: linux.FS_VERITY_HASH_ALG_SHA512, 850 dataAndTreeInSameFile: true, 851 }, 852 } 853 for _, tc := range testCases { 854 t.Run(tc.name, func(t *testing.T) { 855 rand.Seed(time.Now().UnixNano()) 856 // Use a random dataSize. Minimum size 2 so that we can pick a random 857 // portion from it. 858 dataSize := rand.Int63n(200*hostarch.PageSize) + 2 859 860 // Pick a random portion of data. 861 start := rand.Int63n(dataSize - 1) 862 size := rand.Int63n(dataSize) + 1 863 864 var buf bytes.Buffer 865 data, params := prepareVerify(t, dataSize, tc.hashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, start, size, &buf) 866 867 // Checks that the random portion of data from the original data is 868 // verified successfully. 869 n, err := Verify(¶ms) 870 if err != nil && err != io.EOF { 871 t.Errorf("Verification failed for correct data: %v", err) 872 } 873 if size > dataSize-start { 874 size = dataSize - start 875 } 876 if n != size { 877 t.Errorf("Got Verify output size %d, want %d", n, size) 878 } 879 if int64(buf.Len()) != size { 880 t.Errorf("Got Verify output buf size %d, want %d", buf.Len(), size) 881 } 882 if !bytes.Equal(data[start:start+size], buf.Bytes()) { 883 t.Errorf("Incorrect output buf from Verify") 884 } 885 886 // Verify that modified metadata should fail verification. 887 buf.Reset() 888 params.Name = defaultName + "abc" 889 if _, err := Verify(¶ms); errors.Is(err, nil) { 890 t.Error("Verify succeeded for modified metadata, expect failure") 891 } 892 893 // Flip a random bit in randPortion, and check that verification fails. 894 buf.Reset() 895 randBytePos := rand.Int63n(size) 896 data[start+randBytePos] ^= 1 897 params.File = bytes.NewReader(data) 898 params.Name = defaultName 899 900 if _, err := Verify(¶ms); errors.Is(err, nil) { 901 t.Error("Verification succeeded for modified data, expect failure") 902 } 903 }) 904 } 905 }