// Copyright 2020 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package verity

import (
	"fmt"
	"io"
	"math/rand"
	"strconv"
	"testing"
	"time"

	"gvisor.dev/gvisor/pkg/abi/linux"
	"gvisor.dev/gvisor/pkg/context"
	"gvisor.dev/gvisor/pkg/fspath"
	"gvisor.dev/gvisor/pkg/sentry/arch"
	"gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil"
	"gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
	"gvisor.dev/gvisor/pkg/sentry/kernel"
	"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
	"gvisor.dev/gvisor/pkg/sentry/vfs"
	"gvisor.dev/gvisor/pkg/syserror"
	"gvisor.dev/gvisor/pkg/usermem"
)

// rootMerkleFilename is the name of the root Merkle tree file.
const rootMerkleFilename = "root.verity"

// maxDataSize is the maximum data size written to the file for test.
const maxDataSize = 100000

// newVerityRoot creates a new verity mount, and returns the root. The
// underlying file system is tmpfs. If the error is not nil, then cleanup
// should be called when the root is no longer needed.
func newVerityRoot(t *testing.T, hashAlg HashAlgorithm) (*vfs.VirtualFilesystem, vfs.VirtualDentry, *kernel.Task, error) {
	k, err := testutil.Boot()
	if err != nil {
		t.Fatalf("testutil.Boot: %v", err)
	}

	ctx := k.SupervisorContext()

	rand.Seed(time.Now().UnixNano())
	vfsObj := &vfs.VirtualFilesystem{}
	if err := vfsObj.Init(ctx); err != nil {
		return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("VFS init: %v", err)
	}

	vfsObj.MustRegisterFilesystemType("verity", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
		AllowUserMount: true,
	})

	vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
		AllowUserMount: true,
	})

	mntns, err := vfsObj.NewMountNamespace(ctx, auth.CredentialsFromContext(ctx), "", "verity", &vfs.MountOptions{
		GetFilesystemOptions: vfs.GetFilesystemOptions{
			InternalData: InternalFilesystemOptions{
				RootMerkleFileName:           rootMerkleFilename,
				LowerName:                    "tmpfs",
				Alg:                          hashAlg,
				AllowRuntimeEnable:           true,
				NoCrashOnVerificationFailure: true,
			},
		},
	})
	if err != nil {
		return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("NewMountNamespace: %v", err)
	}
	root := mntns.Root()
	root.IncRef()

	// Use lowerRoot in the task as we modify the lower file system
	// directly in many tests.
	lowerRoot := root.Dentry().Impl().(*dentry).lowerVD
	tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
	task, err := testutil.CreateTask(ctx, "name", tc, mntns, lowerRoot, lowerRoot)
	if err != nil {
		t.Fatalf("testutil.CreateTask: %v", err)
	}

	t.Helper()
	t.Cleanup(func() {
		root.DecRef(ctx)
		mntns.DecRef(ctx)
	})
	return vfsObj, root, task, nil
}

// newFileFD creates a new file in the verity mount, and returns the FD. The FD
// points to a file that has random data generated.
func newFileFD(ctx context.Context, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, filePath string, mode linux.FileMode) (*vfs.FileDescription, int, error) {
	creds := auth.CredentialsFromContext(ctx)
	lowerRoot := root.Dentry().Impl().(*dentry).lowerVD

	// Create the file in the underlying file system.
	lowerFD, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
		Root:  lowerRoot,
		Start: lowerRoot,
		Path:  fspath.Parse(filePath),
	}, &vfs.OpenOptions{
		Flags: linux.O_RDWR | linux.O_CREAT | linux.O_EXCL,
		Mode:  linux.ModeRegular | mode,
	})
	if err != nil {
		return nil, 0, err
	}

	// Generate random data to be written to the file.
	dataSize := rand.Intn(maxDataSize) + 1
	data := make([]byte, dataSize)
	rand.Read(data)

	// Write directly to the underlying FD, since verity FD is read-only.
	n, err := lowerFD.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
	if err != nil {
		return nil, 0, err
	}

	if n != int64(len(data)) {
		return nil, 0, fmt.Errorf("lowerFD.Write got write length %d, want %d", n, len(data))
	}

	lowerFD.DecRef(ctx)

	// Now open the verity file descriptor.
	fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
		Root:  root,
		Start: root,
		Path:  fspath.Parse(filePath),
	}, &vfs.OpenOptions{
		Flags: linux.O_RDONLY,
		Mode:  linux.ModeRegular | mode,
	})
	return fd, dataSize, err
}

