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  }