github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fsimpl/verity/verity_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 verity 16 17 import ( 18 "fmt" 19 "io" 20 "math/rand" 21 "strconv" 22 "testing" 23 "time" 24 25 "github.com/SagerNet/gvisor/pkg/abi/linux" 26 "github.com/SagerNet/gvisor/pkg/context" 27 "github.com/SagerNet/gvisor/pkg/errors/linuxerr" 28 "github.com/SagerNet/gvisor/pkg/fspath" 29 "github.com/SagerNet/gvisor/pkg/sentry/arch" 30 "github.com/SagerNet/gvisor/pkg/sentry/fsimpl/testutil" 31 "github.com/SagerNet/gvisor/pkg/sentry/fsimpl/tmpfs" 32 "github.com/SagerNet/gvisor/pkg/sentry/kernel" 33 "github.com/SagerNet/gvisor/pkg/sentry/kernel/auth" 34 "github.com/SagerNet/gvisor/pkg/sentry/vfs" 35 "github.com/SagerNet/gvisor/pkg/usermem" 36 ) 37 38 const ( 39 // rootMerkleFilename is the name of the root Merkle tree file. 40 rootMerkleFilename = "root.verity" 41 // maxDataSize is the maximum data size of a test file. 42 maxDataSize = 100000 43 ) 44 45 var hashAlgs = []HashAlgorithm{SHA256, SHA512} 46 47 func dentryFromVD(t *testing.T, vd vfs.VirtualDentry) *dentry { 48 t.Helper() 49 d, ok := vd.Dentry().Impl().(*dentry) 50 if !ok { 51 t.Fatalf("can't assert %T as a *dentry", vd) 52 } 53 return d 54 } 55 56 // dentryFromFD returns the dentry corresponding to fd. 57 func dentryFromFD(t *testing.T, fd *vfs.FileDescription) *dentry { 58 t.Helper() 59 f, ok := fd.Impl().(*fileDescription) 60 if !ok { 61 t.Fatalf("can't assert %T as a *fileDescription", fd) 62 } 63 return f.d 64 } 65 66 // newVerityRoot creates a new verity mount, and returns the root. The 67 // underlying file system is tmpfs. If the error is not nil, then cleanup 68 // should be called when the root is no longer needed. 69 func newVerityRoot(t *testing.T, hashAlg HashAlgorithm) (*vfs.VirtualFilesystem, vfs.VirtualDentry, context.Context, error) { 70 t.Helper() 71 k, err := testutil.Boot() 72 if err != nil { 73 t.Fatalf("testutil.Boot: %v", err) 74 } 75 76 ctx := k.SupervisorContext() 77 78 rand.Seed(time.Now().UnixNano()) 79 vfsObj := &vfs.VirtualFilesystem{} 80 if err := vfsObj.Init(ctx); err != nil { 81 return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("VFS init: %v", err) 82 } 83 84 vfsObj.MustRegisterFilesystemType("verity", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ 85 AllowUserMount: true, 86 }) 87 88 vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ 89 AllowUserMount: true, 90 }) 91 92 data := "root_name=" + rootMerkleFilename 93 mntns, err := vfsObj.NewMountNamespace(ctx, auth.CredentialsFromContext(ctx), "", "verity", &vfs.MountOptions{ 94 GetFilesystemOptions: vfs.GetFilesystemOptions{ 95 Data: data, 96 InternalData: InternalFilesystemOptions{ 97 LowerName: "tmpfs", 98 Alg: hashAlg, 99 AllowRuntimeEnable: true, 100 Action: ErrorOnViolation, 101 }, 102 }, 103 }) 104 if err != nil { 105 return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("NewMountNamespace: %v", err) 106 } 107 root := mntns.Root() 108 root.IncRef() 109 110 // Use lowerRoot in the task as we modify the lower file system 111 // directly in many tests. 112 lowerRoot := root.Dentry().Impl().(*dentry).lowerVD 113 tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) 114 task, err := testutil.CreateTask(ctx, "name", tc, mntns, lowerRoot, lowerRoot) 115 if err != nil { 116 t.Fatalf("testutil.CreateTask: %v", err) 117 } 118 119 t.Cleanup(func() { 120 root.DecRef(ctx) 121 mntns.DecRef(ctx) 122 }) 123 return vfsObj, root, task.AsyncContext(), nil 124 } 125 126 // openVerityAt opens a verity file. 127 // 128 // TODO(chongc): release reference from opening the file when done. 129 func openVerityAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, vd vfs.VirtualDentry, path string, flags uint32, mode linux.FileMode) (*vfs.FileDescription, error) { 130 return vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ 131 Root: vd, 132 Start: vd, 133 Path: fspath.Parse(path), 134 }, &vfs.OpenOptions{ 135 Flags: flags, 136 Mode: mode, 137 }) 138 } 139 140 // openLowerAt opens the file in the underlying file system. 141 // 142 // TODO(chongc): release reference from opening the file when done. 143 func (d *dentry) openLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, path string, flags uint32, mode linux.FileMode) (*vfs.FileDescription, error) { 144 return vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ 145 Root: d.lowerVD, 146 Start: d.lowerVD, 147 Path: fspath.Parse(path), 148 }, &vfs.OpenOptions{ 149 Flags: flags, 150 Mode: mode, 151 }) 152 } 153 154 // openLowerMerkleAt opens the Merkle file in the underlying file system. 155 // 156 // TODO(chongc): release reference from opening the file when done. 157 func (d *dentry) openLowerMerkleAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, flags uint32, mode linux.FileMode) (*vfs.FileDescription, error) { 158 return vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ 159 Root: d.lowerMerkleVD, 160 Start: d.lowerMerkleVD, 161 }, &vfs.OpenOptions{ 162 Flags: flags, 163 Mode: mode, 164 }) 165 } 166 167 // mkdirLowerAt creates a directory in the underlying file system. 168 func (d *dentry) mkdirLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, path string, mode linux.FileMode) error { 169 return vfsObj.MkdirAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ 170 Root: d.lowerVD, 171 Start: d.lowerVD, 172 Path: fspath.Parse(path), 173 }, &vfs.MkdirOptions{ 174 Mode: mode, 175 }) 176 } 177 178 // unlinkLowerAt deletes the file in the underlying file system. 179 func (d *dentry) unlinkLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, path string) error { 180 return vfsObj.UnlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ 181 Root: d.lowerVD, 182 Start: d.lowerVD, 183 Path: fspath.Parse(path), 184 }) 185 } 186 187 // unlinkLowerMerkleAt deletes the Merkle file in the underlying file system. 188 func (d *dentry) unlinkLowerMerkleAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, path string) error { 189 return vfsObj.UnlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ 190 Root: d.lowerVD, 191 Start: d.lowerVD, 192 Path: fspath.Parse(merklePrefix + path), 193 }) 194 } 195 196 // renameLowerAt renames file name to newName in the underlying file system. 197 func (d *dentry) renameLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, name string, newName string) error { 198 return vfsObj.RenameAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ 199 Root: d.lowerVD, 200 Start: d.lowerVD, 201 Path: fspath.Parse(name), 202 }, &vfs.PathOperation{ 203 Root: d.lowerVD, 204 Start: d.lowerVD, 205 Path: fspath.Parse(newName), 206 }, &vfs.RenameOptions{}) 207 } 208 209 // renameLowerMerkleAt renames Merkle file name to newName in the underlying 210 // file system. 211 func (d *dentry) renameLowerMerkleAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, name string, newName string) error { 212 return vfsObj.RenameAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ 213 Root: d.lowerVD, 214 Start: d.lowerVD, 215 Path: fspath.Parse(merklePrefix + name), 216 }, &vfs.PathOperation{ 217 Root: d.lowerVD, 218 Start: d.lowerVD, 219 Path: fspath.Parse(merklePrefix + newName), 220 }, &vfs.RenameOptions{}) 221 } 222 223 // symlinkLowerAt creates a symbolic link at symlink referring to the given target 224 // in the underlying filesystem. 225 func (d *dentry) symlinkLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, target, symlink string) error { 226 return vfsObj.SymlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ 227 Root: d.lowerVD, 228 Start: d.lowerVD, 229 Path: fspath.Parse(symlink), 230 }, target) 231 } 232 233 // newFileFD creates a new file in the verity mount, and returns the FD. The FD 234 // points to a file that has random data generated. 235 func newFileFD(ctx context.Context, t *testing.T, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, filePath string, mode linux.FileMode) (*vfs.FileDescription, int, error) { 236 // Create the file in the underlying file system. 237 lowerFD, err := dentryFromVD(t, root).openLowerAt(ctx, vfsObj, filePath, linux.O_RDWR|linux.O_CREAT|linux.O_EXCL, linux.ModeRegular|mode) 238 if err != nil { 239 return nil, 0, err 240 } 241 242 // Generate random data to be written to the file. 243 dataSize := rand.Intn(maxDataSize) + 1 244 data := make([]byte, dataSize) 245 rand.Read(data) 246 247 // Write directly to the underlying FD, since verity FD is read-only. 248 n, err := lowerFD.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{}) 249 if err != nil { 250 return nil, 0, err 251 } 252 253 if n != int64(len(data)) { 254 return nil, 0, fmt.Errorf("lowerFD.Write got write length %d, want %d", n, len(data)) 255 } 256 257 lowerFD.DecRef(ctx) 258 259 // Now open the verity file descriptor. 260 fd, err := openVerityAt(ctx, vfsObj, root, filePath, linux.O_RDONLY, mode) 261 return fd, dataSize, err 262 } 263 264 // newDirFD creates a new directory in the verity mount, and returns the FD. 265 func newDirFD(ctx context.Context, t *testing.T, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, dirPath string, mode linux.FileMode) (*vfs.FileDescription, error) { 266 // Create the directory in the underlying file system. 267 if err := dentryFromVD(t, root).mkdirLowerAt(ctx, vfsObj, dirPath, linux.ModeRegular|mode); err != nil { 268 return nil, err 269 } 270 if _, err := dentryFromVD(t, root).openLowerAt(ctx, vfsObj, dirPath, linux.O_RDONLY|linux.O_DIRECTORY, linux.ModeRegular|mode); err != nil { 271 return nil, err 272 } 273 return openVerityAt(ctx, vfsObj, root, dirPath, linux.O_RDONLY|linux.O_DIRECTORY, mode) 274 } 275 276 // newEmptyFileFD creates a new empty file in the verity mount, and returns the FD. 277 func newEmptyFileFD(ctx context.Context, t *testing.T, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, filePath string, mode linux.FileMode) (*vfs.FileDescription, error) { 278 // Create the file in the underlying file system. 279 _, err := dentryFromVD(t, root).openLowerAt(ctx, vfsObj, filePath, linux.O_RDWR|linux.O_CREAT|linux.O_EXCL, linux.ModeRegular|mode) 280 if err != nil { 281 return nil, err 282 } 283 // Now open the verity file descriptor. 284 fd, err := openVerityAt(ctx, vfsObj, root, filePath, linux.O_RDONLY, mode) 285 return fd, err 286 } 287 288 // flipRandomBit randomly flips a bit in the file represented by fd. 289 func flipRandomBit(ctx context.Context, fd *vfs.FileDescription, size int) error { 290 randomPos := int64(rand.Intn(size)) 291 byteToModify := make([]byte, 1) 292 if _, err := fd.PRead(ctx, usermem.BytesIOSequence(byteToModify), randomPos, vfs.ReadOptions{}); err != nil { 293 return fmt.Errorf("lowerFD.PRead: %v", err) 294 } 295 byteToModify[0] ^= 1 296 if _, err := fd.PWrite(ctx, usermem.BytesIOSequence(byteToModify), randomPos, vfs.WriteOptions{}); err != nil { 297 return fmt.Errorf("lowerFD.PWrite: %v", err) 298 } 299 return nil 300 } 301 302 func enableVerity(ctx context.Context, t *testing.T, fd *vfs.FileDescription) { 303 t.Helper() 304 var args arch.SyscallArguments 305 args[1] = arch.SyscallArgument{Value: linux.FS_IOC_ENABLE_VERITY} 306 if _, err := fd.Ioctl(ctx, nil /* uio */, args); err != nil { 307 t.Fatalf("enable verity: %v", err) 308 } 309 } 310 311 // TestOpen ensures that when a file is created, the corresponding Merkle tree 312 // file and the root Merkle tree file exist. 313 func TestOpen(t *testing.T) { 314 for _, alg := range hashAlgs { 315 vfsObj, root, ctx, err := newVerityRoot(t, alg) 316 if err != nil { 317 t.Fatalf("newVerityRoot: %v", err) 318 } 319 320 filename := "verity-test-file" 321 fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644) 322 if err != nil { 323 t.Fatalf("newFileFD: %v", err) 324 } 325 326 // Ensure that the corresponding Merkle tree file is created. 327 if _, err = dentryFromFD(t, fd).openLowerMerkleAt(ctx, vfsObj, linux.O_RDONLY, linux.ModeRegular); err != nil { 328 t.Errorf("OpenAt Merkle tree file %s: %v", merklePrefix+filename, err) 329 } 330 331 // Ensure the root merkle tree file is created. 332 if _, err = dentryFromVD(t, root).openLowerMerkleAt(ctx, vfsObj, linux.O_RDONLY, linux.ModeRegular); err != nil { 333 t.Errorf("OpenAt root Merkle tree file %s: %v", merklePrefix+rootMerkleFilename, err) 334 } 335 } 336 } 337 338 // TestPReadUnmodifiedFileSucceeds ensures that pread from an untouched verity 339 // file succeeds after enabling verity for it. 340 func TestPReadUnmodifiedFileSucceeds(t *testing.T) { 341 for _, alg := range hashAlgs { 342 vfsObj, root, ctx, err := newVerityRoot(t, alg) 343 if err != nil { 344 t.Fatalf("newVerityRoot: %v", err) 345 } 346 347 filename := "verity-test-file" 348 fd, size, err := newFileFD(ctx, t, vfsObj, root, filename, 0644) 349 if err != nil { 350 t.Fatalf("newFileFD: %v", err) 351 } 352 353 // Enable verity on the file and confirm a normal read succeeds. 354 enableVerity(ctx, t, fd) 355 356 buf := make([]byte, size) 357 n, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0 /* offset */, vfs.ReadOptions{}) 358 if err != nil && err != io.EOF { 359 t.Fatalf("fd.PRead: %v", err) 360 } 361 362 if n != int64(size) { 363 t.Errorf("fd.PRead got read length %d, want %d", n, size) 364 } 365 } 366 } 367 368 // TestReadUnmodifiedFileSucceeds ensures that read from an untouched verity 369 // file succeeds after enabling verity for it. 370 func TestReadUnmodifiedFileSucceeds(t *testing.T) { 371 for _, alg := range hashAlgs { 372 vfsObj, root, ctx, err := newVerityRoot(t, alg) 373 if err != nil { 374 t.Fatalf("newVerityRoot: %v", err) 375 } 376 377 filename := "verity-test-file" 378 fd, size, err := newFileFD(ctx, t, vfsObj, root, filename, 0644) 379 if err != nil { 380 t.Fatalf("newFileFD: %v", err) 381 } 382 383 // Enable verity on the file and confirm a normal read succeeds. 384 enableVerity(ctx, t, fd) 385 386 buf := make([]byte, size) 387 n, err := fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{}) 388 if err != nil && err != io.EOF { 389 t.Fatalf("fd.Read: %v", err) 390 } 391 392 if n != int64(size) { 393 t.Errorf("fd.PRead got read length %d, want %d", n, size) 394 } 395 } 396 } 397 398 // TestReadUnmodifiedEmptyFileSucceeds ensures that read from an untouched empty verity 399 // file succeeds after enabling verity for it. 400 func TestReadUnmodifiedEmptyFileSucceeds(t *testing.T) { 401 for _, alg := range hashAlgs { 402 vfsObj, root, ctx, err := newVerityRoot(t, alg) 403 if err != nil { 404 t.Fatalf("newVerityRoot: %v", err) 405 } 406 407 filename := "verity-test-empty-file" 408 fd, err := newEmptyFileFD(ctx, t, vfsObj, root, filename, 0644) 409 if err != nil { 410 t.Fatalf("newEmptyFileFD: %v", err) 411 } 412 413 // Enable verity on the file and confirm a normal read succeeds. 414 enableVerity(ctx, t, fd) 415 416 var buf []byte 417 n, err := fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{}) 418 if err != nil && err != io.EOF { 419 t.Fatalf("fd.Read: %v", err) 420 } 421 422 if n != 0 { 423 t.Errorf("fd.Read got read length %d, expected 0", n) 424 } 425 } 426 } 427 428 // TestReopenUnmodifiedFileSucceeds ensures that reopen an untouched verity file 429 // succeeds after enabling verity for it. 430 func TestReopenUnmodifiedFileSucceeds(t *testing.T) { 431 for _, alg := range hashAlgs { 432 vfsObj, root, ctx, err := newVerityRoot(t, alg) 433 if err != nil { 434 t.Fatalf("newVerityRoot: %v", err) 435 } 436 437 filename := "verity-test-file" 438 fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644) 439 if err != nil { 440 t.Fatalf("newFileFD: %v", err) 441 } 442 443 // Enable verity on the file and confirms a normal read succeeds. 444 enableVerity(ctx, t, fd) 445 446 // Ensure reopening the verity enabled file succeeds. 447 if _, err = openVerityAt(ctx, vfsObj, root, filename, linux.O_RDONLY, linux.ModeRegular); err != nil { 448 t.Errorf("reopen enabled file failed: %v", err) 449 } 450 } 451 } 452 453 // TestOpenNonexistentFile ensures that opening a nonexistent file does not 454 // trigger verification failure, even if the parent directory is verified. 455 func TestOpenNonexistentFile(t *testing.T) { 456 vfsObj, root, ctx, err := newVerityRoot(t, SHA256) 457 if err != nil { 458 t.Fatalf("newVerityRoot: %v", err) 459 } 460 461 filename := "verity-test-file" 462 fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644) 463 if err != nil { 464 t.Fatalf("newFileFD: %v", err) 465 } 466 467 // Enable verity on the file and confirms a normal read succeeds. 468 enableVerity(ctx, t, fd) 469 470 // Enable verity on the parent directory. 471 parentFD, err := openVerityAt(ctx, vfsObj, root, "", linux.O_RDONLY, linux.ModeRegular) 472 if err != nil { 473 t.Fatalf("OpenAt: %v", err) 474 } 475 enableVerity(ctx, t, parentFD) 476 477 // Ensure open an unexpected file in the parent directory fails with 478 // ENOENT rather than verification failure. 479 if _, err = openVerityAt(ctx, vfsObj, root, filename+"abc", linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.ENOENT, err) { 480 t.Errorf("OpenAt unexpected error: %v", err) 481 } 482 } 483 484 // TestPReadModifiedFileFails ensures that read from a modified verity file 485 // fails. 486 func TestPReadModifiedFileFails(t *testing.T) { 487 for _, alg := range hashAlgs { 488 vfsObj, root, ctx, err := newVerityRoot(t, alg) 489 if err != nil { 490 t.Fatalf("newVerityRoot: %v", err) 491 } 492 493 filename := "verity-test-file" 494 fd, size, err := newFileFD(ctx, t, vfsObj, root, filename, 0644) 495 if err != nil { 496 t.Fatalf("newFileFD: %v", err) 497 } 498 499 // Enable verity on the file. 500 enableVerity(ctx, t, fd) 501 502 // Open a new lowerFD that's read/writable. 503 lowerFD, err := dentryFromFD(t, fd).openLowerAt(ctx, vfsObj, "", linux.O_RDWR, linux.ModeRegular) 504 if err != nil { 505 t.Fatalf("OpenAt: %v", err) 506 } 507 508 if err := flipRandomBit(ctx, lowerFD, size); err != nil { 509 t.Fatalf("flipRandomBit: %v", err) 510 } 511 512 // Confirm that read from the modified file fails. 513 buf := make([]byte, size) 514 if _, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0 /* offset */, vfs.ReadOptions{}); err == nil { 515 t.Fatalf("fd.PRead succeeded, expected failure") 516 } 517 } 518 } 519 520 // TestReadModifiedFileFails ensures that read from a modified verity file 521 // fails. 522 func TestReadModifiedFileFails(t *testing.T) { 523 for _, alg := range hashAlgs { 524 vfsObj, root, ctx, err := newVerityRoot(t, alg) 525 if err != nil { 526 t.Fatalf("newVerityRoot: %v", err) 527 } 528 529 filename := "verity-test-file" 530 fd, size, err := newFileFD(ctx, t, vfsObj, root, filename, 0644) 531 if err != nil { 532 t.Fatalf("newFileFD: %v", err) 533 } 534 535 // Enable verity on the file. 536 enableVerity(ctx, t, fd) 537 538 // Open a new lowerFD that's read/writable. 539 lowerFD, err := dentryFromFD(t, fd).openLowerAt(ctx, vfsObj, "", linux.O_RDWR, linux.ModeRegular) 540 if err != nil { 541 t.Fatalf("OpenAt: %v", err) 542 } 543 544 if err := flipRandomBit(ctx, lowerFD, size); err != nil { 545 t.Fatalf("flipRandomBit: %v", err) 546 } 547 548 // Confirm that read from the modified file fails. 549 buf := make([]byte, size) 550 if _, err := fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{}); err == nil { 551 t.Fatalf("fd.Read succeeded, expected failure") 552 } 553 } 554 } 555 556 // TestModifiedMerkleFails ensures that read from a verity file fails if the 557 // corresponding Merkle tree file is modified. 558 func TestModifiedMerkleFails(t *testing.T) { 559 for _, alg := range hashAlgs { 560 vfsObj, root, ctx, err := newVerityRoot(t, alg) 561 if err != nil { 562 t.Fatalf("newVerityRoot: %v", err) 563 } 564 565 filename := "verity-test-file" 566 fd, size, err := newFileFD(ctx, t, vfsObj, root, filename, 0644) 567 if err != nil { 568 t.Fatalf("newFileFD: %v", err) 569 } 570 571 // Enable verity on the file. 572 enableVerity(ctx, t, fd) 573 574 // Open a new lowerMerkleFD that's read/writable. 575 lowerMerkleFD, err := dentryFromFD(t, fd).openLowerMerkleAt(ctx, vfsObj, linux.O_RDWR, linux.ModeRegular) 576 if err != nil { 577 t.Fatalf("OpenAt: %v", err) 578 } 579 580 // Flip a random bit in the Merkle tree file. 581 stat, err := lowerMerkleFD.Stat(ctx, vfs.StatOptions{}) 582 if err != nil { 583 t.Errorf("lowerMerkleFD.Stat: %v", err) 584 } 585 586 if err := flipRandomBit(ctx, lowerMerkleFD, int(stat.Size)); err != nil { 587 t.Fatalf("flipRandomBit: %v", err) 588 } 589 590 // Confirm that read from a file with modified Merkle tree fails. 591 buf := make([]byte, size) 592 if _, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0 /* offset */, vfs.ReadOptions{}); err == nil { 593 t.Fatalf("fd.PRead succeeded with modified Merkle file") 594 } 595 } 596 } 597 598 // TestModifiedParentMerkleFails ensures that open a verity enabled file in a 599 // verity enabled directory fails if the hashes related to the target file in 600 // the parent Merkle tree file is modified. 601 func TestModifiedParentMerkleFails(t *testing.T) { 602 for _, alg := range hashAlgs { 603 vfsObj, root, ctx, err := newVerityRoot(t, alg) 604 if err != nil { 605 t.Fatalf("newVerityRoot: %v", err) 606 } 607 608 filename := "verity-test-file" 609 fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644) 610 if err != nil { 611 t.Fatalf("newFileFD: %v", err) 612 } 613 614 // Enable verity on the file. 615 enableVerity(ctx, t, fd) 616 617 // Enable verity on the parent directory. 618 parentFD, err := openVerityAt(ctx, vfsObj, root, "", linux.O_RDONLY, linux.ModeRegular) 619 if err != nil { 620 t.Fatalf("OpenAt: %v", err) 621 } 622 enableVerity(ctx, t, parentFD) 623 624 // Open a new lowerMerkleFD that's read/writable. 625 parentLowerMerkleFD, err := dentryFromFD(t, fd).parent.openLowerMerkleAt(ctx, vfsObj, linux.O_RDWR, linux.ModeRegular) 626 if err != nil { 627 t.Fatalf("OpenAt: %v", err) 628 } 629 630 // Flip a random bit in the parent Merkle tree file. 631 // This parent directory contains only one child, so any random 632 // modification in the parent Merkle tree should cause verification 633 // failure when opening the child file. 634 sizeString, err := parentLowerMerkleFD.GetXattr(ctx, &vfs.GetXattrOptions{ 635 Name: childrenOffsetXattr, 636 Size: sizeOfStringInt32, 637 }) 638 if err != nil { 639 t.Fatalf("parentLowerMerkleFD.GetXattr: %v", err) 640 } 641 parentMerkleSize, err := strconv.Atoi(sizeString) 642 if err != nil { 643 t.Fatalf("Failed convert size to int: %v", err) 644 } 645 if err := flipRandomBit(ctx, parentLowerMerkleFD, parentMerkleSize); err != nil { 646 t.Fatalf("flipRandomBit: %v", err) 647 } 648 649 parentLowerMerkleFD.DecRef(ctx) 650 651 // Ensure reopening the verity enabled file fails. 652 if _, err = openVerityAt(ctx, vfsObj, root, filename, linux.O_RDONLY, linux.ModeRegular); err == nil { 653 t.Errorf("OpenAt file with modified parent Merkle succeeded") 654 } 655 } 656 } 657 658 // TestUnmodifiedStatSucceeds ensures that stat of an untouched verity file 659 // succeeds after enabling verity for it. 660 func TestUnmodifiedStatSucceeds(t *testing.T) { 661 for _, alg := range hashAlgs { 662 vfsObj, root, ctx, err := newVerityRoot(t, alg) 663 if err != nil { 664 t.Fatalf("newVerityRoot: %v", err) 665 } 666 667 filename := "verity-test-file" 668 fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644) 669 if err != nil { 670 t.Fatalf("newFileFD: %v", err) 671 } 672 673 // Enable verity on the file and confirm that stat succeeds. 674 enableVerity(ctx, t, fd) 675 if _, err := fd.Stat(ctx, vfs.StatOptions{}); err != nil { 676 t.Errorf("fd.Stat: %v", err) 677 } 678 } 679 } 680 681 // TestModifiedStatFails checks that getting stat for a file with modified stat 682 // should fail. 683 func TestModifiedStatFails(t *testing.T) { 684 for _, alg := range hashAlgs { 685 vfsObj, root, ctx, err := newVerityRoot(t, alg) 686 if err != nil { 687 t.Fatalf("newVerityRoot: %v", err) 688 } 689 690 filename := "verity-test-file" 691 fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644) 692 if err != nil { 693 t.Fatalf("newFileFD: %v", err) 694 } 695 696 // Enable verity on the file. 697 enableVerity(ctx, t, fd) 698 699 lowerFD := fd.Impl().(*fileDescription).lowerFD 700 // Change the stat of the underlying file, and check that stat fails. 701 if err := lowerFD.SetStat(ctx, vfs.SetStatOptions{ 702 Stat: linux.Statx{ 703 Mask: uint32(linux.STATX_MODE), 704 Mode: 0777, 705 }, 706 }); err != nil { 707 t.Fatalf("lowerFD.SetStat: %v", err) 708 } 709 710 if _, err := fd.Stat(ctx, vfs.StatOptions{}); err == nil { 711 t.Errorf("fd.Stat succeeded when it should fail") 712 } 713 } 714 } 715 716 // TestOpenDeletedFileFails ensures that opening a deleted verity enabled file 717 // and/or the corresponding Merkle tree file fails with the verity error. 718 func TestOpenDeletedFileFails(t *testing.T) { 719 testCases := []struct { 720 name string 721 // The original file is removed if changeFile is true. 722 changeFile bool 723 // The Merkle tree file is removed if changeMerkleFile is true. 724 changeMerkleFile bool 725 }{ 726 { 727 name: "FileOnly", 728 changeFile: true, 729 changeMerkleFile: false, 730 }, 731 { 732 name: "MerkleOnly", 733 changeFile: false, 734 changeMerkleFile: true, 735 }, 736 { 737 name: "FileAndMerkle", 738 changeFile: true, 739 changeMerkleFile: true, 740 }, 741 } 742 for _, tc := range testCases { 743 t.Run(tc.name, func(t *testing.T) { 744 vfsObj, root, ctx, err := newVerityRoot(t, SHA256) 745 if err != nil { 746 t.Fatalf("newVerityRoot: %v", err) 747 } 748 749 filename := "verity-test-file" 750 fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644) 751 if err != nil { 752 t.Fatalf("newFileFD: %v", err) 753 } 754 755 // Enable verity on the file. 756 enableVerity(ctx, t, fd) 757 758 if tc.changeFile { 759 if err := dentryFromVD(t, root).unlinkLowerAt(ctx, vfsObj, filename); err != nil { 760 t.Fatalf("UnlinkAt: %v", err) 761 } 762 } 763 if tc.changeMerkleFile { 764 if err := dentryFromVD(t, root).unlinkLowerMerkleAt(ctx, vfsObj, filename); err != nil { 765 t.Fatalf("UnlinkAt: %v", err) 766 } 767 } 768 769 // Ensure reopening the verity enabled file fails. 770 if _, err = openVerityAt(ctx, vfsObj, root, filename, linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.EIO, err) { 771 t.Errorf("got OpenAt error: %v, expected EIO", err) 772 } 773 }) 774 } 775 } 776 777 // TestOpenRenamedFileFails ensures that opening a renamed verity enabled file 778 // and/or the corresponding Merkle tree file fails with the verity error. 779 func TestOpenRenamedFileFails(t *testing.T) { 780 testCases := []struct { 781 name string 782 // The original file is renamed if changeFile is true. 783 changeFile bool 784 // The Merkle tree file is renamed if changeMerkleFile is true. 785 changeMerkleFile bool 786 }{ 787 { 788 name: "FileOnly", 789 changeFile: true, 790 changeMerkleFile: false, 791 }, 792 { 793 name: "MerkleOnly", 794 changeFile: false, 795 changeMerkleFile: true, 796 }, 797 { 798 name: "FileAndMerkle", 799 changeFile: true, 800 changeMerkleFile: true, 801 }, 802 } 803 for _, tc := range testCases { 804 t.Run(tc.name, func(t *testing.T) { 805 vfsObj, root, ctx, err := newVerityRoot(t, SHA256) 806 if err != nil { 807 t.Fatalf("newVerityRoot: %v", err) 808 } 809 810 filename := "verity-test-file" 811 fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644) 812 if err != nil { 813 t.Fatalf("newFileFD: %v", err) 814 } 815 816 // Enable verity on the file. 817 enableVerity(ctx, t, fd) 818 819 newFilename := "renamed-test-file" 820 if tc.changeFile { 821 if err := dentryFromVD(t, root).renameLowerAt(ctx, vfsObj, filename, newFilename); err != nil { 822 t.Fatalf("RenameAt: %v", err) 823 } 824 } 825 if tc.changeMerkleFile { 826 if err := dentryFromVD(t, root).renameLowerMerkleAt(ctx, vfsObj, filename, newFilename); err != nil { 827 t.Fatalf("UnlinkAt: %v", err) 828 } 829 } 830 831 // Ensure reopening the verity enabled file fails. 832 if _, err = openVerityAt(ctx, vfsObj, root, filename, linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.EIO, err) { 833 t.Errorf("got OpenAt error: %v, expected EIO", err) 834 } 835 }) 836 } 837 } 838 839 // TestUnmodifiedSymlinkFileReadSucceeds ensures that readlink() for an 840 // unmodified verity enabled symlink succeeds. 841 func TestUnmodifiedSymlinkFileReadSucceeds(t *testing.T) { 842 testCases := []struct { 843 name string 844 // The symlink target is a directory. 845 hasDirectoryTarget bool 846 // The symlink target is a directory and contains a regular file which will be 847 // used to test walking a symlink. 848 testWalk bool 849 }{ 850 { 851 name: "RegularFileTarget", 852 hasDirectoryTarget: false, 853 testWalk: false, 854 }, 855 { 856 name: "DirectoryTarget", 857 hasDirectoryTarget: true, 858 testWalk: false, 859 }, 860 { 861 name: "RegularFileInSymlinkDirectory", 862 hasDirectoryTarget: true, 863 testWalk: true, 864 }, 865 } 866 for _, tc := range testCases { 867 t.Run(tc.name, func(t *testing.T) { 868 if tc.testWalk && !tc.hasDirectoryTarget { 869 t.Fatalf("Invalid test case: hasDirectoryTarget can't be false when testing symlink walk") 870 } 871 872 vfsObj, root, ctx, err := newVerityRoot(t, SHA256) 873 if err != nil { 874 t.Fatalf("newVerityRoot: %v", err) 875 } 876 877 var target string 878 if tc.hasDirectoryTarget { 879 target = "verity-test-dir" 880 if _, err := newDirFD(ctx, t, vfsObj, root, target, 0644); err != nil { 881 t.Fatalf("newDirFD: %v", err) 882 } 883 } else { 884 target = "verity-test-file" 885 if _, _, err := newFileFD(ctx, t, vfsObj, root, target, 0644); err != nil { 886 t.Fatalf("newFileFD: %v", err) 887 } 888 } 889 890 if tc.testWalk { 891 fileInTargetDirectory := target + "/" + "verity-test-file" 892 if _, _, err := newFileFD(ctx, t, vfsObj, root, fileInTargetDirectory, 0644); err != nil { 893 t.Fatalf("newFileFD: %v", err) 894 } 895 } 896 897 symlink := "verity-test-symlink" 898 if err := dentryFromVD(t, root).symlinkLowerAt(ctx, vfsObj, target, symlink); err != nil { 899 t.Fatalf("SymlinkAt: %v", err) 900 } 901 902 fd, err := openVerityAt(ctx, vfsObj, root, symlink, linux.O_PATH|linux.O_NOFOLLOW, linux.ModeRegular) 903 904 if err != nil { 905 t.Fatalf("openVerityAt symlink: %v", err) 906 } 907 908 enableVerity(ctx, t, fd) 909 910 if _, err := vfsObj.ReadlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ 911 Root: root, 912 Start: root, 913 Path: fspath.Parse(symlink), 914 }); err != nil { 915 t.Fatalf("ReadlinkAt: %v", err) 916 } 917 918 if tc.testWalk { 919 fileInSymlinkDirectory := symlink + "/verity-test-file" 920 // Ensure opening the verity enabled file in the symlink directory succeeds. 921 if _, err := openVerityAt(ctx, vfsObj, root, fileInSymlinkDirectory, linux.O_RDONLY, linux.ModeRegular); err != nil { 922 t.Errorf("open enabled file failed: %v", err) 923 } 924 } 925 }) 926 } 927 } 928 929 // TestDeletedSymlinkFileReadFails ensures that reading value of a deleted verity enabled 930 // symlink fails. 931 func TestDeletedSymlinkFileReadFails(t *testing.T) { 932 testCases := []struct { 933 name string 934 // The original symlink is unlinked if deleteLink is true. 935 deleteLink bool 936 // The Merkle tree file is renamed if deleteMerkleFile is true. 937 deleteMerkleFile bool 938 // The symlink target is a directory. 939 hasDirectoryTarget bool 940 // The symlink target is a directory and contains a regular file which will be 941 // used to test walking a symlink. 942 testWalk bool 943 }{ 944 { 945 name: "DeleteLinkRegularFile", 946 deleteLink: true, 947 deleteMerkleFile: false, 948 hasDirectoryTarget: false, 949 testWalk: false, 950 }, 951 { 952 name: "DeleteMerkleRegFile", 953 deleteLink: false, 954 deleteMerkleFile: true, 955 hasDirectoryTarget: false, 956 testWalk: false, 957 }, 958 { 959 name: "DeleteLinkAndMerkleRegFile", 960 deleteLink: true, 961 deleteMerkleFile: true, 962 hasDirectoryTarget: false, 963 testWalk: false, 964 }, 965 { 966 name: "DeleteLinkDirectory", 967 deleteLink: true, 968 deleteMerkleFile: false, 969 hasDirectoryTarget: true, 970 testWalk: false, 971 }, 972 { 973 name: "DeleteMerkleDirectory", 974 deleteLink: false, 975 deleteMerkleFile: true, 976 hasDirectoryTarget: true, 977 testWalk: false, 978 }, 979 { 980 name: "DeleteLinkAndMerkleDirectory", 981 deleteLink: true, 982 deleteMerkleFile: true, 983 hasDirectoryTarget: true, 984 testWalk: false, 985 }, 986 { 987 name: "DeleteLinkDirectoryWalk", 988 deleteLink: true, 989 deleteMerkleFile: false, 990 hasDirectoryTarget: true, 991 testWalk: true, 992 }, 993 { 994 name: "DeleteMerkleDirectoryWalk", 995 deleteLink: false, 996 deleteMerkleFile: true, 997 hasDirectoryTarget: true, 998 testWalk: true, 999 }, 1000 { 1001 name: "DeleteLinkAndMerkleDirectoryWalk", 1002 deleteLink: true, 1003 deleteMerkleFile: true, 1004 hasDirectoryTarget: true, 1005 testWalk: true, 1006 }, 1007 } 1008 for _, tc := range testCases { 1009 t.Run(tc.name, func(t *testing.T) { 1010 if tc.testWalk && !tc.hasDirectoryTarget { 1011 t.Fatalf("Invalid test case: hasDirectoryTarget can't be false when testing symlink walk") 1012 } 1013 1014 vfsObj, root, ctx, err := newVerityRoot(t, SHA256) 1015 if err != nil { 1016 t.Fatalf("newVerityRoot: %v", err) 1017 } 1018 1019 var target string 1020 if tc.hasDirectoryTarget { 1021 target = "verity-test-dir" 1022 if _, err := newDirFD(ctx, t, vfsObj, root, target, 0644); err != nil { 1023 t.Fatalf("newDirFD: %v", err) 1024 } 1025 } else { 1026 target = "verity-test-file" 1027 if _, _, err := newFileFD(ctx, t, vfsObj, root, target, 0644); err != nil { 1028 t.Fatalf("newFileFD: %v", err) 1029 } 1030 } 1031 1032 symlink := "verity-test-symlink" 1033 if err := dentryFromVD(t, root).symlinkLowerAt(ctx, vfsObj, target, symlink); err != nil { 1034 t.Fatalf("SymlinkAt: %v", err) 1035 } 1036 1037 fd, err := openVerityAt(ctx, vfsObj, root, symlink, linux.O_PATH|linux.O_NOFOLLOW, linux.ModeRegular) 1038 1039 if err != nil { 1040 t.Fatalf("openVerityAt symlink: %v", err) 1041 } 1042 1043 if tc.testWalk { 1044 fileInTargetDirectory := target + "/" + "verity-test-file" 1045 if _, _, err := newFileFD(ctx, t, vfsObj, root, fileInTargetDirectory, 0644); err != nil { 1046 t.Fatalf("newFileFD: %v", err) 1047 } 1048 } 1049 1050 enableVerity(ctx, t, fd) 1051 1052 if tc.deleteLink { 1053 if err := dentryFromVD(t, root).unlinkLowerAt(ctx, vfsObj, symlink); err != nil { 1054 t.Fatalf("UnlinkAt: %v", err) 1055 } 1056 } 1057 if tc.deleteMerkleFile { 1058 if err := dentryFromVD(t, root).unlinkLowerMerkleAt(ctx, vfsObj, symlink); err != nil { 1059 t.Fatalf("UnlinkAt: %v", err) 1060 } 1061 } 1062 if _, err := vfsObj.ReadlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ 1063 Root: root, 1064 Start: root, 1065 Path: fspath.Parse(symlink), 1066 }); !linuxerr.Equals(linuxerr.EIO, err) { 1067 t.Fatalf("ReadlinkAt succeeded with modified symlink: %v", err) 1068 } 1069 1070 if tc.testWalk { 1071 fileInSymlinkDirectory := symlink + "/verity-test-file" 1072 // Ensure opening the verity enabled file in the symlink directory fails. 1073 if _, err := openVerityAt(ctx, vfsObj, root, fileInSymlinkDirectory, linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.EIO, err) { 1074 t.Errorf("Open succeeded with modified symlink: %v", err) 1075 } 1076 } 1077 }) 1078 } 1079 } 1080 1081 // TestModifiedSymlinkFileReadFails ensures that reading value of a modified verity enabled 1082 // symlink fails. 1083 func TestModifiedSymlinkFileReadFails(t *testing.T) { 1084 testCases := []struct { 1085 name string 1086 // The symlink target is a directory. 1087 hasDirectoryTarget bool 1088 // The symlink target is a directory and contains a regular file which will be 1089 // used to test walking a symlink. 1090 testWalk bool 1091 }{ 1092 { 1093 name: "RegularFileTarget", 1094 hasDirectoryTarget: false, 1095 testWalk: false, 1096 }, 1097 { 1098 name: "DirectoryTarget", 1099 hasDirectoryTarget: true, 1100 testWalk: false, 1101 }, 1102 { 1103 name: "RegularFileInSymlinkDirectory", 1104 hasDirectoryTarget: true, 1105 testWalk: true, 1106 }, 1107 } 1108 for _, tc := range testCases { 1109 t.Run(tc.name, func(t *testing.T) { 1110 if tc.testWalk && !tc.hasDirectoryTarget { 1111 t.Fatalf("Invalid test case: hasDirectoryTarget can't be false when testing symlink walk") 1112 } 1113 1114 vfsObj, root, ctx, err := newVerityRoot(t, SHA256) 1115 if err != nil { 1116 t.Fatalf("newVerityRoot: %v", err) 1117 } 1118 1119 var target string 1120 if tc.hasDirectoryTarget { 1121 target = "verity-test-dir" 1122 if _, err := newDirFD(ctx, t, vfsObj, root, target, 0644); err != nil { 1123 t.Fatalf("newDirFD: %v", err) 1124 } 1125 } else { 1126 target = "verity-test-file" 1127 if _, _, err := newFileFD(ctx, t, vfsObj, root, target, 0644); err != nil { 1128 t.Fatalf("newFileFD: %v", err) 1129 } 1130 } 1131 1132 // Create symlink which points to target file. 1133 symlink := "verity-test-symlink" 1134 if err := dentryFromVD(t, root).symlinkLowerAt(ctx, vfsObj, target, symlink); err != nil { 1135 t.Fatalf("SymlinkAt: %v", err) 1136 } 1137 1138 // Open symlink file to get the fd for ioctl in new step. 1139 fd, err := openVerityAt(ctx, vfsObj, root, symlink, linux.O_PATH|linux.O_NOFOLLOW, linux.ModeRegular) 1140 if err != nil { 1141 t.Fatalf("OpenAt symlink: %v", err) 1142 } 1143 1144 if tc.testWalk { 1145 fileInTargetDirectory := target + "/" + "verity-test-file" 1146 if _, _, err := newFileFD(ctx, t, vfsObj, root, fileInTargetDirectory, 0644); err != nil { 1147 t.Fatalf("newFileFD: %v", err) 1148 } 1149 } 1150 1151 enableVerity(ctx, t, fd) 1152 1153 var newTarget string 1154 if tc.hasDirectoryTarget { 1155 newTarget = "verity-test-dir-new" 1156 if _, err := newDirFD(ctx, t, vfsObj, root, newTarget, 0644); err != nil { 1157 t.Fatalf("newDirFD: %v", err) 1158 } 1159 } else { 1160 newTarget = "verity-test-file-new" 1161 if _, _, err := newFileFD(ctx, t, vfsObj, root, newTarget, 0644); err != nil { 1162 t.Fatalf("newFileFD: %v", err) 1163 } 1164 } 1165 1166 // Unlink symlink->target. 1167 if err := dentryFromVD(t, root).unlinkLowerAt(ctx, vfsObj, symlink); err != nil { 1168 t.Fatalf("UnlinkAt: %v", err) 1169 } 1170 1171 // Link symlink->newTarget. 1172 if err := dentryFromVD(t, root).symlinkLowerAt(ctx, vfsObj, newTarget, symlink); err != nil { 1173 t.Fatalf("SymlinkAt: %v", err) 1174 } 1175 1176 // Freshen lower dentry for symlink. 1177 symlinkVD, err := vfsObj.GetDentryAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ 1178 Root: root, 1179 Start: root, 1180 Path: fspath.Parse(symlink), 1181 }, &vfs.GetDentryOptions{}) 1182 if err != nil { 1183 t.Fatalf("Failed to get symlink dentry: %v", err) 1184 } 1185 symlinkDentry := dentryFromVD(t, symlinkVD) 1186 1187 symlinkLowerVD, err := dentryFromVD(t, root).getLowerAt(ctx, vfsObj, symlink) 1188 if err != nil { 1189 t.Fatalf("Failed to get symlink lower dentry: %v", err) 1190 } 1191 symlinkDentry.lowerVD = symlinkLowerVD 1192 1193 // Verify ReadlinkAt() fails. 1194 if _, err := vfsObj.ReadlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ 1195 Root: root, 1196 Start: root, 1197 Path: fspath.Parse(symlink), 1198 }); !linuxerr.Equals(linuxerr.EIO, err) { 1199 t.Fatalf("ReadlinkAt succeeded with modified symlink: %v", err) 1200 } 1201 1202 if tc.testWalk { 1203 fileInSymlinkDirectory := symlink + "/verity-test-file" 1204 // Ensure opening the verity enabled file in the symlink directory fails. 1205 if _, err := openVerityAt(ctx, vfsObj, root, fileInSymlinkDirectory, linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.EIO, err) { 1206 t.Errorf("Open succeeded with modified symlink: %v", err) 1207 } 1208 } 1209 }) 1210 } 1211 }