// corruptRandomBit randomly flips a bit in the file represented by fd.
func corruptRandomBit(ctx context.Context, fd *vfs.FileDescription, size int) error {
	// Flip a random bit in the underlying file.
	randomPos := int64(rand.Intn(size))
	byteToModify := make([]byte, 1)
	if _, err := fd.PRead(ctx, usermem.BytesIOSequence(byteToModify), randomPos, vfs.ReadOptions{}); err != nil {
		return fmt.Errorf("lowerFD.PRead: %v", err)
	}
	byteToModify[0] ^= 1
	if _, err := fd.PWrite(ctx, usermem.BytesIOSequence(byteToModify), randomPos, vfs.WriteOptions{}); err != nil {
		return fmt.Errorf("lowerFD.PWrite: %v", err)
	}
	return nil
}

var hashAlgs = []HashAlgorithm{SHA256, SHA512}

// TestOpen ensures that when a file is created, the corresponding Merkle tree
// file and the root Merkle tree file exist.
func TestOpen(t *testing.T) {
	for _, alg := range hashAlgs {
		vfsObj, root, ctx, err := newVerityRoot(t, alg)
		if err != nil {
			t.Fatalf("newVerityRoot: %v", err)
		}

		filename := "verity-test-file"
		if _, _, err := newFileFD(ctx, vfsObj, root, filename, 0644); err != nil {
			t.Fatalf("newFileFD: %v", err)
		}

		// Ensure that the corresponding Merkle tree file is created.
		lowerRoot := root.Dentry().Impl().(*dentry).lowerVD
		if _, err = vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
			Root:  lowerRoot,
			Start: lowerRoot,
			Path:  fspath.Parse(merklePrefix + filename),
		}, &vfs.OpenOptions{
			Flags: linux.O_RDONLY,
		}); err != nil {
			t.Errorf("OpenAt Merkle tree file %s: %v", merklePrefix+filename, err)
		}

		// Ensure the root merkle tree file is created.
		if _, err = vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
			Root:  lowerRoot,
			Start: lowerRoot,
			Path:  fspath.Parse(merklePrefix + rootMerkleFilename),
		}, &vfs.OpenOptions{
			Flags: linux.O_RDONLY,
		}); err != nil {
			t.Errorf("OpenAt root Merkle tree file %s: %v", merklePrefix+rootMerkleFilename, err)
		}
	}
}

// TestPReadUnmodifiedFileSucceeds ensures that pread from an untouched verity
// file succeeds after enabling verity for it.
func TestPReadUnmodifiedFileSucceeds(t *testing.T) {
	for _, alg := range hashAlgs {
		vfsObj, root, ctx, err := newVerityRoot(t, alg)
		if err != nil {
			t.Fatalf("newVerityRoot: %v", err)
		}

		filename := "verity-test-file"
		fd, size, err := newFileFD(ctx, vfsObj, root, filename, 0644)
		if err != nil {
			t.Fatalf("newFileFD: %v", err)
		}

		// Enable verity on the file and confirm a normal read succeeds.
		var args arch.SyscallArguments
		args[1] = arch.SyscallArgument{Value: linux.FS_IOC_ENABLE_VERITY}
		if _, err := fd.Ioctl(ctx, nil /* uio */, args); err != nil {
			t.Fatalf("Ioctl: %v", err)
		}

		buf := make([]byte, size)
		n, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0 /* offset */, vfs.ReadOptions{})
		if err != nil && err != io.EOF {
			t.Fatalf("fd.PRead: %v", err)
		}

		if n != int64(size) {
			t.Errorf("fd.PRead got read length %d, want %d", n, size)
		}
	}
}

// TestReadUnmodifiedFileSucceeds ensures that read from an untouched verity
// file succeeds after enabling verity for it.
func TestReadUnmodifiedFileSucceeds(t *testing.T) {
	for _, alg := range hashAlgs {
		vfsObj, root, ctx, err := newVerityRoot(t, alg)
		if err != nil {
			t.Fatalf("newVerityRoot: %v", err)
		}

		filename := "verity-test-file"
		fd, size, err := newFileFD(ctx, vfsObj, root, filename, 0644)
		if err != nil {
			t.Fatalf("newFileFD: %v", err)
		}

		// Enable verity on the file and confirm a normal read succeeds.
		var args arch.SyscallArguments
		args[1] = arch.SyscallArgument{Value: linux.FS_IOC_ENABLE_VERITY}
		if _, err := fd.Ioctl(ctx, nil /* uio */, args); err != nil {
			t.Fatalf("Ioctl: %v", err)
		}

		buf := make([]byte, size)
		n, err := fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{})
		if err != nil && err != io.EOF {
			t.Fatalf("fd.Read: %v", err)
		}

		if n != int64(size) {
			t.Errorf("fd.PRead got read length %d, want %d", n, size)
		}
	}
}

// TestReopenUnmodifiedFileSucceeds ensures that reopen an untouched verity file
// succeeds after enabling verity for it.
func TestReopenUnmodifiedFileSucceeds(t *testing.T) {
	for _, alg := range hashAlgs {
		vfsObj, root, ctx, err := newVerityRoot(t, alg)
		if err != nil {
			t.Fatalf("newVerityRoot: %v", err)
		}

		filename := "verity-test-file"
		fd, _, err := newFileFD(ctx, vfsObj, root, filename, 0644)
		if err != nil {
			t.Fatalf("newFileFD: %v", err)
		}

		// Enable verity on the file and confirms a normal read succeeds.
		var args arch.SyscallArguments
		args[1] = arch.SyscallArgument{Value: linux.FS_IOC_ENABLE_VERITY}
		if _, err := fd.Ioctl(ctx, nil /* uio */, args); err != nil {
			t.Fatalf("Ioctl: %v", err)
		}

		// Ensure reopening the verity enabled file succeeds.
		if _, err = vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
			Root:  root,
			Start: root,
			Path:  fspath.Parse(filename),
		}, &vfs.OpenOptions{
			Flags: linux.O_RDONLY,
			Mode:  linux.ModeRegular,
		}); err != nil {
			t.Errorf("reopen enabled file failed: %v", err)
		}
	}
}

// TestOpenNonexistentFile ensures that opening a nonexistent file does not
// trigger verification failure, even if the parent directory is verified.
func TestOpenNonexistentFile(t *testing.T) {
	vfsObj, root, ctx, err := newVerityRoot(t, SHA256)
	if err != nil {
		t.Fatalf("newVerityRoot: %v", err)
	}

	filename := "verity-test-file"
	fd, _, err := newFileFD(ctx, vfsObj, root, filename, 0644)
	if err != nil {
		t.Fatalf("newFileFD: %v", err)
	}

	// Enable verity on the file and confirms a normal read succeeds.
	var args arch.SyscallArguments
	args[1] = arch.SyscallArgument{Value: linux.FS_IOC_ENABLE_VERITY}
	if _, err := fd.Ioctl(ctx, nil /* uio */, args); err != nil {
		t.Fatalf("Ioctl: %v", err)
	}

	// Enable verity on the parent directory.
	parentFD, err := vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
		Root:  root,
		Start: root,
	}, &vfs.OpenOptions{
		Flags: linux.O_RDONLY,
	})
	if err != nil {
		t.Fatalf("OpenAt: %v", err)
	}

	if _, err := parentFD.Ioctl(ctx, nil /* uio */, args); err != nil {
		t.Fatalf("Ioctl: %v", err)
	}

	// Ensure open an unexpected file in the parent directory fails with
	// ENOENT rather than verification failure.
	if _, err = vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
		Root:  root,
		Start: root,
		Path:  fspath.Parse(filename + "abc"),
	}, &vfs.OpenOptions{
		Flags: linux.O_RDONLY,
		Mode:  linux.ModeRegular,
	}); err != syserror.ENOENT {
		t.Errorf("OpenAt unexpected error: %v", err)
	}
}

// TestPReadModifiedFileFails ensures that read from a modified verity file
// fails.
func TestPReadModifiedFileFails(t *testing.T) {
	for _, alg := range hashAlgs {
		vfsObj, root, ctx, err := newVerityRoot(t, alg)
		if err != nil {
			t.Fatalf("newVerityRoot: %v", err)
		}

		filename := "verity-test-file"
		fd, size, err := newFileFD(ctx, vfsObj, root, filename, 0644)
		if err != nil {
			t.Fatalf("newFileFD: %v", err)
		}

		// Enable verity on the file.
		var args arch.SyscallArguments
		args[1] = arch.SyscallArgument{Value: linux.FS_IOC_ENABLE_VERITY}
		if _, err := fd.Ioctl(ctx, nil /* uio */, args); err != nil {
			t.Fatalf("Ioctl: %v", err)
		}

		// Open a new lowerFD that's read/writable.
		lowerVD := fd.Impl().(*fileDescription).d.lowerVD

		lowerFD, err := vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
			Root:  lowerVD,
			Start: lowerVD,
		}, &vfs.OpenOptions{
			Flags: linux.O_RDWR,
		})
		if err != nil {
			t.Fatalf("OpenAt: %v", err)
		}

		if err := corruptRandomBit(ctx, lowerFD, size); err != nil {
			t.Fatalf("corruptRandomBit: %v", err)
		}

		// Confirm that read from the modified file fails.
		buf := make([]byte, size)
		if _, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0 /* offset */, vfs.ReadOptions{}); err == nil {
			t.Fatalf("fd.PRead succeeded, expected failure")
		}
	}
}

// TestReadModifiedFileFails ensures that read from a modified verity file
// fails.
func TestReadModifiedFileFails(t *testing.T) {
	for _, alg := range hashAlgs {
		vfsObj, root, ctx, err := newVerityRoot(t, alg)
		if err != nil {
			t.Fatalf("newVerityRoot: %v", err)
		}

		filename := "verity-test-file"
		fd, size, err := newFileFD(ctx, vfsObj, root, filename, 0644)
		if err != nil {
			t.Fatalf("newFileFD: %v", err)
		}

		// Enable verity on the file.
		var args arch.SyscallArguments
		args[1] = arch.SyscallArgument{Value: linux.FS_IOC_ENABLE_VERITY}
		if _, err := fd.Ioctl(ctx, nil /* uio */, args); err != nil {
			t.Fatalf("Ioctl: %v", err)
		}

		// Open a new lowerFD that's read/writable.
		lowerVD := fd.Impl().(*fileDescription).d.lowerVD

		lowerFD, err := vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
			Root:  lowerVD,
			Start: lowerVD,
		}, &vfs.OpenOptions{
			Flags: linux.O_RDWR,
		})
		if err != nil {
			t.Fatalf("OpenAt: %v", err)
		}

		if err := corruptRandomBit(ctx, lowerFD, size); err != nil {
			t.Fatalf("corruptRandomBit: %v", err)
		}

		// Confirm that read from the modified file fails.
		buf := make([]byte, size)
		if _, err := fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{}); err == nil {
			t.Fatalf("fd.Read succeeded, expected failure")
		}
	}
}

// TestModifiedMerkleFails ensures that read from a verity file fails if the
// corresponding Merkle tree file is modified.
func TestModifiedMerkleFails(t *testing.T) {
	for _, alg := range hashAlgs {
		vfsObj, root, ctx, err := newVerityRoot(t, alg)
		if err != nil {
			t.Fatalf("newVerityRoot: %v", err)
		}

		filename := "verity-test-file"
		fd, size, err := newFileFD(ctx, vfsObj, root, filename, 0644)
		if err != nil {
			t.Fatalf("newFileFD: %v", err)
		}

		// Enable verity on the file.
		var args arch.SyscallArguments
		args[1] = arch.SyscallArgument{Value: linux.FS_IOC_ENABLE_VERITY}
		if _, err := fd.Ioctl(ctx, nil /* uio */, args); err != nil {
			t.Fatalf("Ioctl: %v", err)
		}

		// Open a new lowerMerkleFD that's read/writable.
		lowerMerkleVD := fd.Impl().(*fileDescription).d.lowerMerkleVD

		lowerMerkleFD, err := vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
			Root:  lowerMerkleVD,
			Start: lowerMerkleVD,
		}, &vfs.OpenOptions{
			Flags: linux.O_RDWR,
		})
		if err != nil {
			t.Fatalf("OpenAt: %v", err)
		}

		// Flip a random bit in the Merkle tree file.
		stat, err := lowerMerkleFD.Stat(ctx, vfs.StatOptions{})
		if err != nil {
			t.Errorf("lowerMerkleFD.Stat: %v", err)
		}

		if err := corruptRandomBit(ctx, lowerMerkleFD, int(stat.Size)); err != nil {
			t.Fatalf("corruptRandomBit: %v", err)
		}

		// Confirm that read from a file with modified Merkle tree fails.
		buf := make([]byte, size)
		if _, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0 /* offset */, vfs.ReadOptions{}); err == nil {
			fmt.Println(buf)
			t.Fatalf("fd.PRead succeeded with modified Merkle file")
		}
	}
}

// TestModifiedParentMerkleFails ensures that open a verity enabled file in a
// verity enabled directory fails if the hashes related to the target file in
// the parent Merkle tree file is modified.
func TestModifiedParentMerkleFails(t *testing.T) {
	for _, alg := range hashAlgs {
		vfsObj, root, ctx, err := newVerityRoot(t, alg)
		if err != nil {
			t.Fatalf("newVerityRoot: %v", err)
		}

		filename := "verity-test-file"
		fd, _, err := newFileFD(ctx, vfsObj, root, filename, 0644)
		if err != nil {
			t.Fatalf("newFileFD: %v", err)
		}

		// Enable verity on the file.
		var args arch.SyscallArguments
		args[1] = arch.SyscallArgument{Value: linux.FS_IOC_ENABLE_VERITY}
		if _, err := fd.Ioctl(ctx, nil /* uio */, args); err != nil {
			t.Fatalf("Ioctl: %v", err)
		}

		// Enable verity on the parent directory.
		parentFD, err := vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
			Root:  root,
			Start: root,
		}, &vfs.OpenOptions{
			Flags: linux.O_RDONLY,
		})
		if err != nil {
			t.Fatalf("OpenAt: %v", err)
		}

		if _, err := parentFD.Ioctl(ctx, nil /* uio */, args); err != nil {
			t.Fatalf("Ioctl: %v", err)
		}

		// Open a new lowerMerkleFD that's read/writable.
		parentLowerMerkleVD := fd.Impl().(*fileDescription).d.parent.lowerMerkleVD

		parentLowerMerkleFD, err := vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
			Root:  parentLowerMerkleVD,
			Start: parentLowerMerkleVD,
		}, &vfs.OpenOptions{
			Flags: linux.O_RDWR,
		})
		if err != nil {
			t.Fatalf("OpenAt: %v", err)
		}

		// Flip a random bit in the parent Merkle tree file.
		// This parent directory contains only one child, so any random
		// modification in the parent Merkle tree should cause verification
		// failure when opening the child file.
		sizeString, err := parentLowerMerkleFD.GetXattr(ctx, &vfs.GetXattrOptions{
			Name: childrenOffsetXattr,
			Size: sizeOfStringInt32,
		})
		if err != nil {
			t.Fatalf("parentLowerMerkleFD.GetXattr: %v", err)
		}
		parentMerkleSize, err := strconv.Atoi(sizeString)
		if err != nil {
			t.Fatalf("Failed convert size to int: %v", err)
		}
		if err := corruptRandomBit(ctx, parentLowerMerkleFD, parentMerkleSize); err != nil {
			t.Fatalf("corruptRandomBit: %v", err)
		}

		parentLowerMerkleFD.DecRef(ctx)

		// Ensure reopening the verity enabled file fails.
		if _, err = vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
			Root:  root,
			Start: root,
			Path:  fspath.Parse(filename),
		}, &vfs.OpenOptions{
			Flags: linux.O_RDONLY,
			Mode:  linux.ModeRegular,
		}); err == nil {
			t.Errorf("OpenAt file with modified parent Merkle succeeded")
		}
	}
}

// TestUnmodifiedStatSucceeds ensures that stat of an untouched verity file
// succeeds after enabling verity for it.
func TestUnmodifiedStatSucceeds(t *testing.T) {
	for _, alg := range hashAlgs {
		vfsObj, root, ctx, err := newVerityRoot(t, alg)
		if err != nil {
			t.Fatalf("newVerityRoot: %v", err)
		}

		filename := "verity-test-file"
		fd, _, err := newFileFD(ctx, vfsObj, root, filename, 0644)
		if err != nil {
			t.Fatalf("newFileFD: %v", err)
		}

		// Enable verity on the file and confirms stat succeeds.
		var args arch.SyscallArguments
		args[1] = arch.SyscallArgument{Value: linux.FS_IOC_ENABLE_VERITY}
		if _, err := fd.Ioctl(ctx, nil /* uio */, args); err != nil {
			t.Fatalf("fd.Ioctl: %v", err)
		}

		if _, err := fd.Stat(ctx, vfs.StatOptions{}); err != nil {
			t.Errorf("fd.Stat: %v", err)
		}
	}
}

// TestModifiedStatFails checks that getting stat for a file with modified stat
// should fail.
func TestModifiedStatFails(t *testing.T) {
	for _, alg := range hashAlgs {
		vfsObj, root, ctx, err := newVerityRoot(t, alg)
		if err != nil {
			t.Fatalf("newVerityRoot: %v", err)
		}

		filename := "verity-test-file"
		fd, _, err := newFileFD(ctx, vfsObj, root, filename, 0644)
		if err != nil {
			t.Fatalf("newFileFD: %v", err)
		}

		// Enable verity on the file.
		var args arch.SyscallArguments
		args[1] = arch.SyscallArgument{Value: linux.FS_IOC_ENABLE_VERITY}
		if _, err := fd.Ioctl(ctx, nil /* uio */, args); err != nil {
			t.Fatalf("fd.Ioctl: %v", err)
		}

		lowerFD := fd.Impl().(*fileDescription).lowerFD
		// Change the stat of the underlying file, and check that stat fails.
		if err := lowerFD.SetStat(ctx, vfs.SetStatOptions{
			Stat: linux.Statx{
				Mask: uint32(linux.STATX_MODE),
				Mode: 0777,
			},
		}); err != nil {
			t.Fatalf("lowerFD.SetStat: %v", err)
		}

		if _, err := fd.Stat(ctx, vfs.StatOptions{}); err == nil {
			t.Errorf("fd.Stat succeeded when it should fail")
		}
	}
}

// TestOpenDeletedFileFails ensures that opening a deleted verity enabled file
// and/or the corresponding Merkle tree file fails with the verity error.
func TestOpenDeletedFileFails(t *testing.T) {
	testCases := []struct {
		// The original file is removed if changeFile is true.
		changeFile bool
		// The Merkle tree file is removed if changeMerkleFile is true.
		changeMerkleFile bool
	}{
		{
			changeFile:       true,
			changeMerkleFile: false,
		},
		{
			changeFile:       false,
			changeMerkleFile: true,
		},
		{
			changeFile:       true,
			changeMerkleFile: true,
		},
	}
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("changeFile:%t, changeMerkleFile:%t", tc.changeFile, tc.changeMerkleFile), func(t *testing.T) {
			vfsObj, root, ctx, err := newVerityRoot(t, SHA256)
			if err != nil {
				t.Fatalf("newVerityRoot: %v", err)
			}

			filename := "verity-test-file"
			fd, _, err := newFileFD(ctx, vfsObj, root, filename, 0644)
			if err != nil {
				t.Fatalf("newFileFD: %v", err)
			}

			// Enable verity on the file.
			var args arch.SyscallArguments
			args[1] = arch.SyscallArgument{Value: linux.FS_IOC_ENABLE_VERITY}
			if _, err := fd.Ioctl(ctx, nil /* uio */, args); err != nil {
				t.Fatalf("Ioctl: %v", err)
			}

			rootLowerVD := root.Dentry().Impl().(*dentry).lowerVD
			if tc.changeFile {
				if err := vfsObj.UnlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
					Root:  rootLowerVD,
					Start: rootLowerVD,
					Path:  fspath.Parse(filename),
				}); err != nil {
					t.Fatalf("UnlinkAt: %v", err)
				}
			}
			if tc.changeMerkleFile {
				if err := vfsObj.UnlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
					Root:  rootLowerVD,
					Start: rootLowerVD,
					Path:  fspath.Parse(merklePrefix + filename),
				}); err != nil {
					t.Fatalf("UnlinkAt: %v", err)
				}
			}

			// Ensure reopening the verity enabled file fails.
			if _, err = vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
				Root:  root,
				Start: root,
				Path:  fspath.Parse(filename),
			}, &vfs.OpenOptions{
				Flags: linux.O_RDONLY,
				Mode:  linux.ModeRegular,
			}); err != syserror.EIO {
				t.Errorf("got OpenAt error: %v, expected EIO", err)
			}
		})
	}
}

// TestOpenRenamedFileFails ensures that opening a renamed verity enabled file
// and/or the corresponding Merkle tree file fails with the verity error.
func TestOpenRenamedFileFails(t *testing.T) {
	testCases := []struct {
		// The original file is renamed if changeFile is true.
		changeFile bool
		// The Merkle tree file is renamed if changeMerkleFile is true.
		changeMerkleFile bool
	}{
		{
			changeFile:       true,
			changeMerkleFile: false,
		},
		{
			changeFile:       false,
			changeMerkleFile: true,
		},
		{
			changeFile:       true,
			changeMerkleFile: true,
		},
	}
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("changeFile:%t, changeMerkleFile:%t", tc.changeFile, tc.changeMerkleFile), func(t *testing.T) {
			vfsObj, root, ctx, err := newVerityRoot(t, SHA256)
			if err != nil {
				t.Fatalf("newVerityRoot: %v", err)
			}

			filename := "verity-test-file"
			fd, _, err := newFileFD(ctx, vfsObj, root, filename, 0644)
			if err != nil {
				t.Fatalf("newFileFD: %v", err)
			}

			// Enable verity on the file.
			var args arch.SyscallArguments
			args[1] = arch.SyscallArgument{Value: linux.FS_IOC_ENABLE_VERITY}
			if _, err := fd.Ioctl(ctx, nil /* uio */, args); err != nil {
				t.Fatalf("Ioctl: %v", err)
			}

			rootLowerVD := root.Dentry().Impl().(*dentry).lowerVD
			newFilename := "renamed-test-file"
			if tc.changeFile {
				if err := vfsObj.RenameAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
					Root:  rootLowerVD,
					Start: rootLowerVD,
					Path:  fspath.Parse(filename),
				}, &vfs.PathOperation{
					Root:  rootLowerVD,
					Start: rootLowerVD,
					Path:  fspath.Parse(newFilename),
				}, &vfs.RenameOptions{}); err != nil {
					t.Fatalf("RenameAt: %v", err)
				}
			}
			if tc.changeMerkleFile {
				if err := vfsObj.RenameAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
					Root:  rootLowerVD,
					Start: rootLowerVD,
					Path:  fspath.Parse(merklePrefix + filename),
				}, &vfs.PathOperation{
					Root:  rootLowerVD,
					Start: rootLowerVD,
					Path:  fspath.Parse(merklePrefix + newFilename),
				}, &vfs.RenameOptions{}); err != nil {
					t.Fatalf("UnlinkAt: %v", err)
				}
			}

			// Ensure reopening the verity enabled file fails.
			if _, err = vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
				Root:  root,
				Start: root,
				Path:  fspath.Parse(filename),
			}, &vfs.OpenOptions{
				Flags: linux.O_RDONLY,
				Mode:  linux.ModeRegular,
			}); err != syserror.EIO {
				t.Errorf("got OpenAt error: %v, expected EIO", err)
			}
		})
	}
}