From 1822dfa7ac95eb0dceabb415139e6abdc9d1e503 Mon Sep 17 00:00:00 2001 From: Nicolas Lacasse Date: Fri, 10 Jun 2022 22:16:17 -0700 Subject: [PATCH] Delete verityfs. PiperOrigin-RevId: 454300799 --- pkg/abi/linux/ioctl.go | 32 - pkg/merkletree/BUILD | 23 - pkg/merkletree/merkletree.go | 558 ------ pkg/merkletree/merkletree_test.go | 905 --------- pkg/sentry/fsimpl/verity/BUILD | 69 - pkg/sentry/fsimpl/verity/filesystem.go | 1084 ----------- pkg/sentry/fsimpl/verity/save_restore.go | 25 - pkg/sentry/fsimpl/verity/verity.go | 1696 ----------------- pkg/sentry/fsimpl/verity/verity_test.go | 1211 ------------ runsc/boot/BUILD | 1 - runsc/boot/vfs.go | 70 - runsc/cli/main.go | 1 - runsc/cmd/BUILD | 1 - runsc/cmd/gofer.go | 17 +- runsc/cmd/verity_prepare.go | 114 -- runsc/config/config.go | 3 - runsc/config/flags.go | 1 - runsc/fsgofer/filter/filter.go | 6 - runsc/fsgofer/fsgofer.go | 32 +- runsc/fsgofer/fsgofer_test.go | 11 - runsc/fsgofer/lisafs.go | 24 +- runsc/fsgofer/lisafs_test.go | 2 +- runsc/specutils/fs.go | 9 +- test/perf/BUILD | 35 - test/perf/linux/BUILD | 96 - test/perf/linux/verity_open_benchmark.cc | 71 - .../linux/verity_open_read_close_benchmark.cc | 75 - test/perf/linux/verity_randread_benchmark.cc | 108 -- test/perf/linux/verity_read_benchmark.cc | 69 - test/perf/linux/verity_stat_benchmark.cc | 84 - test/syscalls/BUILD | 20 - test/syscalls/linux/BUILD | 82 - test/syscalls/linux/verity_getdents.cc | 92 - test/syscalls/linux/verity_ioctl.cc | 241 --- test/syscalls/linux/verity_mmap.cc | 155 -- test/syscalls/linux/verity_mount.cc | 54 - test/syscalls/linux/verity_symlink.cc | 114 -- test/util/BUILD | 13 - test/util/verity_util.cc | 97 - test/util/verity_util.h | 84 - tools/verity/BUILD | 15 - tools/verity/measure_tool.go | 117 -- tools/verity/measure_tool_unsafe.go | 39 - 43 files changed, 11 insertions(+), 7545 deletions(-) delete mode 100644 pkg/merkletree/BUILD delete mode 100644 pkg/merkletree/merkletree.go delete mode 100644 pkg/merkletree/merkletree_test.go delete mode 100644 pkg/sentry/fsimpl/verity/BUILD delete mode 100644 pkg/sentry/fsimpl/verity/filesystem.go delete mode 100644 pkg/sentry/fsimpl/verity/save_restore.go delete mode 100644 pkg/sentry/fsimpl/verity/verity.go delete mode 100644 pkg/sentry/fsimpl/verity/verity_test.go delete mode 100644 runsc/cmd/verity_prepare.go delete mode 100644 test/perf/linux/verity_open_benchmark.cc delete mode 100644 test/perf/linux/verity_open_read_close_benchmark.cc delete mode 100644 test/perf/linux/verity_randread_benchmark.cc delete mode 100644 test/perf/linux/verity_read_benchmark.cc delete mode 100644 test/perf/linux/verity_stat_benchmark.cc delete mode 100644 test/syscalls/linux/verity_getdents.cc delete mode 100644 test/syscalls/linux/verity_ioctl.cc delete mode 100644 test/syscalls/linux/verity_mmap.cc delete mode 100644 test/syscalls/linux/verity_mount.cc delete mode 100644 test/syscalls/linux/verity_symlink.cc delete mode 100644 test/util/verity_util.cc delete mode 100644 test/util/verity_util.h delete mode 100644 tools/verity/BUILD delete mode 100644 tools/verity/measure_tool.go delete mode 100644 tools/verity/measure_tool_unsafe.go diff --git a/pkg/abi/linux/ioctl.go b/pkg/abi/linux/ioctl.go index 006b5a525..d6dbedc3e 100644 --- a/pkg/abi/linux/ioctl.go +++ b/pkg/abi/linux/ioctl.go @@ -113,38 +113,6 @@ const ( _IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS ) -// Constants from uapi/linux/fs.h. -const ( - FS_IOC_GETFLAGS = 2148034049 - FS_VERITY_FL = 1048576 -) - -// Constants from uapi/linux/fsverity.h. -const ( - FS_VERITY_HASH_ALG_SHA256 = 1 - FS_VERITY_HASH_ALG_SHA512 = 2 - - FS_IOC_ENABLE_VERITY = 1082156677 - FS_IOC_MEASURE_VERITY = 3221513862 -) - -// DigestMetadata is a helper struct for VerityDigest. -// -// +marshal -type DigestMetadata struct { - DigestAlgorithm uint16 - DigestSize uint16 -} - -// SizeOfDigestMetadata is the size of struct DigestMetadata. -const SizeOfDigestMetadata = 4 - -// VerityDigest is struct from uapi/linux/fsverity.h. -type VerityDigest struct { - Metadata DigestMetadata - Digest []byte -} - // IOC outputs the result of _IOC macro in asm-generic/ioctl.h. func IOC(dir, typ, nr, size uint32) uint32 { return uint32(dir)<<_IOC_DIRSHIFT | typ<<_IOC_TYPESHIFT | nr<<_IOC_NRSHIFT | size<<_IOC_SIZESHIFT diff --git a/pkg/merkletree/BUILD b/pkg/merkletree/BUILD deleted file mode 100644 index dcd6c3bf5..000000000 --- a/pkg/merkletree/BUILD +++ /dev/null @@ -1,23 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "merkletree", - srcs = ["merkletree.go"], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/hostarch", - ], -) - -go_test( - name = "merkletree_test", - srcs = ["merkletree_test.go"], - library = ":merkletree", - deps = [ - "//pkg/abi/linux", - "//pkg/hostarch", - ], -) diff --git a/pkg/merkletree/merkletree.go b/pkg/merkletree/merkletree.go deleted file mode 100644 index 6358ad8e9..000000000 --- a/pkg/merkletree/merkletree.go +++ /dev/null @@ -1,558 +0,0 @@ -// 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 merkletree implements Merkle tree generating and verification. -package merkletree - -import ( - "bytes" - "crypto/sha256" - "crypto/sha512" - "encoding/gob" - "fmt" - "io" - - "gvisor.dev/gvisor/pkg/abi/linux" - - "gvisor.dev/gvisor/pkg/hostarch" -) - -const ( - // sha256DigestSize specifies the digest size of a SHA256 hash. - sha256DigestSize = 32 - // sha512DigestSize specifies the digest size of a SHA512 hash. - sha512DigestSize = 64 -) - -// DigestSize returns the size (in bytes) of a digest. -func DigestSize(hashAlgorithm int) int { - switch hashAlgorithm { - case linux.FS_VERITY_HASH_ALG_SHA256: - return sha256DigestSize - case linux.FS_VERITY_HASH_ALG_SHA512: - return sha512DigestSize - default: - return -1 - } -} - -// Layout defines the scale of a Merkle tree. -type Layout struct { - // blockSize is the size of a data block to be hashed. - blockSize int64 - // digestSize is the size of a generated hash. - digestSize int64 - // levelOffset contains the offset of the beginning of each level in - // bytes. The number of levels in the tree is the length of the slice. - // The leaf nodes (level 0) contain hashes of blocks of the input data. - // Each level N contains hashes of the blocks in level N-1. The highest - // level is the root hash. - levelOffset []int64 -} - -// InitLayout initializes and returns a new Layout object describing the structure -// of a tree. dataSize specifies the size of input data in bytes. -func InitLayout(dataSize int64, hashAlgorithms int, dataAndTreeInSameFile bool) (Layout, error) { - layout := Layout{ - blockSize: hostarch.PageSize, - } - - switch hashAlgorithms { - case linux.FS_VERITY_HASH_ALG_SHA256: - layout.digestSize = sha256DigestSize - case linux.FS_VERITY_HASH_ALG_SHA512: - layout.digestSize = sha512DigestSize - default: - return Layout{}, fmt.Errorf("unexpected hash algorithms") - } - - // treeStart is the offset (in bytes) of the first level of the tree in - // the file. If data and tree are in different files, treeStart should - // be zero. If data is in the same file as the tree, treeStart points - // to the block after the last data block (which may be zero-padded). - var treeStart int64 - if dataAndTreeInSameFile { - treeStart = dataSize - if dataSize%layout.blockSize != 0 { - treeStart += layout.blockSize - dataSize%layout.blockSize - } - } - - numBlocks := (dataSize + layout.blockSize - 1) / layout.blockSize - level := 0 - offset := int64(0) - - // Calculate the number of levels in the Merkle tree and the beginning - // offset of each level. Level 0 consists of the leaf nodes that - // contain the hashes of the data blocks, while level numLevels - 1 is - // the root. - for numBlocks > 1 { - layout.levelOffset = append(layout.levelOffset, treeStart+offset*layout.blockSize) - // Round numBlocks up to fill up a block. - numBlocks += (layout.hashesPerBlock() - numBlocks%layout.hashesPerBlock()) % layout.hashesPerBlock() - offset += numBlocks / layout.hashesPerBlock() - numBlocks = numBlocks / layout.hashesPerBlock() - level++ - } - layout.levelOffset = append(layout.levelOffset, treeStart+offset*layout.blockSize) - - return layout, nil -} - -// hashesPerBlock() returns the number of digests in each block. For example, -// if blockSize is 4096 bytes, and digestSize is 32 bytes, there will be 128 -// hashesPerBlock. Therefore 128 hashes in one level will be combined in one -// hash in the level above. -func (layout Layout) hashesPerBlock() int64 { - return layout.blockSize / layout.digestSize -} - -// numLevels returns the total number of levels in the Merkle tree. -func (layout Layout) numLevels() int { - return len(layout.levelOffset) -} - -// rootLevel returns the level of the root hash. -func (layout Layout) rootLevel() int { - return layout.numLevels() - 1 -} - -// digestOffset finds the offset of a digest from the beginning of the tree. -// The target digest is at level of the tree, with index from the beginning of -// the current level. -func (layout Layout) digestOffset(level int, index int64) int64 { - return layout.levelOffset[level] + index*layout.digestSize -} - -// blockOffset finds the offset of a block from the beginning of the tree. The -// target block is at level of the tree, with index from the beginning of the -// current level. -func (layout Layout) blockOffset(level int, index int64) int64 { - return layout.levelOffset[level] + index*layout.blockSize -} - -// VerityDescriptor is a struct that is serialized and hashed to get a file's -// root hash, which contains the root hash of the raw content and the file's -// meatadata. -type VerityDescriptor struct { - Name string - FileSize int64 - Mode uint32 - UID uint32 - GID uint32 - Children []string - SymlinkTarget string - RootHash []byte -} - -func (d *VerityDescriptor) encode() []byte { - b := new(bytes.Buffer) - e := gob.NewEncoder(b) - e.Encode(d) - return b.Bytes() -} - -// verify generates a hash from d, and compares it with expected. -func (d *VerityDescriptor) verify(expected []byte, hashAlgorithms int) error { - h, err := hashData(d.encode(), hashAlgorithms) - if err != nil { - return err - } - if !bytes.Equal(h[:], expected) { - return fmt.Errorf("unexpected root hash") - } - return nil - -} - -// hashData hashes data and returns the result hash based on the hash -// algorithms. -func hashData(data []byte, hashAlgorithms int) ([]byte, error) { - var digest []byte - switch hashAlgorithms { - case linux.FS_VERITY_HASH_ALG_SHA256: - digestArray := sha256.Sum256(data) - digest = digestArray[:] - case linux.FS_VERITY_HASH_ALG_SHA512: - digestArray := sha512.Sum512(data) - digest = digestArray[:] - default: - return nil, fmt.Errorf("unexpected hash algorithms") - } - return digest, nil -} - -// GenerateParams contains the parameters used to generate a Merkle tree for a -// given file. -type GenerateParams struct { - // File is a reader of the file to be hashed. - File io.ReaderAt - // Size is the size of the file. - Size int64 - // Name is the name of the target file. - Name string - // Mode is the mode of the target file. - Mode uint32 - // UID is the user ID of the target file. - UID uint32 - // GID is the group ID of the target file. - GID uint32 - // Children is a map of children names for a directory. It should be - // empty for a regular file. - Children []string - // SymlinkTarget is the target path of a symlink file, or "" if the file is not a symlink. - SymlinkTarget string - // HashAlgorithms is the algorithms used to hash data. - HashAlgorithms int - // TreeReader is a reader for the Merkle tree. - TreeReader io.ReaderAt - // TreeWriter is a writer for the Merkle tree. - TreeWriter io.Writer - // DataAndTreeInSameFile is true if data and Merkle tree are in the same - // file, or false if Merkle tree is a separate file from data. - DataAndTreeInSameFile bool -} - -// Generate constructs a Merkle tree for the contents of params.File. The -// output is written to params.TreeWriter. -// -// Generate returns a hash of a VerityDescriptor, which contains the file -// metadata and the hash from file content. -func Generate(params *GenerateParams) ([]byte, error) { - descriptor := VerityDescriptor{ - FileSize: params.Size, - Name: params.Name, - Mode: params.Mode, - UID: params.UID, - GID: params.GID, - Children: params.Children, - SymlinkTarget: params.SymlinkTarget, - } - - // If file is a symlink do not generate root hash for file content. - if params.SymlinkTarget != "" { - return hashData(descriptor.encode(), params.HashAlgorithms) - } - - layout, err := InitLayout(params.Size, params.HashAlgorithms, params.DataAndTreeInSameFile) - if err != nil { - return nil, err - } - - numBlocks := (params.Size + layout.blockSize - 1) / layout.blockSize - - // If the data is in the same file as the tree, zero pad the last data - // block. - bytesInLastBlock := params.Size % layout.blockSize - if params.DataAndTreeInSameFile && bytesInLastBlock != 0 { - zeroBuf := make([]byte, layout.blockSize-bytesInLastBlock) - if _, err := params.TreeWriter.Write(zeroBuf); err != nil { - return nil, err - } - } - - var root []byte - for level := 0; level < layout.numLevels(); level++ { - for i := int64(0); i < numBlocks; i++ { - buf := make([]byte, layout.blockSize) - var ( - n int - err error - ) - if level == 0 { - // Read data block from the target file since level 0 includes hashes - // of blocks in the input data. - n, err = params.File.ReadAt(buf, i*layout.blockSize) - } else { - // Read data block from the tree file since levels higher than 0 are - // hashing the lower level hashes. - n, err = params.TreeReader.ReadAt(buf, layout.blockOffset(level-1, i)) - } - - // err is populated as long as the bytes read is smaller than the buffer - // size. This could be the case if we are reading the last block, and - // break in that case. If this is the last block, the end of the block - // will be zero-padded. - if n == 0 && err == io.EOF { - break - } else if err != nil && err != io.EOF { - return nil, err - } - // Hash the bytes in buf. - digest, err := hashData(buf, params.HashAlgorithms) - if err != nil { - return nil, err - } - - if level == layout.rootLevel() { - root = digest - } - - // Write the generated hash to the end of the tree file. - if _, err = params.TreeWriter.Write(digest[:]); err != nil { - return nil, err - } - } - // If the generated digests do not round up to a block, zero-padding the - // remaining of the last block. But no need to do so for root. - if level != layout.rootLevel() && numBlocks%layout.hashesPerBlock() != 0 { - zeroBuf := make([]byte, layout.blockSize-(numBlocks%layout.hashesPerBlock())*layout.digestSize) - if _, err := params.TreeWriter.Write(zeroBuf[:]); err != nil { - return nil, err - } - } - numBlocks = (numBlocks + layout.hashesPerBlock() - 1) / layout.hashesPerBlock() - } - descriptor.RootHash = root - return hashData(descriptor.encode(), params.HashAlgorithms) -} - -// VerifyParams contains the params used to verify a portion of a file against -// a Merkle tree. -type VerifyParams struct { - // Out will be filled with verified data. - Out io.Writer - // File is a handler on the file to be verified. - File io.ReaderAt - // tree is a handler on the Merkle tree used to verify file. - Tree io.ReaderAt - // Size is the size of the file. - Size int64 - // Name is the name of the target file. - Name string - // Mode is the mode of the target file. - Mode uint32 - // UID is the user ID of the target file. - UID uint32 - // GID is the group ID of the target file. - GID uint32 - // Children is a map of children names for a directory. It should be - // empty for a regular file. - Children []string - // SymlinkTarget is the target path of a symlink file, or "" if the file is not a symlink. - SymlinkTarget string - // HashAlgorithms is the algorithms used to hash data. - HashAlgorithms int - // ReadOffset is the offset of the data range to be verified. - ReadOffset int64 - // ReadSize is the size of the data range to be verified. - ReadSize int64 - // Expected is a trusted hash for the file. It is compared with the - // calculated root hash to verify the content. - Expected []byte - // DataAndTreeInSameFile is true if data and Merkle tree are in the same - // file, or false if Merkle tree is a separate file from data. - DataAndTreeInSameFile bool -} - -// verifyMetadata verifies the metadata by hashing a descriptor that contains -// the metadata and compare the generated hash with expected. -// -// For verifyMetadata, params.data is not needed. It only accesses params.tree -// for the raw root hash. -func verifyMetadata(params *VerifyParams, layout *Layout) error { - var root []byte - // Only read the root hash if we expect that the file is not a symlink and its - // Merkle tree file is non-empty. - if params.Size != 0 && params.SymlinkTarget == "" { - root = make([]byte, layout.digestSize) - if _, err := params.Tree.ReadAt(root, layout.blockOffset(layout.rootLevel(), 0 /* index */)); err != nil { - return fmt.Errorf("failed to read root hash: %w", err) - } - } - descriptor := VerityDescriptor{ - Name: params.Name, - FileSize: params.Size, - Mode: params.Mode, - UID: params.UID, - GID: params.GID, - Children: params.Children, - SymlinkTarget: params.SymlinkTarget, - RootHash: root, - } - return descriptor.verify(params.Expected, params.HashAlgorithms) -} - -// cachedHashes stores verified hashes from a previous hash step. -type cachedHashes struct { - // offset is the offset of cached hash in each level. - offset []int64 - // hash is the verified cache for each level from previous hash steps. - hash [][]byte -} - -// Verify verifies the content read from data with offset. The content is -// verified against tree. If content spans across multiple blocks, each block is -// verified. Verification fails if the hash of the data does not match the tree -// at any level, or if the final root hash does not match expected. -// Once the data is verified, it will be written using params.Out. -// -// Verify checks for both target file content and metadata. If readSize is 0, -// only metadata is checked. -func Verify(params *VerifyParams) (int64, error) { - if params.ReadSize < 0 { - return 0, fmt.Errorf("unexpected read size: %d", params.ReadSize) - } - layout, err := InitLayout(int64(params.Size), params.HashAlgorithms, params.DataAndTreeInSameFile) - if err != nil { - return 0, err - } - if params.ReadSize == 0 { - return 0, verifyMetadata(params, &layout) - } - - // Calculate the index of blocks that includes the target range in input - // data. - firstDataBlock := params.ReadOffset / layout.blockSize - lastDataBlock := (params.ReadOffset + params.ReadSize - 1) / layout.blockSize - - size := (lastDataBlock - firstDataBlock + 1) * layout.blockSize - retBuf := make([]byte, size) - n, err := params.File.ReadAt(retBuf, firstDataBlock*layout.blockSize) - if err != nil && err != io.EOF { - return 0, err - } - total := int64(n) - bytesRead := int64(0) - - // Only cache hash results if reading more than a block. - var ch *cachedHashes - if lastDataBlock > firstDataBlock { - ch = &cachedHashes{ - offset: make([]int64, layout.numLevels()), - hash: make([][]byte, layout.numLevels()), - } - } - for i := firstDataBlock; i <= lastDataBlock; i++ { - // Reach the end of file during verification. - if total <= 0 { - return bytesRead, io.EOF - } - // Read a block that includes all or part of target range in - // input data. - buf := retBuf[(i-firstDataBlock)*layout.blockSize : (i-firstDataBlock+1)*layout.blockSize] - - descriptor := VerityDescriptor{ - Name: params.Name, - FileSize: params.Size, - Mode: params.Mode, - UID: params.UID, - GID: params.GID, - SymlinkTarget: params.SymlinkTarget, - Children: params.Children, - } - if err := verifyBlock(params.Tree, &descriptor, &layout, buf, i, params.HashAlgorithms, params.Expected, ch); err != nil { - return bytesRead, err - } - - // startOff is the beginning of the read range within the - // current data block. Note that for all blocks other than the - // first, startOff should be 0. - startOff := int64(0) - if i == firstDataBlock { - startOff = params.ReadOffset % layout.blockSize - } - // endOff is the end of the read range within the current data - // block. Note that for all blocks other than the last, endOff - // should be the block size. - endOff := layout.blockSize - if i == lastDataBlock { - endOff = (params.ReadOffset+params.ReadSize-1)%layout.blockSize + 1 - } - - // If the provided size exceeds the end of input data, we should - // only copy the parts in buf that's part of input data. - if startOff > total { - startOff = total - } - if endOff > total { - endOff = total - } - - n, err := params.Out.Write(buf[startOff:endOff]) - if err != nil { - return bytesRead, err - } - bytesRead += int64(n) - total -= endOff - } - return bytesRead, nil -} - -// verifyBlock verifies a block against tree. index is the number of block in -// original data. The block is verified through each level of the tree. It -// fails if the calculated hash from block is different from any level of -// hashes stored in tree. And the final root hash is compared with -// expected. -func verifyBlock(tree io.ReaderAt, descriptor *VerityDescriptor, layout *Layout, dataBlock []byte, blockIndex int64, hashAlgorithms int, expected []byte, ch *cachedHashes) error { - if len(dataBlock) != int(layout.blockSize) { - return fmt.Errorf("incorrect block size") - } - - expectedDigest := make([]byte, layout.digestSize) - treeBlock := make([]byte, layout.blockSize) - var digest []byte - for level := 0; level < layout.numLevels(); level++ { - // No need to verify remaining levels if the current block has - // been verified in a previous call and cached. - if ch != nil && ch.offset[level] == layout.digestOffset(level, blockIndex) && ch.hash[level] != nil { - break - } - - // Calculate hash. - if level == 0 { - h, err := hashData(dataBlock, hashAlgorithms) - if err != nil { - return err - } - digest = h - } else { - // Read a block in previous level that contains the - // hash we just generated, and generate a next level - // hash from it. - if _, err := tree.ReadAt(treeBlock, layout.blockOffset(level-1, blockIndex)); err != nil { - return err - } - h, err := hashData(treeBlock, hashAlgorithms) - if err != nil { - return err - } - digest = h - } - - // Read the digest for the current block and store in - // expectedDigest. - if _, err := tree.ReadAt(expectedDigest, layout.digestOffset(level, blockIndex)); err != nil { - return err - } - - if !bytes.Equal(digest, expectedDigest) { - return fmt.Errorf("verification failed") - } - if ch != nil { - ch.offset[level] = layout.digestOffset(level, blockIndex) - ch.hash[level] = expectedDigest - } - blockIndex = blockIndex / layout.hashesPerBlock() - } - - // Verification for the tree succeeded. Now hash the descriptor with - // the root hash and compare it with expected. - if ch != nil { - descriptor.RootHash = ch.hash[layout.rootLevel()] - } else { - descriptor.RootHash = digest - } - return descriptor.verify(expected, hashAlgorithms) -} diff --git a/pkg/merkletree/merkletree_test.go b/pkg/merkletree/merkletree_test.go deleted file mode 100644 index 1447fd139..000000000 --- a/pkg/merkletree/merkletree_test.go +++ /dev/null @@ -1,905 +0,0 @@ -// 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 merkletree - -import ( - "bytes" - "errors" - "fmt" - "io" - "math/rand" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/abi/linux" - - "gvisor.dev/gvisor/pkg/hostarch" -) - -func TestLayout(t *testing.T) { - testCases := []struct { - name string - dataSize int64 - hashAlgorithms int - dataAndTreeInSameFile bool - expectedDigestSize int64 - expectedLevelOffset []int64 - }{ - { - name: "SmallSizeSHA256SeparateFile", - dataSize: 100, - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, - dataAndTreeInSameFile: false, - expectedDigestSize: 32, - expectedLevelOffset: []int64{0}, - }, - { - name: "SmallSizeSHA512SeparateFile", - dataSize: 100, - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, - dataAndTreeInSameFile: false, - expectedDigestSize: 64, - expectedLevelOffset: []int64{0}, - }, - { - name: "SmallSizeSHA256SameFile", - dataSize: 100, - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, - dataAndTreeInSameFile: true, - expectedDigestSize: 32, - expectedLevelOffset: []int64{hostarch.PageSize}, - }, - { - name: "SmallSizeSHA512SameFile", - dataSize: 100, - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, - dataAndTreeInSameFile: true, - expectedDigestSize: 64, - expectedLevelOffset: []int64{hostarch.PageSize}, - }, - { - name: "MiddleSizeSHA256SeparateFile", - dataSize: 1000000, - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, - dataAndTreeInSameFile: false, - expectedDigestSize: 32, - expectedLevelOffset: []int64{0, 2 * hostarch.PageSize, 3 * hostarch.PageSize}, - }, - { - name: "MiddleSizeSHA512SeparateFile", - dataSize: 1000000, - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, - dataAndTreeInSameFile: false, - expectedDigestSize: 64, - expectedLevelOffset: []int64{0, 4 * hostarch.PageSize, 5 * hostarch.PageSize}, - }, - { - name: "MiddleSizeSHA256SameFile", - dataSize: 1000000, - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, - dataAndTreeInSameFile: true, - expectedDigestSize: 32, - expectedLevelOffset: []int64{245 * hostarch.PageSize, 247 * hostarch.PageSize, 248 * hostarch.PageSize}, - }, - { - name: "MiddleSizeSHA512SameFile", - dataSize: 1000000, - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, - dataAndTreeInSameFile: true, - expectedDigestSize: 64, - expectedLevelOffset: []int64{245 * hostarch.PageSize, 249 * hostarch.PageSize, 250 * hostarch.PageSize}, - }, - { - name: "LargeSizeSHA256SeparateFile", - dataSize: 4096 * int64(hostarch.PageSize), - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, - dataAndTreeInSameFile: false, - expectedDigestSize: 32, - expectedLevelOffset: []int64{0, 32 * hostarch.PageSize, 33 * hostarch.PageSize}, - }, - { - name: "LargeSizeSHA512SeparateFile", - dataSize: 4096 * int64(hostarch.PageSize), - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, - dataAndTreeInSameFile: false, - expectedDigestSize: 64, - expectedLevelOffset: []int64{0, 64 * hostarch.PageSize, 65 * hostarch.PageSize}, - }, - { - name: "LargeSizeSHA256SameFile", - dataSize: 4096 * int64(hostarch.PageSize), - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, - dataAndTreeInSameFile: true, - expectedDigestSize: 32, - expectedLevelOffset: []int64{4096 * hostarch.PageSize, 4128 * hostarch.PageSize, 4129 * hostarch.PageSize}, - }, - { - name: "LargeSizeSHA512SameFile", - dataSize: 4096 * int64(hostarch.PageSize), - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, - dataAndTreeInSameFile: true, - expectedDigestSize: 64, - expectedLevelOffset: []int64{4096 * hostarch.PageSize, 4160 * hostarch.PageSize, 4161 * hostarch.PageSize}, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - l, err := InitLayout(tc.dataSize, tc.hashAlgorithms, tc.dataAndTreeInSameFile) - if err != nil { - t.Fatalf("Failed to InitLayout: %v", err) - } - if l.blockSize != int64(hostarch.PageSize) { - t.Errorf("Got blockSize %d, want %d", l.blockSize, hostarch.PageSize) - } - if l.digestSize != tc.expectedDigestSize { - t.Errorf("Got digestSize %d, want %d", l.digestSize, sha256DigestSize) - } - if l.numLevels() != len(tc.expectedLevelOffset) { - t.Errorf("Got levels %d, want %d", l.numLevels(), len(tc.expectedLevelOffset)) - } - for i := 0; i < l.numLevels() && i < len(tc.expectedLevelOffset); i++ { - if l.levelOffset[i] != tc.expectedLevelOffset[i] { - t.Errorf("Got levelStart[%d] %d, want %d", i, l.levelOffset[i], tc.expectedLevelOffset[i]) - } - } - }) - } -} - -const ( - defaultName = "merkle_test" - defaultMode = 0644 - defaultUID = 0 - defaultGID = 0 - defaultSymlinkPath = "merkle_test_link" - defaultHashAlgorithm = linux.FS_VERITY_HASH_ALG_SHA256 -) - -// bytesReadWriter is used to read from/write to/seek in a byte array. Unlike -// bytes.Buffer, it keeps the whole buffer during read so that it can be reused. -type bytesReadWriter struct { - // bytes contains the underlying byte array. - bytes []byte - // readPos is the currently location for Read. Write always appends to - // the end of the array. - readPos int -} - -func (brw *bytesReadWriter) Write(p []byte) (int, error) { - brw.bytes = append(brw.bytes, p...) - return len(p), nil -} - -func (brw *bytesReadWriter) ReadAt(p []byte, off int64) (int, error) { - bytesRead := copy(p, brw.bytes[off:]) - if bytesRead == 0 { - return bytesRead, io.EOF - } - return bytesRead, nil -} - -func TestGenerate(t *testing.T) { - // The input data has size dataSize. It starts with the data in startWith, - // and all other bytes are zeroes. - testCases := []struct { - name string - data []byte - hashAlgorithms int - dataAndTreeInSameFile bool - expectedHash []byte - }{ - { - name: "OnePageZeroesSHA256SeparateFile", - data: bytes.Repeat([]byte{0}, hostarch.PageSize), - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, - dataAndTreeInSameFile: false, - expectedHash: []byte{78, 38, 225, 107, 61, 246, 26, 6, 71, 163, 254, 97, 112, 200, 87, 232, 190, 87, 231, 160, 119, 124, 61, 229, 49, 126, 90, 223, 134, 51, 77, 182}, - }, - { - name: "OnePageZeroesSHA256SameFile", - data: bytes.Repeat([]byte{0}, hostarch.PageSize), - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, - dataAndTreeInSameFile: true, - expectedHash: []byte{78, 38, 225, 107, 61, 246, 26, 6, 71, 163, 254, 97, 112, 200, 87, 232, 190, 87, 231, 160, 119, 124, 61, 229, 49, 126, 90, 223, 134, 51, 77, 182}, - }, - { - name: "OnePageZeroesSHA512SeparateFile", - data: bytes.Repeat([]byte{0}, hostarch.PageSize), - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, - dataAndTreeInSameFile: false, - expectedHash: []byte{221, 45, 182, 132, 61, 212, 227, 145, 150, 131, 98, 221, 195, 5, 89, 21, 188, 36, 250, 101, 85, 78, 197, 253, 193, 23, 74, 219, 28, 108, 77, 47, 65, 79, 123, 144, 50, 245, 109, 72, 71, 80, 24, 77, 158, 95, 242, 185, 109, 163, 105, 183, 67, 106, 55, 194, 223, 46, 12, 242, 165, 203, 172, 254}, - }, - { - name: "OnePageZeroesSHA512SameFile", - data: bytes.Repeat([]byte{0}, hostarch.PageSize), - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, - dataAndTreeInSameFile: true, - expectedHash: []byte{221, 45, 182, 132, 61, 212, 227, 145, 150, 131, 98, 221, 195, 5, 89, 21, 188, 36, 250, 101, 85, 78, 197, 253, 193, 23, 74, 219, 28, 108, 77, 47, 65, 79, 123, 144, 50, 245, 109, 72, 71, 80, 24, 77, 158, 95, 242, 185, 109, 163, 105, 183, 67, 106, 55, 194, 223, 46, 12, 242, 165, 203, 172, 254}, - }, - { - name: "MultiplePageZeroesSHA256SeparateFile", - data: bytes.Repeat([]byte{0}, 128*hostarch.PageSize+1), - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, - dataAndTreeInSameFile: false, - expectedHash: []byte{131, 122, 73, 143, 4, 202, 193, 156, 218, 169, 196, 223, 70, 100, 117, 191, 241, 113, 134, 11, 229, 231, 105, 157, 156, 0, 66, 213, 122, 145, 174, 8}, - }, - { - name: "MultiplePageZeroesSHA256SameFile", - data: bytes.Repeat([]byte{0}, 128*hostarch.PageSize+1), - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, - dataAndTreeInSameFile: true, - expectedHash: []byte{131, 122, 73, 143, 4, 202, 193, 156, 218, 169, 196, 223, 70, 100, 117, 191, 241, 113, 134, 11, 229, 231, 105, 157, 156, 0, 66, 213, 122, 145, 174, 8}, - }, - { - name: "MultiplePageZeroesSHA512SeparateFile", - data: bytes.Repeat([]byte{0}, 128*hostarch.PageSize+1), - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, - dataAndTreeInSameFile: false, - expectedHash: []byte{211, 48, 232, 110, 240, 51, 99, 241, 123, 138, 42, 76, 94, 86, 59, 200, 3, 246, 137, 148, 189, 226, 111, 103, 146, 29, 12, 218, 40, 182, 33, 99, 193, 163, 238, 26, 184, 13, 165, 187, 68, 173, 139, 9, 208, 59, 0, 192, 180, 50, 221, 35, 43, 119, 194, 16, 64, 84, 116, 63, 158, 195, 194, 226}, - }, - { - name: "MultiplePageZeroesSHA512SameFile", - data: bytes.Repeat([]byte{0}, 128*hostarch.PageSize+1), - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, - dataAndTreeInSameFile: true, - expectedHash: []byte{211, 48, 232, 110, 240, 51, 99, 241, 123, 138, 42, 76, 94, 86, 59, 200, 3, 246, 137, 148, 189, 226, 111, 103, 146, 29, 12, 218, 40, 182, 33, 99, 193, 163, 238, 26, 184, 13, 165, 187, 68, 173, 139, 9, 208, 59, 0, 192, 180, 50, 221, 35, 43, 119, 194, 16, 64, 84, 116, 63, 158, 195, 194, 226}, - }, - { - name: "SingleASHA256SeparateFile", - data: []byte{'a'}, - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, - dataAndTreeInSameFile: false, - expectedHash: []byte{26, 47, 238, 138, 235, 244, 140, 231, 129, 240, 155, 252, 219, 44, 46, 72, 57, 249, 139, 88, 132, 238, 86, 108, 181, 115, 96, 72, 99, 210, 134, 47}, - }, - { - name: "SingleASHA256SameFile", - data: []byte{'a'}, - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, - dataAndTreeInSameFile: true, - expectedHash: []byte{26, 47, 238, 138, 235, 244, 140, 231, 129, 240, 155, 252, 219, 44, 46, 72, 57, 249, 139, 88, 132, 238, 86, 108, 181, 115, 96, 72, 99, 210, 134, 47}, - }, - { - name: "SingleASHA512SeparateFile", - data: []byte{'a'}, - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, - dataAndTreeInSameFile: false, - expectedHash: []byte{44, 30, 224, 12, 102, 119, 163, 171, 119, 175, 212, 121, 231, 188, 125, 171, 79, 28, 144, 234, 75, 122, 44, 75, 15, 101, 173, 92, 233, 109, 234, 60, 173, 148, 125, 85, 94, 234, 95, 91, 16, 196, 88, 175, 23, 129, 226, 110, 24, 238, 5, 49, 186, 128, 72, 188, 193, 180, 207, 193, 203, 119, 40, 191}, - }, - { - name: "SingleASHA512SameFile", - data: []byte{'a'}, - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, - dataAndTreeInSameFile: true, - expectedHash: []byte{44, 30, 224, 12, 102, 119, 163, 171, 119, 175, 212, 121, 231, 188, 125, 171, 79, 28, 144, 234, 75, 122, 44, 75, 15, 101, 173, 92, 233, 109, 234, 60, 173, 148, 125, 85, 94, 234, 95, 91, 16, 196, 88, 175, 23, 129, 226, 110, 24, 238, 5, 49, 186, 128, 72, 188, 193, 180, 207, 193, 203, 119, 40, 191}, - }, - { - name: "OnePageASHA256SeparateFile", - data: bytes.Repeat([]byte{'a'}, hostarch.PageSize), - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, - dataAndTreeInSameFile: false, - expectedHash: []byte{166, 254, 83, 46, 241, 111, 18, 47, 79, 6, 181, 197, 176, 143, 211, 204, 53, 5, 245, 134, 172, 95, 97, 131, 236, 132, 197, 138, 123, 78, 43, 13}, - }, - { - name: "OnePageASHA256SameFile", - data: bytes.Repeat([]byte{'a'}, hostarch.PageSize), - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256, - dataAndTreeInSameFile: true, - expectedHash: []byte{166, 254, 83, 46, 241, 111, 18, 47, 79, 6, 181, 197, 176, 143, 211, 204, 53, 5, 245, 134, 172, 95, 97, 131, 236, 132, 197, 138, 123, 78, 43, 13}, - }, - { - name: "OnePageASHA512SeparateFile", - data: bytes.Repeat([]byte{'a'}, hostarch.PageSize), - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, - dataAndTreeInSameFile: false, - expectedHash: []byte{23, 69, 6, 79, 39, 232, 90, 246, 62, 55, 4, 229, 47, 36, 230, 24, 233, 47, 55, 36, 26, 139, 196, 78, 242, 12, 194, 77, 109, 81, 151, 188, 63, 201, 127, 235, 81, 214, 91, 200, 19, 232, 240, 14, 197, 1, 99, 224, 18, 213, 203, 242, 44, 102, 25, 62, 90, 189, 106, 107, 129, 61, 115, 39}, - }, - { - name: "OnePageASHA512SameFile", - data: bytes.Repeat([]byte{'a'}, hostarch.PageSize), - hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512, - dataAndTreeInSameFile: true, - expectedHash: []byte{23, 69, 6, 79, 39, 232, 90, 246, 62, 55, 4, 229, 47, 36, 230, 24, 233, 47, 55, 36, 26, 139, 196, 78, 242, 12, 194, 77, 109, 81, 151, 188, 63, 201, 127, 235, 81, 214, 91, 200, 19, 232, 240, 14, 197, 1, 99, 224, 18, 213, 203, 242, 44, 102, 25, 62, 90, 189, 106, 107, 129, 61, 115, 39}, - }, - } - - for _, tc := range testCases { - t.Run(fmt.Sprintf(tc.name), func(t *testing.T) { - var tree bytesReadWriter - params := GenerateParams{ - Size: int64(len(tc.data)), - Name: defaultName, - Mode: defaultMode, - UID: defaultUID, - GID: defaultGID, - Children: []string{}, - HashAlgorithms: tc.hashAlgorithms, - TreeReader: &tree, - TreeWriter: &tree, - DataAndTreeInSameFile: tc.dataAndTreeInSameFile, - } - if tc.dataAndTreeInSameFile { - tree.Write(tc.data) - params.File = &tree - } else { - params.File = &bytesReadWriter{ - bytes: tc.data, - } - } - hash, err := Generate(¶ms) - if err != nil { - t.Fatalf("Got err: %v, want nil", err) - } - if !bytes.Equal(hash, tc.expectedHash) { - t.Errorf("Got hash: %v, want %v", hash, tc.expectedHash) - } - }) - } -} - -// prepareVerify generates test data and corresponding Merkle tree, and returns -// the prepared VerifyParams. -// The test data has size dataSize. The data is hashed with hashAlgorithm. The -// portion to be verified is the range [verifyStart, verifyStart + verifySize). -func prepareVerify(t *testing.T, dataSize int64, hashAlgorithm int, dataAndTreeInSameFile, isSymlink bool, verifyStart, verifySize int64, out io.Writer) ([]byte, VerifyParams) { - t.Helper() - data := make([]byte, dataSize) - // Generate random bytes in data. - rand.Read(data) - - var tree bytesReadWriter - genParams := GenerateParams{ - Size: int64(len(data)), - Name: defaultName, - Mode: defaultMode, - UID: defaultUID, - GID: defaultGID, - Children: []string{}, - HashAlgorithms: hashAlgorithm, - TreeReader: &tree, - TreeWriter: &tree, - DataAndTreeInSameFile: dataAndTreeInSameFile, - } - if dataAndTreeInSameFile { - tree.Write(data) - genParams.File = &tree - } else { - genParams.File = &bytesReadWriter{ - bytes: data, - } - } - - if isSymlink { - genParams.SymlinkTarget = defaultSymlinkPath - } - hash, err := Generate(&genParams) - if err != nil { - t.Fatalf("could not generate Merkle tree:%v", err) - } - - return data, VerifyParams{ - Out: out, - File: bytes.NewReader(data), - Tree: &tree, - Size: dataSize, - Name: defaultName, - Mode: defaultMode, - UID: defaultUID, - GID: defaultGID, - Children: []string{}, - HashAlgorithms: hashAlgorithm, - ReadOffset: verifyStart, - ReadSize: verifySize, - Expected: hash, - DataAndTreeInSameFile: dataAndTreeInSameFile, - } -} - -func TestVerifyInvalidRange(t *testing.T) { - testCases := []struct { - name string - verifyStart int64 - verifySize int64 - }{ - // Verify range starts outside data range. - { - name: "StartOutsideRange", - verifyStart: hostarch.PageSize, - verifySize: 1, - }, - // Verify range ends outside data range. - { - name: "EndOutsideRange", - verifyStart: 0, - verifySize: 2 * hostarch.PageSize, - }, - // Verify range with negative size. - { - name: "NegativeSize", - verifyStart: 1, - verifySize: -1, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - var buf bytes.Buffer - _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, false /* dataAndTreeInSameFile */, false /* isSymlink */, tc.verifyStart, tc.verifySize, &buf) - if _, err := Verify(¶ms); errors.Is(err, nil) { - t.Errorf("Verification succeeded when expected to fail") - } - }) - } -} - -func TestVerifyUnmodifiedMetadata(t *testing.T) { - testCases := []struct { - name string - dataAndTreeInSameFile bool - isSymlink bool - }{ - { - name: "SeparateFile", - dataAndTreeInSameFile: false, - isSymlink: true, - }, - { - name: "SameFile", - dataAndTreeInSameFile: true, - isSymlink: false, - }, - { - name: "SameFileSymlink", - dataAndTreeInSameFile: true, - isSymlink: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - var buf bytes.Buffer - _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, tc.dataAndTreeInSameFile, tc.isSymlink, 0 /* verifyStart */, 0 /* verifySize */, &buf) - if tc.isSymlink { - params.SymlinkTarget = defaultSymlinkPath - } - if _, err := Verify(¶ms); !errors.Is(err, nil) { - t.Errorf("Verification failed when expected to succeed: %v", err) - } - }) - } -} - -func TestVerifyModifiedName(t *testing.T) { - testCases := []struct { - name string - dataAndTreeInSameFile bool - }{ - { - name: "SeparateFile", - dataAndTreeInSameFile: false, - }, - { - name: "SameFile", - dataAndTreeInSameFile: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - var buf bytes.Buffer - _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, 0 /* verifyStart */, 0 /* verifySize */, &buf) - params.Name += "abc" - if _, err := Verify(¶ms); errors.Is(err, nil) { - t.Errorf("Verification succeeded when expected to fail") - } - }) - } -} - -func TestVerifyModifiedSize(t *testing.T) { - testCases := []struct { - name string - dataAndTreeInSameFile bool - }{ - { - name: "SeparateFile", - dataAndTreeInSameFile: false, - }, - { - name: "SameFile", - dataAndTreeInSameFile: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - var buf bytes.Buffer - _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, 0 /* verifyStart */, 0 /* verifySize */, &buf) - params.Size-- - if _, err := Verify(¶ms); errors.Is(err, nil) { - t.Errorf("Verification succeeded when expected to fail") - } - }) - } -} - -func TestVerifyModifiedMode(t *testing.T) { - testCases := []struct { - name string - dataAndTreeInSameFile bool - }{ - { - name: "SeparateFile", - dataAndTreeInSameFile: false, - }, - { - name: "SameFile", - dataAndTreeInSameFile: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - var buf bytes.Buffer - _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, 0 /* verifyStart */, 0 /* verifySize */, &buf) - params.Mode++ - if _, err := Verify(¶ms); errors.Is(err, nil) { - t.Errorf("Verification succeeded when expected to fail") - } - }) - } -} - -func TestVerifyModifiedUID(t *testing.T) { - testCases := []struct { - name string - dataAndTreeInSameFile bool - }{ - { - name: "SeparateFile", - dataAndTreeInSameFile: false, - }, - { - name: "SameFile", - dataAndTreeInSameFile: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - var buf bytes.Buffer - _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, 0 /* verifyStart */, 0 /* verifySize */, &buf) - params.UID++ - if _, err := Verify(¶ms); errors.Is(err, nil) { - t.Errorf("Verification succeeded when expected to fail") - } - }) - } -} - -func TestVerifyModifiedGID(t *testing.T) { - testCases := []struct { - name string - dataAndTreeInSameFile bool - }{ - { - name: "SeparateFile", - dataAndTreeInSameFile: false, - }, - { - name: "SameFile", - dataAndTreeInSameFile: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - var buf bytes.Buffer - _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, 0 /* verifyStart */, 0 /* verifySize */, &buf) - params.GID++ - if _, err := Verify(¶ms); errors.Is(err, nil) { - t.Errorf("Verification succeeded when expected to fail") - } - }) - } -} - -func TestVerifyModifiedChildren(t *testing.T) { - testCases := []struct { - name string - dataAndTreeInSameFile bool - }{ - { - name: "SeparateFile", - dataAndTreeInSameFile: false, - }, - { - name: "SameFile", - dataAndTreeInSameFile: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - var buf bytes.Buffer - _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, 0 /* verifyStart */, 0 /* verifySize */, &buf) - params.Children = append(params.Children, "abc") - if _, err := Verify(¶ms); errors.Is(err, nil) { - t.Errorf("Verification succeeded when expected to fail") - } - }) - } -} - -func TestVerifyModifiedSymlink(t *testing.T) { - var buf bytes.Buffer - _, params := prepareVerify(t, hostarch.PageSize /* dataSize */, defaultHashAlgorithm, false /* dataAndTreeInSameFile */, true /* isSymlink */, 0 /* verifyStart */, 0 /* verifySize */, &buf) - params.SymlinkTarget = "merkle_modified_test_link" - if _, err := Verify(¶ms); err == nil { - t.Errorf("Verification succeeded when expected to fail") - } -} - -func TestModifyOutsideVerifyRange(t *testing.T) { - testCases := []struct { - name string - // The byte with index modifyByte is modified. - modifyByte int64 - dataAndTreeInSameFile bool - }{ - { - name: "BeforeRangeSeparateFile", - modifyByte: 4*hostarch.PageSize - 1, - dataAndTreeInSameFile: false, - }, - { - name: "BeforeRangeSameFile", - modifyByte: 4*hostarch.PageSize - 1, - dataAndTreeInSameFile: true, - }, - { - name: "AfterRangeSeparateFile", - modifyByte: 5 * hostarch.PageSize, - dataAndTreeInSameFile: false, - }, - { - name: "AfterRangeSameFile", - modifyByte: 5 * hostarch.PageSize, - dataAndTreeInSameFile: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - dataSize := int64(8 * hostarch.PageSize) - verifyStart := int64(4 * hostarch.PageSize) - verifySize := int64(hostarch.PageSize) - var buf bytes.Buffer - // Modified byte is outside verify range. Verify should succeed. - data, params := prepareVerify(t, dataSize, defaultHashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, verifyStart, verifySize, &buf) - // Flip a bit in data and checks Verify results. - data[tc.modifyByte] ^= 1 - n, err := Verify(¶ms) - if !errors.Is(err, nil) { - t.Errorf("Verification failed when expected to succeed: %v", err) - } - if n != verifySize { - t.Errorf("Got Verify output size %d, want %d", n, verifySize) - } - if int64(buf.Len()) != verifySize { - t.Errorf("Got Verify output buf size %d, want %d,", buf.Len(), verifySize) - } - if !bytes.Equal(data[verifyStart:verifyStart+verifySize], buf.Bytes()) { - t.Errorf("Incorrect output buf from Verify") - } - }) - } -} - -func TestModifyInsideVerifyRange(t *testing.T) { - testCases := []struct { - name string - verifyStart int64 - verifySize int64 - // The byte with index modifyByte is modified. - modifyByte int64 - dataAndTreeInSameFile bool - }{ - // Test a block-aligned verify range. - // Modifying a byte in the verified range should cause verify - // to fail. - { - name: "BlockAlignedRangeSeparateFile", - verifyStart: 4 * hostarch.PageSize, - verifySize: hostarch.PageSize, - modifyByte: 4 * hostarch.PageSize, - dataAndTreeInSameFile: false, - }, - { - name: "BlockAlignedRangeSameFile", - verifyStart: 4 * hostarch.PageSize, - verifySize: hostarch.PageSize, - modifyByte: 4 * hostarch.PageSize, - dataAndTreeInSameFile: true, - }, - // The tests below use a non-block-aligned verify range. - // Modifying a byte at strat of verify range should cause - // verify to fail. - { - name: "ModifyStartSeparateFile", - verifyStart: 4*hostarch.PageSize + 123, - verifySize: 2 * hostarch.PageSize, - modifyByte: 4*hostarch.PageSize + 123, - dataAndTreeInSameFile: false, - }, - { - name: "ModifyStartSameFile", - verifyStart: 4*hostarch.PageSize + 123, - verifySize: 2 * hostarch.PageSize, - modifyByte: 4*hostarch.PageSize + 123, - dataAndTreeInSameFile: true, - }, - // Modifying a byte at the end of verify range should cause - // verify to fail. - { - name: "ModifyEndSeparateFile", - verifyStart: 4*hostarch.PageSize + 123, - verifySize: 2 * hostarch.PageSize, - modifyByte: 6*hostarch.PageSize + 123, - dataAndTreeInSameFile: false, - }, - { - name: "ModifyEndSameFile", - verifyStart: 4*hostarch.PageSize + 123, - verifySize: 2 * hostarch.PageSize, - modifyByte: 6*hostarch.PageSize + 123, - dataAndTreeInSameFile: true, - }, - // Modifying a byte in the middle verified block should cause - // verify to fail. - { - name: "ModifyMiddleSeparateFile", - verifyStart: 4*hostarch.PageSize + 123, - verifySize: 2 * hostarch.PageSize, - modifyByte: 5*hostarch.PageSize + 123, - dataAndTreeInSameFile: false, - }, - { - name: "ModifyMiddleSameFile", - verifyStart: 4*hostarch.PageSize + 123, - verifySize: 2 * hostarch.PageSize, - modifyByte: 5*hostarch.PageSize + 123, - dataAndTreeInSameFile: true, - }, - // Modifying a byte in the first block in the verified range - // should cause verify to fail, even the modified bit itself is - // out of verify range. - { - name: "ModifyFirstBlockSeparateFile", - verifyStart: 4*hostarch.PageSize + 123, - verifySize: 2 * hostarch.PageSize, - modifyByte: 4*hostarch.PageSize + 122, - dataAndTreeInSameFile: false, - }, - { - name: "ModifyFirstBlockSameFile", - verifyStart: 4*hostarch.PageSize + 123, - verifySize: 2 * hostarch.PageSize, - modifyByte: 4*hostarch.PageSize + 122, - dataAndTreeInSameFile: true, - }, - // Modifying a byte in the last block in the verified range - // should cause verify to fail, even the modified bit itself is - // out of verify range. - { - name: "ModifyLastBlockSeparateFile", - verifyStart: 4*hostarch.PageSize + 123, - verifySize: 2 * hostarch.PageSize, - modifyByte: 6*hostarch.PageSize + 124, - dataAndTreeInSameFile: false, - }, - { - name: "ModifyLastBlockSameFile", - verifyStart: 4*hostarch.PageSize + 123, - verifySize: 2 * hostarch.PageSize, - modifyByte: 6*hostarch.PageSize + 124, - dataAndTreeInSameFile: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - dataSize := int64(8 * hostarch.PageSize) - var buf bytes.Buffer - data, params := prepareVerify(t, dataSize, defaultHashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, tc.verifyStart, tc.verifySize, &buf) - // Flip a bit in data and checks Verify results. - data[tc.modifyByte] ^= 1 - if _, err := Verify(¶ms); errors.Is(err, nil) { - t.Errorf("Verification succeeded when expected to fail") - } - }) - } -} - -func TestVerifyRandom(t *testing.T) { - testCases := []struct { - name string - hashAlgorithm int - dataAndTreeInSameFile bool - }{ - { - name: "SHA256SeparateFile", - hashAlgorithm: linux.FS_VERITY_HASH_ALG_SHA256, - dataAndTreeInSameFile: false, - }, - { - name: "SHA512SeparateFile", - hashAlgorithm: linux.FS_VERITY_HASH_ALG_SHA512, - dataAndTreeInSameFile: false, - }, - { - name: "SHA256SameFile", - hashAlgorithm: linux.FS_VERITY_HASH_ALG_SHA256, - dataAndTreeInSameFile: true, - }, - { - name: "SHA512SameFile", - hashAlgorithm: linux.FS_VERITY_HASH_ALG_SHA512, - dataAndTreeInSameFile: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - rand.Seed(time.Now().UnixNano()) - // Use a random dataSize. Minimum size 2 so that we can pick a random - // portion from it. - dataSize := rand.Int63n(200*hostarch.PageSize) + 2 - - // Pick a random portion of data. - start := rand.Int63n(dataSize - 1) - size := rand.Int63n(dataSize) + 1 - - var buf bytes.Buffer - data, params := prepareVerify(t, dataSize, tc.hashAlgorithm, tc.dataAndTreeInSameFile, false /* isSymlink */, start, size, &buf) - - // Checks that the random portion of data from the original data is - // verified successfully. - n, err := Verify(¶ms) - if err != nil && err != io.EOF { - t.Errorf("Verification failed for correct data: %v", err) - } - if size > dataSize-start { - size = dataSize - start - } - if n != size { - t.Errorf("Got Verify output size %d, want %d", n, size) - } - if int64(buf.Len()) != size { - t.Errorf("Got Verify output buf size %d, want %d", buf.Len(), size) - } - if !bytes.Equal(data[start:start+size], buf.Bytes()) { - t.Errorf("Incorrect output buf from Verify") - } - - // Verify that modified metadata should fail verification. - buf.Reset() - params.Name = defaultName + "abc" - if _, err := Verify(¶ms); errors.Is(err, nil) { - t.Error("Verify succeeded for modified metadata, expect failure") - } - - // Flip a random bit in randPortion, and check that verification fails. - buf.Reset() - randBytePos := rand.Int63n(size) - data[start+randBytePos] ^= 1 - params.File = bytes.NewReader(data) - params.Name = defaultName - - if _, err := Verify(¶ms); errors.Is(err, nil) { - t.Error("Verification succeeded for modified data, expect failure") - } - }) - } -} diff --git a/pkg/sentry/fsimpl/verity/BUILD b/pkg/sentry/fsimpl/verity/BUILD deleted file mode 100644 index 8a6f2261a..000000000 --- a/pkg/sentry/fsimpl/verity/BUILD +++ /dev/null @@ -1,69 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") -load("//tools/go_generics:defs.bzl", "go_template_instance") - -licenses(["notice"]) - -go_template_instance( - name = "dentry_list", - out = "dentry_list.go", - package = "verity", - prefix = "dentry", - template = "//pkg/ilist:generic_list", - types = { - "Element": "*dentry", - "Linker": "*dentry", - }, -) - -go_library( - name = "verity", - srcs = [ - "dentry_list.go", - "filesystem.go", - "save_restore.go", - "verity.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/atomicbitops", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fspath", - "//pkg/hostarch", - "//pkg/marshal/primitive", - "//pkg/merkletree", - "//pkg/refsvfs2", - "//pkg/safemem", - "//pkg/sentry/arch", - "//pkg/sentry/fs/lock", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/memmap", - "//pkg/sentry/socket/unix/transport", - "//pkg/sentry/vfs", - "//pkg/sync", - "//pkg/usermem", - ], -) - -go_test( - name = "verity_test", - srcs = [ - "verity_test.go", - ], - library = ":verity", - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fspath", - "//pkg/sentry/arch", - "//pkg/sentry/fsimpl/testutil", - "//pkg/sentry/fsimpl/tmpfs", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/vfs", - "//pkg/usermem", - ], -) diff --git a/pkg/sentry/fsimpl/verity/filesystem.go b/pkg/sentry/fsimpl/verity/filesystem.go deleted file mode 100644 index 48e5e71f8..000000000 --- a/pkg/sentry/fsimpl/verity/filesystem.go +++ /dev/null @@ -1,1084 +0,0 @@ -// 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 ( - "bytes" - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/atomicbitops" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fspath" - "gvisor.dev/gvisor/pkg/merkletree" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" -) - -// Sync implements vfs.FilesystemImpl.Sync. -func (fs *filesystem) Sync(ctx context.Context) error { - // All files should be read-only. - return nil -} - -var dentrySlicePool = sync.Pool{ - New: func() interface{} { - ds := make([]*dentry, 0, 4) // arbitrary non-zero initial capacity - return &ds - }, -} - -func appendDentry(ds *[]*dentry, d *dentry) *[]*dentry { - if ds == nil { - ds = dentrySlicePool.Get().(*[]*dentry) - } - *ds = append(*ds, d) - return ds -} - -// Preconditions: ds != nil. -func putDentrySlice(ds *[]*dentry) { - // Allow dentries to be GC'd. - for i := range *ds { - (*ds)[i] = nil - } - *ds = (*ds)[:0] - dentrySlicePool.Put(ds) -} - -// renameMuRUnlockAndCheckCaching calls fs.renameMu.RUnlock(), then calls -// dentry.checkCachingLocked on all dentries in *ds with fs.renameMu locked for -// writing. -// -// ds is a pointer-to-pointer since defer evaluates its arguments immediately, -// but dentry slices are allocated lazily, and it's much easier to say "defer -// fs.renameMuRUnlockAndCheckCaching(&ds)" than "defer func() { -// fs.renameMuRUnlockAndCheckCaching(ds) }()" to work around this. -// +checklocksreleaseread:fs.renameMu -func (fs *filesystem) renameMuRUnlockAndCheckCaching(ctx context.Context, ds **[]*dentry) { - fs.renameMu.RUnlock() - if *ds == nil { - return - } - for _, d := range **ds { - d.checkCachingLocked(ctx, false /* renameMuWriteLocked */) - } - putDentrySlice(*ds) -} - -// stepLocked resolves rp.Component() to an existing file, starting from the -// given directory. -// -// Dentries which may have a reference count of zero, and which therefore -// should be dropped once traversal is complete, are appended to ds. -// -// Preconditions: -// - fs.renameMu must be locked. -// - d.dirMu must be locked. -// - !rp.Done(). -func (fs *filesystem) stepLocked(ctx context.Context, rp *vfs.ResolvingPath, d *dentry, mayFollowSymlinks bool, ds **[]*dentry) (*dentry, error) { - if !d.isDir() { - return nil, linuxerr.ENOTDIR - } - - if err := d.checkPermissions(rp.Credentials(), vfs.MayExec); err != nil { - return nil, err - } - -afterSymlink: - name := rp.Component() - if name == "." { - rp.Advance() - return d, nil - } - if name == ".." { - if isRoot, err := rp.CheckRoot(ctx, &d.vfsd); err != nil { - return nil, err - } else if isRoot || d.parent == nil { - rp.Advance() - return d, nil - } - if err := rp.CheckMount(ctx, &d.parent.vfsd); err != nil { - return nil, err - } - rp.Advance() - return d.parent, nil - } - child, err := fs.getChildLocked(ctx, d, name, ds) - if err != nil { - return nil, err - } - if err := rp.CheckMount(ctx, &child.vfsd); err != nil { - return nil, err - } - if child.isSymlink() && mayFollowSymlinks && rp.ShouldFollowSymlink() { - target, err := child.readlink(ctx) - if err != nil { - return nil, err - } - if err := rp.HandleSymlink(target); err != nil { - return nil, err - } - goto afterSymlink // don't check the current directory again - } - rp.Advance() - return child, nil -} - -// verifyChildLocked verifies the hash of child against the already verified -// hash of the parent to ensure the child is expected. verifyChild triggers a -// sentry panic if unexpected modifications to the file system are detected. In -// ErrorOnViolation mode it returns a linuxerr instead. -// -// Preconditions: -// - fs.renameMu must be locked. -// - d.dirMu must be locked. -func (fs *filesystem) verifyChildLocked(ctx context.Context, parent *dentry, child *dentry) (*dentry, error) { - vfsObj := fs.vfsfs.VirtualFilesystem() - - // Get the path to the child dentry. This is only used to provide path - // information in failure case. - childPath, err := vfsObj.PathnameWithDeleted(ctx, child.fs.rootDentry.lowerVD, child.lowerVD) - if err != nil { - return nil, err - } - - fs.verityMu.RLock() - defer fs.verityMu.RUnlock() - // Read the offset of the child from the extended attributes of the - // corresponding Merkle tree file. - // This is the offset of the hash for child in its parent's Merkle tree - // file. - off, err := vfsObj.GetXattrAt(ctx, fs.creds, &vfs.PathOperation{ - Root: child.lowerMerkleVD, - Start: child.lowerMerkleVD, - }, &vfs.GetXattrOptions{ - Name: merkleOffsetInParentXattr, - Size: sizeOfStringInt32, - }) - - // The Merkle tree file for the child should have been created and - // contains the expected xattrs. If the file or the xattr does not - // exist, it indicates unexpected modifications to the file system. - if linuxerr.Equals(linuxerr.ENOENT, err) || linuxerr.Equals(linuxerr.ENODATA, err) { - return nil, fs.alertIntegrityViolation(fmt.Sprintf("Failed to get xattr %s for %s: %v", merkleOffsetInParentXattr, childPath, err)) - } - if err != nil { - return nil, err - } - // The offset xattr should be an integer. If it's not, it indicates - // unexpected modifications to the file system. - offset, err := strconv.Atoi(off) - if err != nil { - return nil, fs.alertIntegrityViolation(fmt.Sprintf("Failed to convert xattr %s for %s to int: %v", merkleOffsetInParentXattr, childPath, err)) - } - - // Open parent Merkle tree file to read and verify child's hash. - parentMerkleFD, err := vfsObj.OpenAt(ctx, fs.creds, &vfs.PathOperation{ - Root: parent.lowerMerkleVD, - Start: parent.lowerMerkleVD, - }, &vfs.OpenOptions{ - Flags: linux.O_RDONLY, - }) - - // The parent Merkle tree file should have been created. If it's - // missing, it indicates an unexpected modification to the file system. - if linuxerr.Equals(linuxerr.ENOENT, err) { - return nil, fs.alertIntegrityViolation(fmt.Sprintf("Failed to open parent Merkle file for %s: %v", childPath, err)) - } - if err != nil { - return nil, err - } - - defer parentMerkleFD.DecRef(ctx) - - // dataSize is the size of raw data for the Merkle tree. For a file, - // dataSize is the size of the whole file. For a directory, dataSize is - // the size of all its children's hashes. - dataSize, err := parentMerkleFD.GetXattr(ctx, &vfs.GetXattrOptions{ - Name: merkleSizeXattr, - Size: sizeOfStringInt32, - }) - - // The Merkle tree file for the child should have been created and - // contains the expected xattrs. If the file or the xattr does not - // exist, it indicates unexpected modifications to the file system. - if linuxerr.Equals(linuxerr.ENOENT, err) || linuxerr.Equals(linuxerr.ENODATA, err) { - return nil, fs.alertIntegrityViolation(fmt.Sprintf("Failed to get xattr %s for %s: %v", merkleSizeXattr, childPath, err)) - } - if err != nil { - return nil, err - } - - // The dataSize xattr should be an integer. If it's not, it indicates - // unexpected modifications to the file system. - parentSize, err := strconv.Atoi(dataSize) - if err != nil { - return nil, fs.alertIntegrityViolation(fmt.Sprintf("Failed to convert xattr %s for %s to int: %v", merkleSizeXattr, childPath, err)) - } - - fdReader := FileReadWriteSeeker{ - FD: parentMerkleFD, - Ctx: ctx, - } - - parentStat, err := vfsObj.StatAt(ctx, fs.creds, &vfs.PathOperation{ - Root: parent.lowerVD, - Start: parent.lowerVD, - }, &vfs.StatOptions{}) - if linuxerr.Equals(linuxerr.ENOENT, err) { - return nil, fs.alertIntegrityViolation(fmt.Sprintf("Failed to get parent stat for %s: %v", childPath, err)) - } - if err != nil { - return nil, err - } - - // Since we are verifying against a directory Merkle tree, buf should - // contain the hash of the children in the parent Merkle tree when - // Verify returns with success. - var buf bytes.Buffer - parent.hashMu.RLock() - _, err = merkletree.Verify(&merkletree.VerifyParams{ - Out: &buf, - File: &fdReader, - Tree: &fdReader, - Size: int64(parentSize), - Name: parent.name, - Mode: uint32(parentStat.Mode), - UID: parentStat.UID, - GID: parentStat.GID, - Children: parent.childrenList, - HashAlgorithms: fs.alg.toLinuxHashAlg(), - ReadOffset: int64(offset), - ReadSize: int64(merkletree.DigestSize(fs.alg.toLinuxHashAlg())), - Expected: parent.hash, - DataAndTreeInSameFile: true, - }) - parent.hashMu.RUnlock() - if err != nil && err != io.EOF { - return nil, fs.alertIntegrityViolation(fmt.Sprintf("Verification for %s failed: %v", childPath, err)) - } - - // Cache child hash when it's verified the first time. - child.hashMu.Lock() - if len(child.hash) == 0 { - child.hash = buf.Bytes() - } - child.hashMu.Unlock() - return child, nil -} - -// verifyStatAndChildrenLocked verifies the stat and children names against the -// verified hash. The mode/uid/gid and childrenNames of the file is cached -// after verified. -// -// Preconditions: d.dirMu must be locked. -func (fs *filesystem) verifyStatAndChildrenLocked(ctx context.Context, d *dentry, stat linux.Statx) error { - vfsObj := fs.vfsfs.VirtualFilesystem() - - // Get the path to the child dentry. This is only used to provide path - // information in failure case. - childPath, err := vfsObj.PathnameWithDeleted(ctx, d.fs.rootDentry.lowerVD, d.lowerVD) - if err != nil { - return err - } - - fs.verityMu.RLock() - defer fs.verityMu.RUnlock() - - fd, err := vfsObj.OpenAt(ctx, fs.creds, &vfs.PathOperation{ - Root: d.lowerMerkleVD, - Start: d.lowerMerkleVD, - }, &vfs.OpenOptions{ - Flags: linux.O_RDONLY, - }) - if linuxerr.Equals(linuxerr.ENOENT, err) { - return fs.alertIntegrityViolation(fmt.Sprintf("Failed to open merkle file for %s: %v", childPath, err)) - } - if err != nil { - return err - } - - defer fd.DecRef(ctx) - - merkleSize, err := fd.GetXattr(ctx, &vfs.GetXattrOptions{ - Name: merkleSizeXattr, - Size: sizeOfStringInt32, - }) - - if linuxerr.Equals(linuxerr.ENODATA, err) { - return fs.alertIntegrityViolation(fmt.Sprintf("Failed to get xattr %s for merkle file of %s: %v", merkleSizeXattr, childPath, err)) - } - if err != nil { - return err - } - - size, err := strconv.Atoi(merkleSize) - if err != nil { - return fs.alertIntegrityViolation(fmt.Sprintf("Failed to convert xattr %s for %s to int: %v", merkleSizeXattr, childPath, err)) - } - - if d.isDir() && len(d.childrenNames) == 0 { - childrenOffString, err := fd.GetXattr(ctx, &vfs.GetXattrOptions{ - Name: childrenOffsetXattr, - Size: sizeOfStringInt32, - }) - - if linuxerr.Equals(linuxerr.ENODATA, err) { - return fs.alertIntegrityViolation(fmt.Sprintf("Failed to get xattr %s for merkle file of %s: %v", childrenOffsetXattr, childPath, err)) - } - if err != nil { - return err - } - childrenOffset, err := strconv.Atoi(childrenOffString) - if err != nil { - return fs.alertIntegrityViolation(fmt.Sprintf("Failed to convert xattr %s to int: %v", childrenOffsetXattr, err)) - } - - childrenSizeString, err := fd.GetXattr(ctx, &vfs.GetXattrOptions{ - Name: childrenSizeXattr, - Size: sizeOfStringInt32, - }) - - if linuxerr.Equals(linuxerr.ENODATA, err) { - return fs.alertIntegrityViolation(fmt.Sprintf("Failed to get xattr %s for merkle file of %s: %v", childrenSizeXattr, childPath, err)) - } - if err != nil { - return err - } - childrenSize, err := strconv.Atoi(childrenSizeString) - if err != nil { - return fs.alertIntegrityViolation(fmt.Sprintf("Failed to convert xattr %s to int: %v", childrenSizeXattr, err)) - } - - childrenNames := make([]byte, childrenSize) - if _, err := fd.PRead(ctx, usermem.BytesIOSequence(childrenNames), int64(childrenOffset), vfs.ReadOptions{}); err != nil { - return fs.alertIntegrityViolation(fmt.Sprintf("Failed to read children map for %s: %v", childPath, err)) - } - - if err := json.Unmarshal(childrenNames, &d.childrenNames); err != nil { - return fs.alertIntegrityViolation(fmt.Sprintf("Failed to deserialize childrenNames of %s: %v", childPath, err)) - } - } - - fdReader := FileReadWriteSeeker{ - FD: fd, - Ctx: ctx, - } - - var buf bytes.Buffer - d.hashMu.RLock() - - d.generateChildrenList() - - params := &merkletree.VerifyParams{ - Out: &buf, - Tree: &fdReader, - Size: int64(size), - Name: d.name, - Mode: uint32(stat.Mode), - UID: stat.UID, - GID: stat.GID, - Children: d.childrenList, - HashAlgorithms: fs.alg.toLinuxHashAlg(), - ReadOffset: 0, - // Set read size to 0 so only the metadata is verified. - ReadSize: 0, - Expected: d.hash, - DataAndTreeInSameFile: false, - } - d.hashMu.RUnlock() - if d.mode.Load()&linux.S_IFMT == linux.S_IFDIR { - params.DataAndTreeInSameFile = true - } - - if d.isSymlink() { - target, err := vfsObj.ReadlinkAt(ctx, d.fs.creds, &vfs.PathOperation{ - Root: d.lowerVD, - Start: d.lowerVD, - }) - if err != nil { - return err - } - params.SymlinkTarget = target - } - - if _, err := merkletree.Verify(params); err != nil && err != io.EOF { - return fs.alertIntegrityViolation(fmt.Sprintf("Verification stat for %s failed: %v", childPath, err)) - } - d.mode.Store(uint32(stat.Mode)) - d.uid.Store(stat.UID) - d.gid.Store(stat.GID) - d.size = uint32(size) - d.symlinkTarget = params.SymlinkTarget - return nil -} - -// Preconditions: -// - fs.renameMu must be locked. -// - parent.dirMu must be locked. -func (fs *filesystem) getChildLocked(ctx context.Context, parent *dentry, name string, ds **[]*dentry) (*dentry, error) { - if child, ok := parent.children[name]; ok { - // If verity is enabled on child, we should check again whether - // the file and the corresponding Merkle tree are as expected, - // in order to catch deletion/renaming after the last time it's - // accessed. - if child.verityEnabled() { - vfsObj := fs.vfsfs.VirtualFilesystem() - // Get the path to the child dentry. This is only used - // to provide path information in failure case. - path, err := vfsObj.PathnameWithDeleted(ctx, child.fs.rootDentry.lowerVD, child.lowerVD) - if err != nil { - return nil, err - } - - childVD, err := parent.getLowerAt(ctx, vfsObj, name) - if linuxerr.Equals(linuxerr.ENOENT, err) { - // The file was previously accessed. If the - // file does not exist now, it indicates an - // unexpected modification to the file system. - return nil, fs.alertIntegrityViolation(fmt.Sprintf("Target file %s is expected but missing", path)) - } - if err != nil { - return nil, err - } - defer childVD.DecRef(ctx) - - childMerkleVD, err := parent.getLowerAt(ctx, vfsObj, merklePrefix+name) - // The Merkle tree file was previous accessed. If it - // does not exist now, it indicates an unexpected - // modification to the file system. - if linuxerr.Equals(linuxerr.ENOENT, err) { - return nil, fs.alertIntegrityViolation(fmt.Sprintf("Expected Merkle file for target %s but none found", path)) - } - if err != nil { - return nil, err - } - - defer childMerkleVD.DecRef(ctx) - } - - // If enabling verification on files/directories is not allowed - // during runtime, all cached children are already verified. If - // runtime enable is allowed and the parent directory is - // enabled, we should verify the child hash here because it may - // be cached before enabled. - if fs.allowRuntimeEnable { - if parent.verityEnabled() { - if _, err := fs.verifyChildLocked(ctx, parent, child); err != nil { - return nil, err - } - } - if child.verityEnabled() { - vfsObj := fs.vfsfs.VirtualFilesystem() - mask := uint32(linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_UID | linux.STATX_GID) - stat, err := vfsObj.StatAt(ctx, fs.creds, &vfs.PathOperation{ - Root: child.lowerVD, - Start: child.lowerVD, - }, &vfs.StatOptions{ - Mask: mask, - }) - if err != nil { - return nil, err - } - if err := fs.verifyStatAndChildrenLocked(ctx, child, stat); err != nil { - return nil, err - } - } - } - return child, nil - } - child, err := fs.lookupAndVerifyLocked(ctx, parent, name) - if err != nil { - return nil, err - } - if parent.children == nil { - parent.children = make(map[string]*dentry) - } - parent.children[name] = child - // child's refcount is initially 0, so it may be dropped after traversal. - *ds = appendDentry(*ds, child) - return child, nil -} - -// Preconditions: -// - fs.renameMu must be locked. -// - parent.dirMu must be locked. -func (fs *filesystem) lookupAndVerifyLocked(ctx context.Context, parent *dentry, name string) (*dentry, error) { - vfsObj := fs.vfsfs.VirtualFilesystem() - - if parent.verityEnabled() { - if _, ok := parent.childrenNames[name]; !ok { - return nil, linuxerr.ENOENT - } - } - - parentPath, err := vfsObj.PathnameWithDeleted(ctx, parent.fs.rootDentry.lowerVD, parent.lowerVD) - if err != nil { - return nil, err - } - - childVD, err := parent.getLowerAt(ctx, vfsObj, name) - if parent.verityEnabled() && linuxerr.Equals(linuxerr.ENOENT, err) { - return nil, fs.alertIntegrityViolation(fmt.Sprintf("file %s expected but not found", parentPath+"/"+name)) - } - if err != nil { - return nil, err - } - - // The dentry needs to be cleaned up if any error occurs. IncRef will be - // called if a verity child dentry is successfully created. - defer childVD.DecRef(ctx) - - childMerkleVD, err := parent.getLowerAt(ctx, vfsObj, merklePrefix+name) - if err != nil { - if linuxerr.Equals(linuxerr.ENOENT, err) { - if parent.verityEnabled() { - return nil, fs.alertIntegrityViolation(fmt.Sprintf("Merkle file for %s expected but not found", parentPath+"/"+name)) - } - childMerkleFD, err := vfsObj.OpenAt(ctx, fs.creds, &vfs.PathOperation{ - Root: parent.lowerVD, - Start: parent.lowerVD, - Path: fspath.Parse(merklePrefix + name), - }, &vfs.OpenOptions{ - Flags: linux.O_RDWR | linux.O_CREAT, - Mode: 0644, - }) - if err != nil { - return nil, err - } - childMerkleFD.DecRef(ctx) - childMerkleVD, err = parent.getLowerAt(ctx, vfsObj, merklePrefix+name) - if err != nil { - return nil, err - } - } else { - return nil, err - } - } - - // The dentry needs to be cleaned up if any error occurs. IncRef will be - // called if a verity child dentry is successfully created. - defer childMerkleVD.DecRef(ctx) - - mask := uint32(linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_UID | linux.STATX_GID) - stat, err := vfsObj.StatAt(ctx, fs.creds, &vfs.PathOperation{ - Root: childVD, - Start: childVD, - }, &vfs.StatOptions{ - Mask: mask, - }) - if err != nil { - return nil, err - } - - child := fs.newDentry() - child.lowerVD = childVD - child.lowerMerkleVD = childMerkleVD - - // Increase the reference for both childVD and childMerkleVD as they are - // held by child. If this function fails and the child is destroyed, the - // references will be decreased in destroyLocked. - childVD.IncRef() - childMerkleVD.IncRef() - - child.name = name - - child.mode = atomicbitops.FromUint32(uint32(stat.Mode)) - child.uid = atomicbitops.FromUint32(stat.UID) - child.gid = atomicbitops.FromUint32(stat.GID) - child.childrenNames = make(map[string]struct{}) - - // Verify child hash. This should always be performed unless in - // allowRuntimeEnable mode and the parent directory hasn't been enabled - // yet. - if parent.verityEnabled() { - if _, err := fs.verifyChildLocked(ctx, parent, child); err != nil { - child.destroyLocked(ctx) - return nil, err - } - } - if child.verityEnabled() { - if err := fs.verifyStatAndChildrenLocked(ctx, child, stat); err != nil { - child.destroyLocked(ctx) - return nil, err - } - } - - parent.IncRef() - child.parent = parent - - return child, nil -} - -// walkParentDirLocked resolves all but the last path component of rp to an -// existing directory, starting from the given directory (which is usually -// rp.Start().Impl().(*dentry)). It does not check that the returned directory -// is searchable by the provider of rp. -// -// Preconditions: -// - fs.renameMu must be locked. -// - !rp.Done(). -func (fs *filesystem) walkParentDirLocked(ctx context.Context, rp *vfs.ResolvingPath, d *dentry, ds **[]*dentry) (*dentry, error) { - for !rp.Final() { - d.dirMu.Lock() - next, err := fs.stepLocked(ctx, rp, d, true /* mayFollowSymlinks */, ds) - d.dirMu.Unlock() - if err != nil { - return nil, err - } - d = next - } - if !d.isDir() { - return nil, linuxerr.ENOTDIR - } - return d, nil -} - -// resolveLocked resolves rp to an existing file. -// -// Preconditions: fs.renameMu must be locked. -func (fs *filesystem) resolveLocked(ctx context.Context, rp *vfs.ResolvingPath, ds **[]*dentry) (*dentry, error) { - d := rp.Start().Impl().(*dentry) - for !rp.Done() { - d.dirMu.Lock() - next, err := fs.stepLocked(ctx, rp, d, true /* mayFollowSymlinks */, ds) - d.dirMu.Unlock() - if err != nil { - return nil, err - } - d = next - } - if rp.MustBeDir() && !d.isDir() { - return nil, linuxerr.ENOTDIR - } - return d, nil -} - -// AccessAt implements vfs.Filesystem.Impl.AccessAt. -func (fs *filesystem) AccessAt(ctx context.Context, rp *vfs.ResolvingPath, creds *auth.Credentials, ats vfs.AccessTypes) error { - // Verity file system is read-only. - if ats&vfs.MayWrite != 0 { - return linuxerr.EROFS - } - var ds *[]*dentry - fs.renameMu.RLock() - defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds) - d, err := fs.resolveLocked(ctx, rp, &ds) - if err != nil { - return err - } - return d.checkPermissions(creds, ats) -} - -// GetDentryAt implements vfs.FilesystemImpl.GetDentryAt. -func (fs *filesystem) GetDentryAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.GetDentryOptions) (*vfs.Dentry, error) { - var ds *[]*dentry - fs.renameMu.RLock() - defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds) - d, err := fs.resolveLocked(ctx, rp, &ds) - if err != nil { - return nil, err - } - if opts.CheckSearchable { - if !d.isDir() { - return nil, linuxerr.ENOTDIR - } - if err := d.checkPermissions(rp.Credentials(), vfs.MayExec); err != nil { - return nil, err - } - } - d.IncRef() - return &d.vfsd, nil -} - -// GetParentDentryAt implements vfs.FilesystemImpl.GetParentDentryAt. -func (fs *filesystem) GetParentDentryAt(ctx context.Context, rp *vfs.ResolvingPath) (*vfs.Dentry, error) { - var ds *[]*dentry - fs.renameMu.RLock() - defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds) - start := rp.Start().Impl().(*dentry) - d, err := fs.walkParentDirLocked(ctx, rp, start, &ds) - if err != nil { - return nil, err - } - d.IncRef() - return &d.vfsd, nil -} - -// LinkAt implements vfs.FilesystemImpl.LinkAt. -func (fs *filesystem) LinkAt(ctx context.Context, rp *vfs.ResolvingPath, vd vfs.VirtualDentry) error { - // Verity file system is read-only. - return linuxerr.EROFS -} - -// MkdirAt implements vfs.FilesystemImpl.MkdirAt. -func (fs *filesystem) MkdirAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.MkdirOptions) error { - // Verity file system is read-only. - return linuxerr.EROFS -} - -// MknodAt implements vfs.FilesystemImpl.MknodAt. -func (fs *filesystem) MknodAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.MknodOptions) error { - // Verity file system is read-only. - return linuxerr.EROFS -} - -// OpenAt implements vfs.FilesystemImpl.OpenAt. -func (fs *filesystem) OpenAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.OpenOptions) (*vfs.FileDescription, error) { - // Verity fs is read-only. - if opts.Flags&(linux.O_WRONLY|linux.O_CREAT) != 0 { - return nil, linuxerr.EROFS - } - - var ds *[]*dentry - fs.renameMu.RLock() - defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds) - - start := rp.Start().Impl().(*dentry) - if rp.Done() { - return start.openLocked(ctx, rp, &opts) - } - -afterTrailingSymlink: - parent, err := fs.walkParentDirLocked(ctx, rp, start, &ds) - if err != nil { - return nil, err - } - - // Check for search permission in the parent directory. - if err := parent.checkPermissions(rp.Credentials(), vfs.MayExec); err != nil { - return nil, err - } - - // Open existing child or follow symlink. - parent.dirMu.Lock() - child, err := fs.stepLocked(ctx, rp, parent, false /*mayFollowSymlinks*/, &ds) - parent.dirMu.Unlock() - if err != nil { - return nil, err - } - if child.isSymlink() && rp.ShouldFollowSymlink() { - target, err := child.readlink(ctx) - if err != nil { - return nil, err - } - if err := rp.HandleSymlink(target); err != nil { - return nil, err - } - start = parent - goto afterTrailingSymlink - } - return child.openLocked(ctx, rp, &opts) -} - -// Preconditions: fs.renameMu must be locked. -func (d *dentry) openLocked(ctx context.Context, rp *vfs.ResolvingPath, opts *vfs.OpenOptions) (*vfs.FileDescription, error) { - // Users should not open the Merkle tree files. Those are for verity fs - // use only. - if strings.Contains(d.name, merklePrefix) { - return nil, linuxerr.EPERM - } - ats := vfs.AccessTypesForOpenFlags(opts) - if err := d.checkPermissions(rp.Credentials(), ats); err != nil { - return nil, err - } - - // Verity fs is read-only. - if ats&vfs.MayWrite != 0 { - return nil, linuxerr.EROFS - } - - // Get the path to the target file. This is only used to provide path - // information in failure case. - path, err := d.fs.vfsfs.VirtualFilesystem().PathnameWithDeleted(ctx, d.fs.rootDentry.lowerVD, d.lowerVD) - if err != nil { - return nil, err - } - - tmpOpts := *opts - - // Open the lowerFD with O_PATH if a symlink is opened for verity. - if tmpOpts.Flags&linux.O_NOFOLLOW != 0 && d.isSymlink() { - tmpOpts.Flags |= linux.O_PATH - } - - // Open the file in the underlying file system. - lowerFD, err := rp.VirtualFilesystem().OpenAt(ctx, d.fs.creds, &vfs.PathOperation{ - Root: d.lowerVD, - Start: d.lowerVD, - }, &tmpOpts) - - // The file should exist, as we succeeded in finding its dentry. If it's - // missing, it indicates an unexpected modification to the file system. - if err != nil { - if linuxerr.Equals(linuxerr.ENOENT, err) { - return nil, d.fs.alertIntegrityViolation(fmt.Sprintf("File %s expected but not found", path)) - } - return nil, err - } - - // lowerFD needs to be cleaned up if any error occurs. IncRef will be - // called if a verity FD is successfully created. - defer lowerFD.DecRef(ctx) - - // Open the Merkle tree file corresponding to the current file/directory - // to be used later for verifying Read/Walk. - merkleReader, err := rp.VirtualFilesystem().OpenAt(ctx, d.fs.creds, &vfs.PathOperation{ - Root: d.lowerMerkleVD, - Start: d.lowerMerkleVD, - }, &vfs.OpenOptions{ - Flags: linux.O_RDONLY, - }) - - // The Merkle tree file should exist, as we succeeded in finding its - // dentry. If it's missing, it indicates an unexpected modification to - // the file system. - if err != nil { - if linuxerr.Equals(linuxerr.ENOENT, err) { - return nil, d.fs.alertIntegrityViolation(fmt.Sprintf("Merkle file for %s expected but not found", path)) - } - return nil, err - } - - // merkleReader needs to be cleaned up if any error occurs. IncRef will - // be called if a verity FD is successfully created. - defer merkleReader.DecRef(ctx) - - lowerFDOpts := lowerFD.Options() - var merkleWriter *vfs.FileDescription - var parentMerkleWriter *vfs.FileDescription - - // Only open the Merkle tree files for write if in allowRuntimeEnable - // mode. - if d.fs.allowRuntimeEnable { - merkleWriter, err = rp.VirtualFilesystem().OpenAt(ctx, d.fs.creds, &vfs.PathOperation{ - Root: d.lowerMerkleVD, - Start: d.lowerMerkleVD, - }, &vfs.OpenOptions{ - Flags: linux.O_WRONLY | linux.O_APPEND, - }) - if err != nil { - if linuxerr.Equals(linuxerr.ENOENT, err) { - return nil, d.fs.alertIntegrityViolation(fmt.Sprintf("Merkle file for %s expected but not found", path)) - } - return nil, err - } - // merkleWriter is cleaned up if any error occurs. IncRef will - // be called if a verity FD is created successfully. - defer merkleWriter.DecRef(ctx) - - if d.parent != nil { - parentMerkleWriter, err = rp.VirtualFilesystem().OpenAt(ctx, d.fs.creds, &vfs.PathOperation{ - Root: d.parent.lowerMerkleVD, - Start: d.parent.lowerMerkleVD, - }, &vfs.OpenOptions{ - Flags: linux.O_WRONLY | linux.O_APPEND, - }) - if err != nil { - if linuxerr.Equals(linuxerr.ENOENT, err) { - parentPath, _ := d.fs.vfsfs.VirtualFilesystem().PathnameWithDeleted(ctx, d.fs.rootDentry.lowerVD, d.parent.lowerVD) - return nil, d.fs.alertIntegrityViolation(fmt.Sprintf("Merkle file for %s expected but not found", parentPath)) - } - return nil, err - } - // parentMerkleWriter is cleaned up if any error occurs. IncRef - // will be called if a verity FD is created successfully. - defer parentMerkleWriter.DecRef(ctx) - } - } - - fd := &fileDescription{ - d: d, - lowerFD: lowerFD, - merkleReader: merkleReader, - merkleWriter: merkleWriter, - parentMerkleWriter: parentMerkleWriter, - isDir: d.isDir(), - } - - if err := fd.vfsfd.Init(fd, opts.Flags, rp.Mount(), &d.vfsd, &lowerFDOpts); err != nil { - return nil, err - } - lowerFD.IncRef() - merkleReader.IncRef() - if merkleWriter != nil { - merkleWriter.IncRef() - } - if parentMerkleWriter != nil { - parentMerkleWriter.IncRef() - } - return &fd.vfsfd, err -} - -// ReadlinkAt implements vfs.FilesystemImpl.ReadlinkAt. -func (fs *filesystem) ReadlinkAt(ctx context.Context, rp *vfs.ResolvingPath) (string, error) { - var ds *[]*dentry - fs.renameMu.RLock() - defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds) - d, err := fs.resolveLocked(ctx, rp, &ds) - if err != nil { - return "", err - } - return d.readlink(ctx) -} - -// RenameAt implements vfs.FilesystemImpl.RenameAt. -func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldParentVD vfs.VirtualDentry, oldName string, opts vfs.RenameOptions) error { - // Verity file system is read-only. - return linuxerr.EROFS -} - -// RmdirAt implements vfs.FilesystemImpl.RmdirAt. -func (fs *filesystem) RmdirAt(ctx context.Context, rp *vfs.ResolvingPath) error { - // Verity file system is read-only. - return linuxerr.EROFS -} - -// SetStatAt implements vfs.FilesystemImpl.SetStatAt. -func (fs *filesystem) SetStatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.SetStatOptions) error { - // Verity file system is read-only. - return linuxerr.EROFS -} - -// StatAt implements vfs.FilesystemImpl.StatAt. -func (fs *filesystem) StatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.StatOptions) (linux.Statx, error) { - var ds *[]*dentry - fs.renameMu.RLock() - defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds) - d, err := fs.resolveLocked(ctx, rp, &ds) - if err != nil { - return linux.Statx{}, err - } - - var stat linux.Statx - stat, err = fs.vfsfs.VirtualFilesystem().StatAt(ctx, fs.creds, &vfs.PathOperation{ - Root: d.lowerVD, - Start: d.lowerVD, - }, &opts) - if err != nil { - return linux.Statx{}, err - } - d.dirMu.Lock() - if d.verityEnabled() { - if err := fs.verifyStatAndChildrenLocked(ctx, d, stat); err != nil { - return linux.Statx{}, err - } - } - d.dirMu.Unlock() - return stat, nil -} - -// StatFSAt implements vfs.FilesystemImpl.StatFSAt. -func (fs *filesystem) StatFSAt(ctx context.Context, rp *vfs.ResolvingPath) (linux.Statfs, error) { - // TODO(b/159261227): Implement StatFSAt. - return linux.Statfs{}, nil -} - -// SymlinkAt implements vfs.FilesystemImpl.SymlinkAt. -func (fs *filesystem) SymlinkAt(ctx context.Context, rp *vfs.ResolvingPath, target string) error { - // Verity file system is read-only. - return linuxerr.EROFS -} - -// UnlinkAt implements vfs.FilesystemImpl.UnlinkAt. -func (fs *filesystem) UnlinkAt(ctx context.Context, rp *vfs.ResolvingPath) error { - // Verity file system is read-only. - return linuxerr.EROFS -} - -// BoundEndpointAt implements vfs.FilesystemImpl.BoundEndpointAt. -func (fs *filesystem) BoundEndpointAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.BoundEndpointOptions) (transport.BoundEndpoint, error) { - var ds *[]*dentry - fs.renameMu.RLock() - defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds) - if _, err := fs.resolveLocked(ctx, rp, &ds); err != nil { - return nil, err - } - return nil, linuxerr.ECONNREFUSED -} - -// ListXattrAt implements vfs.FilesystemImpl.ListXattrAt. -func (fs *filesystem) ListXattrAt(ctx context.Context, rp *vfs.ResolvingPath, size uint64) ([]string, error) { - var ds *[]*dentry - fs.renameMu.RLock() - defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds) - d, err := fs.resolveLocked(ctx, rp, &ds) - if err != nil { - return nil, err - } - lowerVD := d.lowerVD - return fs.vfsfs.VirtualFilesystem().ListXattrAt(ctx, d.fs.creds, &vfs.PathOperation{ - Root: lowerVD, - Start: lowerVD, - }, size) -} - -// GetXattrAt implements vfs.FilesystemImpl.GetXattrAt. -func (fs *filesystem) GetXattrAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.GetXattrOptions) (string, error) { - var ds *[]*dentry - fs.renameMu.RLock() - defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds) - d, err := fs.resolveLocked(ctx, rp, &ds) - if err != nil { - return "", err - } - lowerVD := d.lowerVD - return fs.vfsfs.VirtualFilesystem().GetXattrAt(ctx, d.fs.creds, &vfs.PathOperation{ - Root: lowerVD, - Start: lowerVD, - }, &opts) -} - -// SetXattrAt implements vfs.FilesystemImpl.SetXattrAt. -func (fs *filesystem) SetXattrAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.SetXattrOptions) error { - // Verity file system is read-only. - return linuxerr.EROFS -} - -// RemoveXattrAt implements vfs.FilesystemImpl.RemoveXattrAt. -func (fs *filesystem) RemoveXattrAt(ctx context.Context, rp *vfs.ResolvingPath, name string) error { - // Verity file system is read-only. - return linuxerr.EROFS -} - -// PrependPath implements vfs.FilesystemImpl.PrependPath. -func (fs *filesystem) PrependPath(ctx context.Context, vfsroot, vd vfs.VirtualDentry, b *fspath.Builder) error { - fs.renameMu.RLock() - defer fs.renameMu.RUnlock() - mnt := vd.Mount() - d := vd.Dentry().Impl().(*dentry) - for { - if mnt == vfsroot.Mount() && &d.vfsd == vfsroot.Dentry() { - return vfs.PrependPathAtVFSRootError{} - } - if &d.vfsd == mnt.Root() { - return nil - } - if d.parent == nil { - return vfs.PrependPathAtNonMountRootError{} - } - b.PrependComponent(d.name) - d = d.parent - } -} diff --git a/pkg/sentry/fsimpl/verity/save_restore.go b/pkg/sentry/fsimpl/verity/save_restore.go deleted file mode 100644 index 95fd34792..000000000 --- a/pkg/sentry/fsimpl/verity/save_restore.go +++ /dev/null @@ -1,25 +0,0 @@ -// 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 ( - "gvisor.dev/gvisor/pkg/refsvfs2" -) - -func (d *dentry) afterLoad() { - if d.refs.Load() != -1 { - refsvfs2.Register(d) - } -} diff --git a/pkg/sentry/fsimpl/verity/verity.go b/pkg/sentry/fsimpl/verity/verity.go deleted file mode 100644 index 9f0b01b67..000000000 --- a/pkg/sentry/fsimpl/verity/verity.go +++ /dev/null @@ -1,1696 +0,0 @@ -// 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 provides a filesystem implementation that is a wrapper of -// another file system. -// The verity file system provides integrity check for the underlying file -// system by providing verification for path traversals and each read. -// The verity file system is read-only, except for one case: when -// allowRuntimeEnable is true, additional Merkle files can be generated using -// the FS_IOC_ENABLE_VERITY ioctl. -// -// Lock order: -// -// filesystem.renameMu -// dentry.cachingMu -// filesystem.cacheMu -// dentry.dirMu -// fileDescription.mu -// filesystem.verityMu -// dentry.hashMu -// -// Locking dentry.dirMu in multiple dentries requires that parent dentries are -// locked before child dentries, and that filesystem.renameMu is locked to -// stabilize this relationship. -package verity - -import ( - "bytes" - "encoding/hex" - "encoding/json" - "fmt" - "math" - "sort" - "strconv" - "strings" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/atomicbitops" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fspath" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/marshal/primitive" - "gvisor.dev/gvisor/pkg/merkletree" - "gvisor.dev/gvisor/pkg/refsvfs2" - "gvisor.dev/gvisor/pkg/safemem" - "gvisor.dev/gvisor/pkg/sentry/arch" - fslock "gvisor.dev/gvisor/pkg/sentry/fs/lock" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" -) - -const ( - // Name is the default filesystem name. - Name = "verity" - - // merklePrefix is the prefix of the Merkle tree files. For example, the Merkle - // tree file for "/foo" is "/.merkle.verity.foo". - merklePrefix = ".merkle.verity." - - // merkleRootPrefix is the prefix of the Merkle tree root file. This - // needs to be different from merklePrefix to avoid name collision. - merkleRootPrefix = ".merkleroot.verity." - - // merkleOffsetInParentXattr is the extended attribute name specifying the - // offset of the child hash in its parent's Merkle tree. - merkleOffsetInParentXattr = "user.merkle.offset" - - // merkleSizeXattr is the extended attribute name specifying the size of data - // hashed by the corresponding Merkle tree. For a regular file, this is the - // file size. For a directory, this is the size of all its children's hashes. - merkleSizeXattr = "user.merkle.size" - - // childrenOffsetXattr is the extended attribute name specifying the - // names of the offset of the serialized children names in the Merkle - // tree file. - childrenOffsetXattr = "user.merkle.childrenOffset" - - // childrenSizeXattr is the extended attribute name specifying the size - // of the serialized children names. - childrenSizeXattr = "user.merkle.childrenSize" - - // sizeOfStringInt32 is the size for a 32 bit integer stored as string in - // extended attributes. The maximum value of a 32 bit integer has 10 digits. - sizeOfStringInt32 = 10 - - // defaultMaxCachedDentries is the default limit of dentry cache. - defaultMaxCachedDentries = uint64(1000) -) - -var ( - // verityMu synchronizes concurrent operations that enable verity and perform - // verification checks. - verityMu sync.RWMutex -) - -// Mount option names for verityfs. -const ( - moptLowerPath = "lower_path" - moptRootHash = "root_hash" - moptRootName = "root_name" - moptDentryCacheLimit = "dentry_cache_limit" -) - -// HashAlgorithm is a type specifying the algorithm used to hash the file -// content. -type HashAlgorithm int - -// ViolationAction is a type specifying the action when an integrity violation -// is detected. -type ViolationAction int - -const ( - // PanicOnViolation terminates the sentry on detected violation. - PanicOnViolation ViolationAction = 0 - // ErrorOnViolation returns an error from the violating system call on - // detected violation. - ErrorOnViolation = 1 -) - -// Currently supported hashing algorithms include SHA256 and SHA512. -const ( - SHA256 HashAlgorithm = iota - SHA512 -) - -func (alg HashAlgorithm) toLinuxHashAlg() int { - switch alg { - case SHA256: - return linux.FS_VERITY_HASH_ALG_SHA256 - case SHA512: - return linux.FS_VERITY_HASH_ALG_SHA512 - default: - return 0 - } -} - -// FilesystemType implements vfs.FilesystemType. -// -// +stateify savable -type FilesystemType struct{} - -// filesystem implements vfs.FilesystemImpl. -// -// +stateify savable -type filesystem struct { - vfsfs vfs.Filesystem - - // creds is a copy of the filesystem's creator's credentials, which are - // used for accesses to the underlying file system. creds is immutable. - creds *auth.Credentials - - // allowRuntimeEnable is true if using ioctl with FS_IOC_ENABLE_VERITY - // to build Merkle trees in the verity file system is allowed. If this - // is false, no new Merkle trees can be built, and only the files that - // had Merkle trees before startup (e.g. from a host filesystem mounted - // with gofer fs) can be verified. - allowRuntimeEnable bool - - // lowerMount is the underlying file system mount. - lowerMount *vfs.Mount - - // rootDentry is the mount root Dentry for this file system, which - // stores the root hash of the whole file system in bytes. - rootDentry *dentry - - // alg is the algorithms used to hash the files in the verity file - // system. - alg HashAlgorithm - - // action specifies the action towards detected violation. - action ViolationAction - - // opts is the string mount options passed to opts.Data. - opts string - - // renameMu synchronizes renaming with non-renaming operations in order - // to ensure consistent lock ordering between dentry.dirMu in different - // dentries. - renameMu sync.RWMutex `state:"nosave"` - - // cachedDentries contains all dentries with 0 references. (Due to race - // conditions, it may also contain dentries with non-zero references.) - // cachedDentriesLen is the number of dentries in cachedDentries. These - // fields are protected by cacheMu. - cacheMu sync.Mutex `state:"nosave"` - cachedDentries dentryList - cachedDentriesLen uint64 - - // maxCachedDentries is the maximum size of filesystem.cachedDentries. - maxCachedDentries uint64 - - // verityMu synchronizes enabling verity files, protects files or - // directories from being enabled by different threads simultaneously. - // It also ensures that verity does not access files that are being - // enabled. - // - // Also, the directory Merkle trees depends on the generated trees of - // its children. So they shouldn't be enabled the same time. This lock - // is for the whole file system to ensure that no more than one file is - // enabled the same time. - verityMu sync.RWMutex `state:"nosave"` - - // released is nonzero once filesystem.Release has been called. - released atomicbitops.Int32 -} - -// InternalFilesystemOptions may be passed as -// vfs.GetFilesystemOptions.InternalData to FilesystemType.GetFilesystem. -// -// +stateify savable -type InternalFilesystemOptions struct { - // LowerName is the name of the filesystem wrapped by verity fs. - LowerName string - - // Alg is the algorithms used to hash the files in the verity file - // system. - Alg HashAlgorithm - - // AllowRuntimeEnable specifies whether the verity file system allows - // enabling verification for files (i.e. building Merkle trees) during - // runtime. - AllowRuntimeEnable bool - - // LowerGetFSOptions is the file system option for the lower layer file - // system wrapped by verity file system. - LowerGetFSOptions vfs.GetFilesystemOptions - - // Action specifies the action on an integrity violation. - Action ViolationAction -} - -// Name implements vfs.FilesystemType.Name. -func (FilesystemType) Name() string { - return Name -} - -// Release implements vfs.FilesystemType.Release. -func (FilesystemType) Release(ctx context.Context) {} - -// alertIntegrityViolation alerts a violation of integrity, which usually means -// unexpected modification to the file system is detected. In ErrorOnViolation -// mode, it returns EIO, otherwise it panic. -func (fs *filesystem) alertIntegrityViolation(msg string) error { - if fs.action == ErrorOnViolation { - return linuxerr.EIO - } - panic(msg) -} - -// GetFilesystem implements vfs.FilesystemType.GetFilesystem. -func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opts vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) { - mopts := vfs.GenericParseMountOptions(opts.Data) - var rootHash []byte - if encodedRootHash, ok := mopts[moptRootHash]; ok { - delete(mopts, moptRootHash) - hash, err := hex.DecodeString(encodedRootHash) - if err != nil { - ctx.Warningf("verity.FilesystemType.GetFilesystem: Failed to decode root hash: %v", err) - return nil, nil, linuxerr.EINVAL - } - rootHash = hash - } - var lowerPathname string - if path, ok := mopts[moptLowerPath]; ok { - delete(mopts, moptLowerPath) - lowerPathname = path - } - rootName := "root" - if root, ok := mopts[moptRootName]; ok { - delete(mopts, moptRootName) - rootName = root - } - maxCachedDentries := defaultMaxCachedDentries - if str, ok := mopts[moptDentryCacheLimit]; ok { - delete(mopts, moptDentryCacheLimit) - maxCD, err := strconv.ParseUint(str, 10, 64) - if err != nil { - ctx.Warningf("verity.FilesystemType.GetFilesystem: invalid dentry cache limit: %s=%s", moptDentryCacheLimit, str) - return nil, nil, linuxerr.EINVAL - } - maxCachedDentries = maxCD - } - - // Check for unparsed options. - if len(mopts) != 0 { - ctx.Warningf("verity.FilesystemType.GetFilesystem: unknown options: %v", mopts) - return nil, nil, linuxerr.EINVAL - } - - // Handle internal options. - iopts, ok := opts.InternalData.(InternalFilesystemOptions) - if len(lowerPathname) == 0 && !ok { - ctx.Warningf("verity.FilesystemType.GetFilesystem: missing verity configs") - return nil, nil, linuxerr.EINVAL - } - if len(lowerPathname) != 0 { - if ok { - ctx.Warningf("verity.FilesystemType.GetFilesystem: unexpected verity configs with specified lower path") - return nil, nil, linuxerr.EINVAL - } - iopts = InternalFilesystemOptions{ - AllowRuntimeEnable: len(rootHash) == 0, - Action: ErrorOnViolation, - } - } - - var lowerMount *vfs.Mount - var mountedLowerVD vfs.VirtualDentry - // Use an existing mount if lowerPath is provided. - if len(lowerPathname) != 0 { - vfsroot := vfs.RootFromContext(ctx) - if vfsroot.Ok() { - defer vfsroot.DecRef(ctx) - } - lowerPath := fspath.Parse(lowerPathname) - if !lowerPath.Absolute { - ctx.Infof("verity.FilesystemType.GetFilesystem: lower_path %q must be absolute", lowerPathname) - return nil, nil, linuxerr.EINVAL - } - var err error - mountedLowerVD, err = vfsObj.GetDentryAt(ctx, creds, &vfs.PathOperation{ - Root: vfsroot, - Start: vfsroot, - Path: lowerPath, - FollowFinalSymlink: true, - }, &vfs.GetDentryOptions{ - CheckSearchable: true, - }) - if err != nil { - ctx.Infof("verity.FilesystemType.GetFilesystem: failed to resolve lower_path %q: %v", lowerPathname, err) - return nil, nil, err - } - lowerMount = mountedLowerVD.Mount() - defer mountedLowerVD.DecRef(ctx) - } else { - // Mount the lower file system. The lower file system is wrapped inside - // verity, and should not be exposed or connected. - mountOpts := &vfs.MountOptions{ - GetFilesystemOptions: iopts.LowerGetFSOptions, - InternalMount: true, - } - mnt, err := vfsObj.MountDisconnected(ctx, creds, "", iopts.LowerName, mountOpts) - if err != nil { - return nil, nil, err - } - lowerMount = mnt - } - - fs := &filesystem{ - creds: creds.Fork(), - alg: iopts.Alg, - lowerMount: lowerMount, - action: iopts.Action, - opts: opts.Data, - allowRuntimeEnable: iopts.AllowRuntimeEnable, - maxCachedDentries: maxCachedDentries, - } - fs.vfsfs.Init(vfsObj, &fstype, fs) - - // Construct the root dentry. - d := fs.newDentry() - // Set the root's reference count to 2. One reference is returned to - // the caller, and the other is held by fs to prevent the root from - // being "cached" and subsequently evicted. - d.refs = atomicbitops.FromInt64(2) - lowerVD := vfs.MakeVirtualDentry(lowerMount, lowerMount.Root()) - lowerVD.IncRef() - d.lowerVD = lowerVD - - rootMerkleName := merkleRootPrefix + rootName - - lowerMerkleVD, err := vfsObj.GetDentryAt(ctx, fs.creds, &vfs.PathOperation{ - Root: lowerVD, - Start: lowerVD, - Path: fspath.Parse(rootMerkleName), - }, &vfs.GetDentryOptions{}) - - // If runtime enable is allowed, the root merkle tree may be absent. We - // should create the tree file. - if linuxerr.Equals(linuxerr.ENOENT, err) && fs.allowRuntimeEnable { - lowerMerkleFD, err := vfsObj.OpenAt(ctx, fs.creds, &vfs.PathOperation{ - Root: lowerVD, - Start: lowerVD, - Path: fspath.Parse(rootMerkleName), - }, &vfs.OpenOptions{ - Flags: linux.O_RDWR | linux.O_CREAT, - Mode: 0644, - }) - if err != nil { - fs.vfsfs.DecRef(ctx) - d.DecRef(ctx) - return nil, nil, err - } - lowerMerkleFD.DecRef(ctx) - lowerMerkleVD, err = vfsObj.GetDentryAt(ctx, fs.creds, &vfs.PathOperation{ - Root: lowerVD, - Start: lowerVD, - Path: fspath.Parse(rootMerkleName), - }, &vfs.GetDentryOptions{}) - if err != nil { - fs.vfsfs.DecRef(ctx) - d.DecRef(ctx) - return nil, nil, err - } - } else if err != nil { - // Failed to get dentry for the root Merkle file. This - // indicates an unexpected modification that removed/renamed - // the root Merkle file, or it's never generated. - fs.vfsfs.DecRef(ctx) - d.DecRef(ctx) - return nil, nil, fs.alertIntegrityViolation("Failed to find root Merkle file") - } - - // Clear the Merkle tree file if they are to be generated at runtime. - // TODO(b/182315468): Optimize the Merkle tree generate process to - // allow only updating certain files/directories. - if fs.allowRuntimeEnable { - lowerMerkleFD, err := vfsObj.OpenAt(ctx, fs.creds, &vfs.PathOperation{ - Root: lowerMerkleVD, - Start: lowerMerkleVD, - }, &vfs.OpenOptions{ - Flags: linux.O_RDWR | linux.O_TRUNC, - Mode: 0644, - }) - if err != nil { - return nil, nil, err - } - lowerMerkleFD.DecRef(ctx) - } - - d.lowerMerkleVD = lowerMerkleVD - - // Get metadata from the underlying file system. - const statMask = linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_UID | linux.STATX_GID - stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{ - Root: lowerVD, - Start: lowerVD, - }, &vfs.StatOptions{ - Mask: statMask, - }) - if err != nil { - fs.vfsfs.DecRef(ctx) - d.DecRef(ctx) - return nil, nil, err - } - - d.mode = atomicbitops.FromUint32(uint32(stat.Mode)) - d.uid = atomicbitops.FromUint32(stat.UID) - d.gid = atomicbitops.FromUint32(stat.GID) - d.childrenNames = make(map[string]struct{}) - - d.hashMu.Lock() - d.hash = make([]byte, len(rootHash)) - copy(d.hash, rootHash) - d.hashMu.Unlock() - - fs.rootDentry = d - - if !d.isDir() { - ctx.Warningf("verity root must be a directory") - return nil, nil, linuxerr.EINVAL - } - - if !fs.allowRuntimeEnable { - // Get children names from the underlying file system. - offString, err := vfsObj.GetXattrAt(ctx, creds, &vfs.PathOperation{ - Root: lowerMerkleVD, - Start: lowerMerkleVD, - }, &vfs.GetXattrOptions{ - Name: childrenOffsetXattr, - Size: sizeOfStringInt32, - }) - if linuxerr.Equals(linuxerr.ENOENT, err) || linuxerr.Equals(linuxerr.ENODATA, err) { - return nil, nil, fs.alertIntegrityViolation(fmt.Sprintf("Failed to get xattr %s: %v", childrenOffsetXattr, err)) - } - if err != nil { - return nil, nil, err - } - - off, err := strconv.Atoi(offString) - if err != nil { - return nil, nil, fs.alertIntegrityViolation(fmt.Sprintf("Failed to convert xattr %s to int: %v", childrenOffsetXattr, err)) - } - - sizeString, err := vfsObj.GetXattrAt(ctx, creds, &vfs.PathOperation{ - Root: lowerMerkleVD, - Start: lowerMerkleVD, - }, &vfs.GetXattrOptions{ - Name: childrenSizeXattr, - Size: sizeOfStringInt32, - }) - if linuxerr.Equals(linuxerr.ENOENT, err) || linuxerr.Equals(linuxerr.ENODATA, err) { - return nil, nil, fs.alertIntegrityViolation(fmt.Sprintf("Failed to get xattr %s: %v", childrenSizeXattr, err)) - } - if err != nil { - return nil, nil, err - } - size, err := strconv.Atoi(sizeString) - if err != nil { - return nil, nil, fs.alertIntegrityViolation(fmt.Sprintf("Failed to convert xattr %s to int: %v", childrenSizeXattr, err)) - } - - lowerMerkleFD, err := vfsObj.OpenAt(ctx, fs.creds, &vfs.PathOperation{ - Root: lowerMerkleVD, - Start: lowerMerkleVD, - }, &vfs.OpenOptions{ - Flags: linux.O_RDONLY, - }) - if linuxerr.Equals(linuxerr.ENOENT, err) { - return nil, nil, fs.alertIntegrityViolation(fmt.Sprintf("Failed to open root Merkle file: %v", err)) - } - if err != nil { - return nil, nil, err - } - - defer lowerMerkleFD.DecRef(ctx) - - childrenNames := make([]byte, size) - if _, err := lowerMerkleFD.PRead(ctx, usermem.BytesIOSequence(childrenNames), int64(off), vfs.ReadOptions{}); err != nil { - return nil, nil, fs.alertIntegrityViolation(fmt.Sprintf("Failed to read root children map: %v", err)) - } - - if err := json.Unmarshal(childrenNames, &d.childrenNames); err != nil { - return nil, nil, fs.alertIntegrityViolation(fmt.Sprintf("Failed to deserialize childrenNames: %v", err)) - } - - if err := fs.verifyStatAndChildrenLocked(ctx, d, stat); err != nil { - return nil, nil, err - } - d.generateChildrenList() - } - - d.vfsd.Init(d) - - return &fs.vfsfs, &d.vfsd, nil -} - -// Release implements vfs.FilesystemImpl.Release. -func (fs *filesystem) Release(ctx context.Context) { - fs.released.Store(1) - fs.lowerMount.DecRef(ctx) - - fs.renameMu.Lock() - fs.evictAllCachedDentriesLocked(ctx) - fs.renameMu.Unlock() - - // An extra reference was held by the filesystem on the root to prevent - // it from being cached/evicted. - fs.rootDentry.DecRef(ctx) -} - -// MountOptions implements vfs.FilesystemImpl.MountOptions. -func (fs *filesystem) MountOptions() string { - return fs.opts -} - -// dentry implements vfs.DentryImpl. -// -// +stateify savable -type dentry struct { - vfsd vfs.Dentry - - // refs is the reference count. Each dentry holds a reference on its - // parent, even if disowned. When refs reaches 0, the dentry may be - // added to the cache or destroyed. If refs == -1, the dentry has - // already been destroyed. refs is accessed using atomic memory - // operations. - refs atomicbitops.Int64 - - // fs is the owning filesystem. fs is immutable. - fs *filesystem - - // mode, uid, gid and size are the file mode, owner, group, and size of - // the file in the underlying file system. They are set when a dentry - // is initialized, and never modified. - mode atomicbitops.Uint32 - uid atomicbitops.Uint32 - gid atomicbitops.Uint32 - size uint32 - - // parent is the dentry corresponding to this dentry's parent directory. - // name is this dentry's name in parent. If this dentry is a filesystem - // root, parent is nil and name is the empty string. parent and name are - // protected by fs.renameMu. - parent *dentry - name string - - // If this dentry represents a directory, children maps the names of - // children for which dentries have been instantiated to those dentries, - // and dirents (if not nil) is a cache of dirents as returned by - // directoryFDs representing this directory. children is protected by - // dirMu. - dirMu sync.Mutex `state:"nosave"` - children map[string]*dentry - - // childrenNames stores the name of all children of the dentry. This is - // used by verity to check whether a child is expected. This is only - // populated by enableVerity. childrenNames is also protected by dirMu. - childrenNames map[string]struct{} - - // childrenList is a complete sorted list of childrenNames. This list - // is generated when verity is enabled, or the first time the file is - // verified in non runtime enable mode. - childrenList []string - - // lowerVD is the VirtualDentry in the underlying file system. It is - // never modified after initialized. - lowerVD vfs.VirtualDentry - - // lowerMerkleVD is the VirtualDentry of the corresponding Merkle tree - // in the underlying file system. It is never modified after - // initialized. - lowerMerkleVD vfs.VirtualDentry - - // symlinkTarget is the target path of a symlink file in the underlying filesystem. - symlinkTarget string - - // hash is the calculated hash for the current file or directory. hash - // is protected by hashMu. - hashMu sync.RWMutex `state:"nosave"` - hash []byte - - // cachingMu is used to synchronize concurrent dentry caching attempts on - // this dentry. - cachingMu sync.Mutex `state:"nosave"` - - // If cached is true, dentryEntry links dentry into - // filesystem.cachedDentries. cached and dentryEntry are protected by - // cachingMu. - cached bool - dentryEntry -} - -// newDentry creates a new dentry representing the given verity file. The -// dentry initially has no references, but is not cached; it is the caller's -// responsibility to set the dentry's reference count and/or call -// dentry.destroy() as appropriate. The dentry is initially invalid in that it -// contains no underlying dentry; the caller is responsible for setting them. -func (fs *filesystem) newDentry() *dentry { - d := &dentry{ - fs: fs, - } - d.vfsd.Init(d) - refsvfs2.Register(d) - return d -} - -// IncRef implements vfs.DentryImpl.IncRef. -func (d *dentry) IncRef() { - r := d.refs.Add(1) - if d.LogRefs() { - refsvfs2.LogIncRef(d, r) - } -} - -// TryIncRef implements vfs.DentryImpl.TryIncRef. -func (d *dentry) TryIncRef() bool { - for { - r := d.refs.Load() - if r <= 0 { - return false - } - if d.refs.CompareAndSwap(r, r+1) { - if d.LogRefs() { - refsvfs2.LogTryIncRef(d, r+1) - } - return true - } - } -} - -// DecRef implements vfs.DentryImpl.DecRef. -func (d *dentry) DecRef(ctx context.Context) { - if d.decRefNoCaching() == 0 { - d.checkCachingLocked(ctx, false /* renameMuWriteLocked */) - } -} - -// decRefNoCaching decrements d's reference count without calling -// d.checkCachingLocked, even if d's reference count reaches 0; callers are -// responsible for ensuring that d.checkCachingLocked will be called later. -func (d *dentry) decRefNoCaching() int64 { - r := d.refs.Add(-1) - if d.LogRefs() { - refsvfs2.LogDecRef(d, r) - } - if r < 0 { - panic("verity.dentry.decRefNoCaching() called without holding a reference") - } - return r -} - -// destroyLocked destroys the dentry. -// -// Preconditions: -// - d.fs.renameMu must be locked for writing. -// - d.refs == 0. -func (d *dentry) destroyLocked(ctx context.Context) { - switch d.refs.Load() { - case 0: - // Mark the dentry destroyed. - d.refs.Store(-1) - case -1: - panic("verity.dentry.destroyLocked() called on already destroyed dentry") - default: - panic("verity.dentry.destroyLocked() called with references on the dentry") - } - - // Drop the reference held by d on its parent without recursively - // locking d.fs.renameMu. - if d.parent != nil && d.parent.decRefNoCaching() == 0 { - d.parent.checkCachingLocked(ctx, true /* renameMuWriteLocked */) - } - - if d.lowerVD.Ok() { - d.lowerVD.DecRef(ctx) - } - if d.lowerMerkleVD.Ok() { - d.lowerMerkleVD.DecRef(ctx) - } - if d.parent != nil { - d.parent.dirMu.Lock() - if !d.vfsd.IsDead() { - delete(d.parent.children, d.name) - } - d.parent.dirMu.Unlock() - } - refsvfs2.Unregister(d) -} - -// RefType implements refsvfs2.CheckedObject.Type. -func (d *dentry) RefType() string { - return "verity.dentry" -} - -// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. -func (d *dentry) LeakMessage() string { - return fmt.Sprintf("[verity.dentry %p] reference count of %d instead of -1", d, d.refs.Load()) -} - -// LogRefs implements refsvfs2.CheckedObject.LogRefs. -// -// This should only be set to true for debugging purposes, as it can generate an -// extremely large amount of output and drastically degrade performance. -func (d *dentry) LogRefs() bool { - return false -} - -// InotifyWithParent implements vfs.DentryImpl.InotifyWithParent. -func (d *dentry) InotifyWithParent(ctx context.Context, events, cookie uint32, et vfs.EventType) { - //TODO(b/159261227): Implement InotifyWithParent. -} - -// Watches implements vfs.DentryImpl.Watches. -func (d *dentry) Watches() *vfs.Watches { - //TODO(b/159261227): Implement Watches. - return nil -} - -// OnZeroWatches implements vfs.DentryImpl.OnZeroWatches. -func (d *dentry) OnZeroWatches(context.Context) { - //TODO(b/159261227): Implement OnZeroWatches. -} - -// checkCachingLocked should be called after d's reference count becomes 0 or -// it becomes disowned. -// -// For performance, checkCachingLocked can also be called after d's reference -// count becomes non-zero, so that d can be removed from the LRU cache. This -// may help in reducing the size of the cache and hence reduce evictions. Note -// that this is not necessary for correctness. -// -// It may be called on a destroyed dentry. For example, -// renameMu[R]UnlockAndCheckCaching may call checkCachingLocked multiple times -// for the same dentry when the dentry is visited more than once in the same -// operation. One of the calls may destroy the dentry, so subsequent calls will -// do nothing. -// -// Preconditions: d.fs.renameMu must be locked for writing if -// renameMuWriteLocked is true; it may be temporarily unlocked. -func (d *dentry) checkCachingLocked(ctx context.Context, renameMuWriteLocked bool) { - d.cachingMu.Lock() - refs := d.refs.Load() - if refs == -1 { - // Dentry has already been destroyed. - d.cachingMu.Unlock() - return - } - if refs > 0 { - // fs.cachedDentries is permitted to contain dentries with non-zero refs, - // which are skipped by fs.evictCachedDentryLocked() upon reaching the end - // of the LRU. But it is still beneficial to remove d from the cache as we - // are already holding d.cachingMu. Keeping a cleaner cache also reduces - // the number of evictions (which is expensive as it acquires fs.renameMu). - d.removeFromCacheLocked() - d.cachingMu.Unlock() - return - } - - if d.fs.released.Load() != 0 { - d.cachingMu.Unlock() - if !renameMuWriteLocked { - // Need to lock d.fs.renameMu to access d.parent. Lock it for writing as - // needed by d.destroyLocked() later. - d.fs.renameMu.Lock() - defer d.fs.renameMu.Unlock() - } - if d.parent != nil { - d.parent.dirMu.Lock() - delete(d.parent.children, d.name) - d.parent.dirMu.Unlock() - } - d.destroyLocked(ctx) // +checklocksforce: see above. - return - } - - d.fs.cacheMu.Lock() - // If d is already cached, just move it to the front of the LRU. - if d.cached { - d.fs.cachedDentries.Remove(d) - d.fs.cachedDentries.PushFront(d) - d.fs.cacheMu.Unlock() - d.cachingMu.Unlock() - return - } - // Cache the dentry, then evict the least recently used cached dentry if - // the cache becomes over-full. - d.fs.cachedDentries.PushFront(d) - d.fs.cachedDentriesLen++ - d.cached = true - shouldEvict := d.fs.cachedDentriesLen > d.fs.maxCachedDentries - d.fs.cacheMu.Unlock() - d.cachingMu.Unlock() - - if shouldEvict { - if !renameMuWriteLocked { - // Need to lock d.fs.renameMu for writing as needed by - // d.evictCachedDentryLocked(). - d.fs.renameMu.Lock() - defer d.fs.renameMu.Unlock() - } - d.fs.evictCachedDentryLocked(ctx) // +checklocksforce: see above. - } -} - -// Preconditions: d.cachingMu must be locked. -func (d *dentry) removeFromCacheLocked() { - if d.cached { - d.fs.cacheMu.Lock() - d.fs.cachedDentries.Remove(d) - d.fs.cachedDentriesLen-- - d.fs.cacheMu.Unlock() - d.cached = false - } -} - -// Precondition: fs.renameMu must be locked for writing; it may be temporarily -// unlocked. -// +checklocks:fs.renameMu -func (fs *filesystem) evictAllCachedDentriesLocked(ctx context.Context) { - for fs.cachedDentriesLen != 0 { - fs.evictCachedDentryLocked(ctx) - } -} - -// Preconditions: -// - fs.renameMu must be locked for writing; it may be temporarily unlocked. -// -// +checklocks:fs.renameMu -func (fs *filesystem) evictCachedDentryLocked(ctx context.Context) { - fs.cacheMu.Lock() - victim := fs.cachedDentries.Back() - fs.cacheMu.Unlock() - if victim == nil { - // fs.cachedDentries may have become empty between when it was - // checked and when we locked fs.cacheMu. - return - } - - victim.cachingMu.Lock() - victim.removeFromCacheLocked() - // victim.refs may have become non-zero from an earlier path resolution - // since it was inserted into fs.cachedDentries. - if victim.refs.Load() != 0 { - victim.cachingMu.Unlock() - return - } - if victim.parent != nil { - victim.parent.dirMu.Lock() - // Note that victim can't be a mount point (in any mount - // namespace), since VFS holds references on mount points. - fs.vfsfs.VirtualFilesystem().InvalidateDentry(ctx, &victim.vfsd) - delete(victim.parent.children, victim.name) - victim.parent.dirMu.Unlock() - } - victim.cachingMu.Unlock() - victim.destroyLocked(ctx) // +checklocksforce: owned as precondition, victim.fs == fs. -} - -func (d *dentry) isSymlink() bool { - return d.mode.Load()&linux.S_IFMT == linux.S_IFLNK -} - -func (d *dentry) isDir() bool { - return d.mode.Load()&linux.S_IFMT == linux.S_IFDIR -} - -func (d *dentry) checkPermissions(creds *auth.Credentials, ats vfs.AccessTypes) error { - return vfs.GenericCheckPermissions(creds, ats, linux.FileMode(d.mode.Load()), auth.KUID(d.uid.Load()), auth.KGID(d.gid.Load())) -} - -// verityEnabled checks whether the file is enabled with verity features. It -// should always be true if runtime enable is not allowed. In runtime enable -// mode, it returns true if the target has been enabled with -// ioctl(FS_IOC_ENABLE_VERITY). -func (d *dentry) verityEnabled() bool { - d.hashMu.RLock() - defer d.hashMu.RUnlock() - return !d.fs.allowRuntimeEnable || len(d.hash) != 0 -} - -// generateChildrenList generates a sorted childrenList from childrenNames, and -// cache it in d for hashing. -func (d *dentry) generateChildrenList() { - if len(d.childrenList) == 0 && len(d.childrenNames) != 0 { - for child := range d.childrenNames { - d.childrenList = append(d.childrenList, child) - } - sort.Strings(d.childrenList) - } -} - -// getLowerAt returns the dentry in the underlying file system, which is -// represented by filename relative to d. -func (d *dentry) getLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, filename string) (vfs.VirtualDentry, error) { - return vfsObj.GetDentryAt(ctx, d.fs.creds, &vfs.PathOperation{ - Root: d.lowerVD, - Start: d.lowerVD, - Path: fspath.Parse(filename), - }, &vfs.GetDentryOptions{}) -} - -func (d *dentry) readlink(ctx context.Context) (string, error) { - vfsObj := d.fs.vfsfs.VirtualFilesystem() - if d.verityEnabled() { - stat, err := vfsObj.StatAt(ctx, d.fs.creds, &vfs.PathOperation{ - Root: d.lowerVD, - Start: d.lowerVD, - }, &vfs.StatOptions{}) - if err != nil { - return "", err - } - d.dirMu.Lock() - defer d.dirMu.Unlock() - if err := d.fs.verifyStatAndChildrenLocked(ctx, d, stat); err != nil { - return "", err - } - return d.symlinkTarget, nil - } - - return d.fs.vfsfs.VirtualFilesystem().ReadlinkAt(ctx, d.fs.creds, &vfs.PathOperation{ - Root: d.lowerVD, - Start: d.lowerVD, - }) -} - -// FileDescription implements vfs.FileDescriptionImpl for verity fds. -// FileDescription is a wrapper of the underlying lowerFD, with support to build -// Merkle trees through the Linux fs-verity API to verify contents read from -// lowerFD. -// -// +stateify savable -type fileDescription struct { - vfsfd vfs.FileDescription - vfs.FileDescriptionDefaultImpl - - // d is the corresponding dentry to the fileDescription. - d *dentry - - // isDir specifies whehter the fileDescription points to a directory. - isDir bool - - // lowerFD is the FileDescription corresponding to the file in the - // underlying file system. - lowerFD *vfs.FileDescription - - // lowerMappable is the memmap.Mappable corresponding to this file in the - // underlying file system. - lowerMappable memmap.Mappable - - // merkleReader is the read-only FileDescription corresponding to the - // Merkle tree file in the underlying file system. - merkleReader *vfs.FileDescription - - // merkleWriter is the FileDescription corresponding to the Merkle tree - // file in the underlying file system for writing. This should only be - // used when allowRuntimeEnable is set to true. - merkleWriter *vfs.FileDescription - - // parentMerkleWriter is the FileDescription of the Merkle tree for the - // directory that contains the current file/directory. This is only used - // if allowRuntimeEnable is set to true. - parentMerkleWriter *vfs.FileDescription - - // off is the file offset. off is protected by mu. - mu sync.Mutex `state:"nosave"` - off int64 -} - -// Release implements vfs.FileDescriptionImpl.Release. -func (fd *fileDescription) Release(ctx context.Context) { - fd.lowerFD.DecRef(ctx) - fd.merkleReader.DecRef(ctx) - if fd.merkleWriter != nil { - fd.merkleWriter.DecRef(ctx) - } - if fd.parentMerkleWriter != nil { - fd.parentMerkleWriter.DecRef(ctx) - } -} - -// Stat implements vfs.FileDescriptionImpl.Stat. -func (fd *fileDescription) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) { - stat, err := fd.lowerFD.Stat(ctx, opts) - if err != nil { - return linux.Statx{}, err - } - fd.d.dirMu.Lock() - if fd.d.verityEnabled() { - if err := fd.d.fs.verifyStatAndChildrenLocked(ctx, fd.d, stat); err != nil { - return linux.Statx{}, err - } - } - fd.d.dirMu.Unlock() - return stat, nil -} - -// SetStat implements vfs.FileDescriptionImpl.SetStat. -func (fd *fileDescription) SetStat(ctx context.Context, opts vfs.SetStatOptions) error { - // Verity files are read-only. - return linuxerr.EPERM -} - -// IterDirents implements vfs.FileDescriptionImpl.IterDirents. -func (fd *fileDescription) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback) error { - if !fd.d.isDir() { - return linuxerr.ENOTDIR - } - fd.mu.Lock() - defer fd.mu.Unlock() - - if _, err := fd.lowerFD.Seek(ctx, fd.off, linux.SEEK_SET); err != nil { - return err - } - - var ds []vfs.Dirent - err := fd.lowerFD.IterDirents(ctx, vfs.IterDirentsCallbackFunc(func(dirent vfs.Dirent) error { - // Do not include the Merkle tree files. - if strings.Contains(dirent.Name, merklePrefix) || strings.Contains(dirent.Name, merkleRootPrefix) { - return nil - } - if fd.d.verityEnabled() { - // Verify that the child is expected. - if dirent.Name != "." && dirent.Name != ".." { - if _, ok := fd.d.childrenNames[dirent.Name]; !ok { - return fd.d.fs.alertIntegrityViolation(fmt.Sprintf("Unexpected children %s", dirent.Name)) - } - } - } - ds = append(ds, dirent) - return nil - })) - - if err != nil { - return err - } - - // The result should be a part of all children plus "." and "..", counting from fd.off. - if fd.d.verityEnabled() && len(ds) != len(fd.d.childrenNames)+2-int(fd.off) { - return fd.d.fs.alertIntegrityViolation(fmt.Sprintf("Unexpected children number %d", len(ds))) - } - - for fd.off < int64(len(ds)) { - if err := cb.Handle(ds[fd.off]); err != nil { - return err - } - fd.off++ - } - return nil -} - -// Seek implements vfs.FileDescriptionImpl.Seek. -func (fd *fileDescription) Seek(ctx context.Context, offset int64, whence int32) (int64, error) { - fd.mu.Lock() - defer fd.mu.Unlock() - n := int64(0) - switch whence { - case linux.SEEK_SET: - // use offset as specified - case linux.SEEK_CUR: - n = fd.off - case linux.SEEK_END: - n = int64(fd.d.size) - default: - return 0, linuxerr.EINVAL - } - if offset > math.MaxInt64-n { - return 0, linuxerr.EINVAL - } - offset += n - if offset < 0 { - return 0, linuxerr.EINVAL - } - fd.off = offset - return offset, nil -} - -// generateMerkleLocked generates a Merkle tree file for fd. If fd points to a -// file /foo/bar, a Merkle tree file /foo/.merkle.verity.bar is generated. The -// hash of the generated Merkle tree and the data size is returned. If fd -// points to a regular file, the data is the content of the file. If fd points -// to a directory, the data is all hashes of its children, written to the Merkle -// tree file. If fd represents a symlink, the data is empty and nothing is written -// to the Merkle tree file. -// -// Preconditions: fd.d.fs.verityMu must be locked. -func (fd *fileDescription) generateMerkleLocked(ctx context.Context) ([]byte, uint64, error) { - fdReader := FileReadWriteSeeker{ - FD: fd.lowerFD, - Ctx: ctx, - } - merkleReader := FileReadWriteSeeker{ - FD: fd.merkleReader, - Ctx: ctx, - } - merkleWriter := FileReadWriteSeeker{ - FD: fd.merkleWriter, - Ctx: ctx, - } - - stat, err := fd.lowerFD.Stat(ctx, vfs.StatOptions{}) - if err != nil { - return nil, 0, err - } - - fd.d.generateChildrenList() - - params := &merkletree.GenerateParams{ - TreeReader: &merkleReader, - TreeWriter: &merkleWriter, - Children: fd.d.childrenList, - HashAlgorithms: fd.d.fs.alg.toLinuxHashAlg(), - Name: fd.d.name, - Mode: uint32(stat.Mode), - UID: stat.UID, - GID: stat.GID, - } - - switch fd.d.mode.Load() & linux.S_IFMT { - case linux.S_IFREG: - // For a regular file, generate a Merkle tree based on its - // content. - params.File = &fdReader - params.Size = int64(stat.Size) - params.DataAndTreeInSameFile = false - case linux.S_IFDIR: - // For a directory, generate a Merkle tree based on the hashes - // of its children that has already been written to the Merkle - // tree file. - merkleStat, err := fd.merkleReader.Stat(ctx, vfs.StatOptions{}) - if err != nil { - return nil, 0, err - } - - params.Size = int64(merkleStat.Size) - params.File = &merkleReader - params.DataAndTreeInSameFile = true - case linux.S_IFLNK: - // For a symlink, generate a Merkle tree file but do not write the root hash - // of the target file content to it. Return a hash of a VerityDescriptor object - // which includes the symlink target name. - target, err := fd.d.readlink(ctx) - if err != nil { - return nil, 0, err - } - - params.Size = int64(stat.Size) - params.DataAndTreeInSameFile = false - params.SymlinkTarget = target - default: - // TODO(b/167728857): Investigate whether and how we should - // enable other types of file. - return nil, 0, linuxerr.EINVAL - } - hash, err := merkletree.Generate(params) - return hash, uint64(params.Size), err -} - -// recordChildrenLocked writes the names of fd's children into the -// corresponding Merkle tree file, and saves the offset/size of the map into -// xattrs. -// -// Preconditions: -// - fd.d.fs.verityMu must be locked. -// - fd.d.isDir() == true. -func (fd *fileDescription) recordChildrenLocked(ctx context.Context) error { - // Record the children names in the Merkle tree file. - childrenNames, err := json.Marshal(fd.d.childrenNames) - if err != nil { - return err - } - - stat, err := fd.merkleWriter.Stat(ctx, vfs.StatOptions{}) - if err != nil { - return err - } - - if err := fd.merkleWriter.SetXattr(ctx, &vfs.SetXattrOptions{ - Name: childrenOffsetXattr, - Value: strconv.Itoa(int(stat.Size)), - }); err != nil { - return err - } - if err := fd.merkleWriter.SetXattr(ctx, &vfs.SetXattrOptions{ - Name: childrenSizeXattr, - Value: strconv.Itoa(len(childrenNames)), - }); err != nil { - return err - } - - if _, err = fd.merkleWriter.Write(ctx, usermem.BytesIOSequence(childrenNames), vfs.WriteOptions{}); err != nil { - return err - } - - return nil -} - -// enableVerity enables verity features on fd by generating a Merkle tree file -// and stores its hash in its parent directory's Merkle tree. -func (fd *fileDescription) enableVerity(ctx context.Context) (uintptr, error) { - if !fd.d.fs.allowRuntimeEnable { - return 0, linuxerr.EPERM - } - - fd.d.fs.verityMu.Lock() - defer fd.d.fs.verityMu.Unlock() - - // In allowRuntimeEnable mode, the underlying fd and read/write fd for - // the Merkle tree file should have all been initialized. For any file - // or directory other than the root, the parent Merkle tree file should - // have also been initialized. - if fd.lowerFD == nil || fd.merkleReader == nil || fd.merkleWriter == nil || (fd.parentMerkleWriter == nil && fd.d != fd.d.fs.rootDentry) { - return 0, fd.d.fs.alertIntegrityViolation("Unexpected verity fd: missing expected underlying fds") - } - - // Populate children names here. We cannot rely on the children - // dentries to populate parent dentry's children names, because the - // parent dentry may be destroyed before users enable verity if its ref - // count drops to zero. - if fd.d.isDir() { - if err := fd.IterDirents(ctx, vfs.IterDirentsCallbackFunc(func(dirent vfs.Dirent) error { - if dirent.Name != "." && dirent.Name != ".." { - fd.d.childrenNames[dirent.Name] = struct{}{} - } - return nil - })); err != nil { - return 0, err - } - } - - hash, dataSize, err := fd.generateMerkleLocked(ctx) - if err != nil { - return 0, err - } - - if fd.parentMerkleWriter != nil { - stat, err := fd.parentMerkleWriter.Stat(ctx, vfs.StatOptions{}) - if err != nil { - return 0, err - } - - // Write the hash of fd to the parent directory's Merkle tree - // file, as it should be part of the parent Merkle tree data. - // parentMerkleWriter is open with O_APPEND, so it should write - // directly to the end of the file. - if _, err = fd.parentMerkleWriter.Write(ctx, usermem.BytesIOSequence(hash), vfs.WriteOptions{}); err != nil { - return 0, err - } - - // Record the offset of the hash of fd in parent directory's - // Merkle tree file. - if err := fd.merkleWriter.SetXattr(ctx, &vfs.SetXattrOptions{ - Name: merkleOffsetInParentXattr, - Value: strconv.Itoa(int(stat.Size)), - }); err != nil { - return 0, err - } - } - - // Record the size of the data being hashed for fd. - if err := fd.merkleWriter.SetXattr(ctx, &vfs.SetXattrOptions{ - Name: merkleSizeXattr, - Value: strconv.Itoa(int(dataSize)), - }); err != nil { - return 0, err - } - - if fd.d.isDir() { - if err := fd.recordChildrenLocked(ctx); err != nil { - return 0, err - } - } - fd.d.hashMu.Lock() - fd.d.hash = hash - fd.d.hashMu.Unlock() - return 0, nil -} - -// measureVerity returns the hash of fd, saved in verityDigest. -func (fd *fileDescription) measureVerity(ctx context.Context, verityDigest hostarch.Addr) (uintptr, error) { - t := kernel.TaskFromContext(ctx) - if t == nil { - return 0, linuxerr.EINVAL - } - var metadata linux.DigestMetadata - - fd.d.hashMu.RLock() - defer fd.d.hashMu.RUnlock() - - // If allowRuntimeEnable is true, an empty fd.d.hash indicates that - // verity is not enabled for the file. If allowRuntimeEnable is false, - // this is an integrity violation because all files should have verity - // enabled, in which case fd.d.hash should be set. - if len(fd.d.hash) == 0 { - if fd.d.fs.allowRuntimeEnable { - return 0, linuxerr.ENODATA - } - return 0, fd.d.fs.alertIntegrityViolation("Ioctl measureVerity: no hash found") - } - - // The first part of VerityDigest is the metadata. - if _, err := metadata.CopyIn(t, verityDigest); err != nil { - return 0, err - } - if metadata.DigestSize < uint16(len(fd.d.hash)) { - return 0, linuxerr.EOVERFLOW - } - - // Populate the output digest size, since DigestSize is both input and - // output. - metadata.DigestSize = uint16(len(fd.d.hash)) - - // First copy the metadata. - if _, err := metadata.CopyOut(t, verityDigest); err != nil { - return 0, err - } - - // Now copy the root hash bytes to the memory after metadata. - _, err := t.CopyOutBytes(hostarch.Addr(uintptr(verityDigest)+linux.SizeOfDigestMetadata), fd.d.hash) - return 0, err -} - -func (fd *fileDescription) verityFlags(ctx context.Context, flags hostarch.Addr) (uintptr, error) { - f := int32(0) - - fd.d.hashMu.RLock() - // All enabled files should store a hash. This flag is not settable via - // FS_IOC_SETFLAGS. - if len(fd.d.hash) != 0 { - f |= linux.FS_VERITY_FL - } - fd.d.hashMu.RUnlock() - - t := kernel.TaskFromContext(ctx) - if t == nil { - return 0, linuxerr.EINVAL - } - _, err := primitive.CopyInt32Out(t, flags, f) - return 0, err -} - -// Ioctl implements vfs.FileDescriptionImpl.Ioctl. -func (fd *fileDescription) Ioctl(ctx context.Context, uio usermem.IO, args arch.SyscallArguments) (uintptr, error) { - switch cmd := args[1].Uint(); cmd { - case linux.FS_IOC_ENABLE_VERITY: - return fd.enableVerity(ctx) - case linux.FS_IOC_MEASURE_VERITY: - return fd.measureVerity(ctx, args[2].Pointer()) - case linux.FS_IOC_GETFLAGS: - return fd.verityFlags(ctx, args[2].Pointer()) - default: - return 0, linuxerr.ENOSYS - } -} - -// Read implements vfs.FileDescriptionImpl.Read. -func (fd *fileDescription) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) { - // Implement Read with PRead by setting offset. - fd.mu.Lock() - n, err := fd.PRead(ctx, dst, fd.off, opts) - fd.off += n - fd.mu.Unlock() - return n, err -} - -// PRead implements vfs.FileDescriptionImpl.PRead. -func (fd *fileDescription) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) { - // No need to verify if the file is not enabled yet in - // allowRuntimeEnable mode. - if !fd.d.verityEnabled() { - return fd.lowerFD.PRead(ctx, dst, offset, opts) - } - - fd.d.fs.verityMu.RLock() - defer fd.d.fs.verityMu.RUnlock() - // dataSize is the size of the whole file. - dataSize, err := fd.merkleReader.GetXattr(ctx, &vfs.GetXattrOptions{ - Name: merkleSizeXattr, - Size: sizeOfStringInt32, - }) - - // The Merkle tree file for the child should have been created and - // contains the expected xattrs. If the xattr does not exist, it - // indicates unexpected modifications to the file system. - if linuxerr.Equals(linuxerr.ENODATA, err) { - return 0, fd.d.fs.alertIntegrityViolation(fmt.Sprintf("Failed to get xattr %s: %v", merkleSizeXattr, err)) - } - if err != nil { - return 0, err - } - - // The dataSize xattr should be an integer. If it's not, it indicates - // unexpected modifications to the file system. - size, err := strconv.Atoi(dataSize) - if err != nil { - return 0, fd.d.fs.alertIntegrityViolation(fmt.Sprintf("Failed to convert xattr %s to int: %v", merkleSizeXattr, err)) - } - - dataReader := FileReadWriteSeeker{ - FD: fd.lowerFD, - Ctx: ctx, - } - - merkleReader := FileReadWriteSeeker{ - FD: fd.merkleReader, - Ctx: ctx, - } - - fd.d.hashMu.RLock() - n, err := merkletree.Verify(&merkletree.VerifyParams{ - Out: dst.Writer(ctx), - File: &dataReader, - Tree: &merkleReader, - Size: int64(size), - Name: fd.d.name, - Mode: fd.d.mode.Load(), - UID: fd.d.uid.Load(), - GID: fd.d.gid.Load(), - Children: fd.d.childrenList, - HashAlgorithms: fd.d.fs.alg.toLinuxHashAlg(), - ReadOffset: offset, - ReadSize: dst.NumBytes(), - Expected: fd.d.hash, - DataAndTreeInSameFile: false, - }) - fd.d.hashMu.RUnlock() - if err != nil { - return 0, fd.d.fs.alertIntegrityViolation(fmt.Sprintf("Verification failed: %v", err)) - } - return n, err -} - -// PWrite implements vfs.FileDescriptionImpl.PWrite. -func (fd *fileDescription) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) { - return 0, linuxerr.EROFS -} - -// Write implements vfs.FileDescriptionImpl.Write. -func (fd *fileDescription) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) { - return 0, linuxerr.EROFS -} - -// ConfigureMMap implements vfs.FileDescriptionImpl.ConfigureMMap. -func (fd *fileDescription) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error { - if err := fd.lowerFD.ConfigureMMap(ctx, opts); err != nil { - return err - } - fd.lowerMappable = opts.Mappable - if opts.MappingIdentity != nil { - opts.MappingIdentity.DecRef(ctx) - opts.MappingIdentity = nil - } - - // Check if mmap is allowed on the lower filesystem. - if !opts.SentryOwnedContent { - return linuxerr.ENODEV - } - return vfs.GenericConfigureMMap(&fd.vfsfd, fd, opts) -} - -// SupportsLocks implements vfs.FileDescriptionImpl.SupportsLocks. -func (fd *fileDescription) SupportsLocks() bool { - return fd.lowerFD.SupportsLocks() -} - -// LockBSD implements vfs.FileDescriptionImpl.LockBSD. -func (fd *fileDescription) LockBSD(ctx context.Context, uid fslock.UniqueID, ownerPID int32, t fslock.LockType, block bool) error { - return fd.lowerFD.LockBSD(ctx, ownerPID, t, block) -} - -// UnlockBSD implements vfs.FileDescriptionImpl.UnlockBSD. -func (fd *fileDescription) UnlockBSD(ctx context.Context, uid fslock.UniqueID) error { - return fd.lowerFD.UnlockBSD(ctx) -} - -// LockPOSIX implements vfs.FileDescriptionImpl.LockPOSIX. -func (fd *fileDescription) LockPOSIX(ctx context.Context, uid fslock.UniqueID, ownerPID int32, t fslock.LockType, r fslock.LockRange, block bool) error { - return fd.lowerFD.LockPOSIX(ctx, uid, ownerPID, t, r, block) -} - -// UnlockPOSIX implements vfs.FileDescriptionImpl.UnlockPOSIX. -func (fd *fileDescription) UnlockPOSIX(ctx context.Context, uid fslock.UniqueID, r fslock.LockRange) error { - return fd.lowerFD.UnlockPOSIX(ctx, uid, r) -} - -// TestPOSIX implements vfs.FileDescriptionImpl.TestPOSIX. -func (fd *fileDescription) TestPOSIX(ctx context.Context, uid fslock.UniqueID, t fslock.LockType, r fslock.LockRange) (linux.Flock, error) { - return fd.lowerFD.TestPOSIX(ctx, uid, t, r) -} - -// Translate implements memmap.Mappable.Translate. -func (fd *fileDescription) Translate(ctx context.Context, required, optional memmap.MappableRange, at hostarch.AccessType) ([]memmap.Translation, error) { - ts, err := fd.lowerMappable.Translate(ctx, required, optional, at) - if err != nil { - return nil, err - } - - // dataSize is the size of the whole file. - dataSize, err := fd.merkleReader.GetXattr(ctx, &vfs.GetXattrOptions{ - Name: merkleSizeXattr, - Size: sizeOfStringInt32, - }) - - // The Merkle tree file for the child should have been created and - // contains the expected xattrs. If the xattr does not exist, it - // indicates unexpected modifications to the file system. - if linuxerr.Equals(linuxerr.ENODATA, err) { - return nil, fd.d.fs.alertIntegrityViolation(fmt.Sprintf("Failed to get xattr %s: %v", merkleSizeXattr, err)) - } - if err != nil { - return nil, err - } - - // The dataSize xattr should be an integer. If it's not, it indicates - // unexpected modifications to the file system. - size, err := strconv.Atoi(dataSize) - if err != nil { - return nil, fd.d.fs.alertIntegrityViolation(fmt.Sprintf("Failed to convert xattr %s to int: %v", merkleSizeXattr, err)) - } - - merkleReader := FileReadWriteSeeker{ - FD: fd.merkleReader, - Ctx: ctx, - } - - for _, t := range ts { - // Content integrity relies on sentry owning the backing data. MapInternal is guaranteed - // to fetch sentry owned memory because we disallow verity mmaps otherwise. - ims, err := t.File.MapInternal(memmap.FileRange{t.Offset, t.Offset + t.Source.Length()}, hostarch.Read) - if err != nil { - return nil, err - } - dataReader := mmapReadSeeker{ims, t.Source.Start} - var buf bytes.Buffer - _, err = merkletree.Verify(&merkletree.VerifyParams{ - Out: &buf, - File: &dataReader, - Tree: &merkleReader, - Size: int64(size), - Name: fd.d.name, - Mode: fd.d.mode.Load(), - UID: fd.d.uid.Load(), - GID: fd.d.gid.Load(), - HashAlgorithms: fd.d.fs.alg.toLinuxHashAlg(), - ReadOffset: int64(t.Source.Start), - ReadSize: int64(t.Source.Length()), - Expected: fd.d.hash, - DataAndTreeInSameFile: false, - }) - if err != nil { - return nil, fd.d.fs.alertIntegrityViolation(fmt.Sprintf("Verification failed: %v", err)) - } - } - return ts, err -} - -// AddMapping implements memmap.Mappable.AddMapping. -func (fd *fileDescription) AddMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) error { - return fd.lowerMappable.AddMapping(ctx, ms, ar, offset, writable) -} - -// RemoveMapping implements memmap.Mappable.RemoveMapping. -func (fd *fileDescription) RemoveMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) { - fd.lowerMappable.RemoveMapping(ctx, ms, ar, offset, writable) -} - -// CopyMapping implements memmap.Mappable.CopyMapping. -func (fd *fileDescription) CopyMapping(ctx context.Context, ms memmap.MappingSpace, srcAR, dstAR hostarch.AddrRange, offset uint64, writable bool) error { - return fd.lowerMappable.CopyMapping(ctx, ms, srcAR, dstAR, offset, writable) -} - -// InvalidateUnsavable implements memmap.Mappable.InvalidateUnsavable. -func (fd *fileDescription) InvalidateUnsavable(context.Context) error { - return nil -} - -// mmapReadSeeker is a helper struct used by fileDescription.Translate to pass -// a safemem.BlockSeq pointing to the mapped region as io.ReaderAt. -type mmapReadSeeker struct { - safemem.BlockSeq - Offset uint64 -} - -// ReadAt implements io.ReaderAt.ReadAt. off is the offset into the mapped file. -func (r *mmapReadSeeker) ReadAt(p []byte, off int64) (int, error) { - bs := r.BlockSeq - // Adjust the offset into the mapped file to get the offset into the internally - // mapped region. - readOffset := off - int64(r.Offset) - if readOffset < 0 { - return 0, linuxerr.EINVAL - } - bs.DropFirst64(uint64(readOffset)) - view := bs.TakeFirst64(uint64(len(p))) - dst := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(p)) - n, err := safemem.CopySeq(dst, view) - return int(n), err -} - -// FileReadWriteSeeker is a helper struct to pass a vfs.FileDescription as -// io.Reader/io.Writer/io.ReadSeeker/io.ReaderAt/io.WriterAt/etc. -type FileReadWriteSeeker struct { - FD *vfs.FileDescription - Ctx context.Context - ROpts vfs.ReadOptions - WOpts vfs.WriteOptions -} - -// ReadAt implements io.ReaderAt.ReadAt. -func (f *FileReadWriteSeeker) ReadAt(p []byte, off int64) (int, error) { - dst := usermem.BytesIOSequence(p) - n, err := f.FD.PRead(f.Ctx, dst, off, f.ROpts) - return int(n), err -} - -// Read implements io.ReadWriteSeeker.Read. -func (f *FileReadWriteSeeker) Read(p []byte) (int, error) { - dst := usermem.BytesIOSequence(p) - n, err := f.FD.Read(f.Ctx, dst, f.ROpts) - return int(n), err -} - -// Seek implements io.ReadWriteSeeker.Seek. -func (f *FileReadWriteSeeker) Seek(offset int64, whence int) (int64, error) { - return f.FD.Seek(f.Ctx, offset, int32(whence)) -} - -// WriteAt implements io.WriterAt.WriteAt. -func (f *FileReadWriteSeeker) WriteAt(p []byte, off int64) (int, error) { - dst := usermem.BytesIOSequence(p) - n, err := f.FD.PWrite(f.Ctx, dst, off, f.WOpts) - return int(n), err -} - -// Write implements io.ReadWriteSeeker.Write. -func (f *FileReadWriteSeeker) Write(p []byte) (int, error) { - buf := usermem.BytesIOSequence(p) - n, err := f.FD.Write(f.Ctx, buf, f.WOpts) - return int(n), err -} diff --git a/pkg/sentry/fsimpl/verity/verity_test.go b/pkg/sentry/fsimpl/verity/verity_test.go deleted file mode 100644 index af041bd50..000000000 --- a/pkg/sentry/fsimpl/verity/verity_test.go +++ /dev/null @@ -1,1211 +0,0 @@ -// 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/errors/linuxerr" - "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/usermem" -) - -const ( - // rootMerkleFilename is the name of the root Merkle tree file. - rootMerkleFilename = "root.verity" - // maxDataSize is the maximum data size of a test file. - maxDataSize = 100000 -) - -var hashAlgs = []HashAlgorithm{SHA256, SHA512} - -func dentryFromVD(t *testing.T, vd vfs.VirtualDentry) *dentry { - t.Helper() - d, ok := vd.Dentry().Impl().(*dentry) - if !ok { - t.Fatalf("can't assert %T as a *dentry", vd) - } - return d -} - -// dentryFromFD returns the dentry corresponding to fd. -func dentryFromFD(t *testing.T, fd *vfs.FileDescription) *dentry { - t.Helper() - f, ok := fd.Impl().(*fileDescription) - if !ok { - t.Fatalf("can't assert %T as a *fileDescription", fd) - } - return f.d -} - -// 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, context.Context, error) { - t.Helper() - 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, - }) - - data := "root_name=" + rootMerkleFilename - mntns, err := vfsObj.NewMountNamespace(ctx, auth.CredentialsFromContext(ctx), "", "verity", &vfs.MountOptions{ - GetFilesystemOptions: vfs.GetFilesystemOptions{ - Data: data, - InternalData: InternalFilesystemOptions{ - LowerName: "tmpfs", - Alg: hashAlg, - AllowRuntimeEnable: true, - Action: ErrorOnViolation, - }, - }, - }) - 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.Cleanup(func() { - root.DecRef(ctx) - mntns.DecRef(ctx) - }) - return vfsObj, root, task.AsyncContext(), nil -} - -// openVerityAt opens a verity file. -// -// TODO(chongc): release reference from opening the file when done. -func openVerityAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, vd vfs.VirtualDentry, path string, flags uint32, mode linux.FileMode) (*vfs.FileDescription, error) { - return vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ - Root: vd, - Start: vd, - Path: fspath.Parse(path), - }, &vfs.OpenOptions{ - Flags: flags, - Mode: mode, - }) -} - -// openLowerAt opens the file in the underlying file system. -// -// TODO(chongc): release reference from opening the file when done. -func (d *dentry) openLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, path string, flags uint32, mode linux.FileMode) (*vfs.FileDescription, error) { - return vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ - Root: d.lowerVD, - Start: d.lowerVD, - Path: fspath.Parse(path), - }, &vfs.OpenOptions{ - Flags: flags, - Mode: mode, - }) -} - -// openLowerMerkleAt opens the Merkle file in the underlying file system. -// -// TODO(chongc): release reference from opening the file when done. -func (d *dentry) openLowerMerkleAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, flags uint32, mode linux.FileMode) (*vfs.FileDescription, error) { - return vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ - Root: d.lowerMerkleVD, - Start: d.lowerMerkleVD, - }, &vfs.OpenOptions{ - Flags: flags, - Mode: mode, - }) -} - -// mkdirLowerAt creates a directory in the underlying file system. -func (d *dentry) mkdirLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, path string, mode linux.FileMode) error { - return vfsObj.MkdirAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ - Root: d.lowerVD, - Start: d.lowerVD, - Path: fspath.Parse(path), - }, &vfs.MkdirOptions{ - Mode: mode, - }) -} - -// unlinkLowerAt deletes the file in the underlying file system. -func (d *dentry) unlinkLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, path string) error { - return vfsObj.UnlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ - Root: d.lowerVD, - Start: d.lowerVD, - Path: fspath.Parse(path), - }) -} - -// unlinkLowerMerkleAt deletes the Merkle file in the underlying file system. -func (d *dentry) unlinkLowerMerkleAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, path string) error { - return vfsObj.UnlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ - Root: d.lowerVD, - Start: d.lowerVD, - Path: fspath.Parse(merklePrefix + path), - }) -} - -// renameLowerAt renames file name to newName in the underlying file system. -func (d *dentry) renameLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, name string, newName string) error { - return vfsObj.RenameAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ - Root: d.lowerVD, - Start: d.lowerVD, - Path: fspath.Parse(name), - }, &vfs.PathOperation{ - Root: d.lowerVD, - Start: d.lowerVD, - Path: fspath.Parse(newName), - }, &vfs.RenameOptions{}) -} - -// renameLowerMerkleAt renames Merkle file name to newName in the underlying -// file system. -func (d *dentry) renameLowerMerkleAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, name string, newName string) error { - return vfsObj.RenameAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ - Root: d.lowerVD, - Start: d.lowerVD, - Path: fspath.Parse(merklePrefix + name), - }, &vfs.PathOperation{ - Root: d.lowerVD, - Start: d.lowerVD, - Path: fspath.Parse(merklePrefix + newName), - }, &vfs.RenameOptions{}) -} - -// symlinkLowerAt creates a symbolic link at symlink referring to the given target -// in the underlying filesystem. -func (d *dentry) symlinkLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, target, symlink string) error { - return vfsObj.SymlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ - Root: d.lowerVD, - Start: d.lowerVD, - Path: fspath.Parse(symlink), - }, target) -} - -// 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, t *testing.T, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, filePath string, mode linux.FileMode) (*vfs.FileDescription, int, error) { - // Create the file in the underlying file system. - lowerFD, err := dentryFromVD(t, root).openLowerAt(ctx, vfsObj, filePath, linux.O_RDWR|linux.O_CREAT|linux.O_EXCL, 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 := openVerityAt(ctx, vfsObj, root, filePath, linux.O_RDONLY, mode) - return fd, dataSize, err -} - -// newDirFD creates a new directory in the verity mount, and returns the FD. -func newDirFD(ctx context.Context, t *testing.T, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, dirPath string, mode linux.FileMode) (*vfs.FileDescription, error) { - // Create the directory in the underlying file system. - if err := dentryFromVD(t, root).mkdirLowerAt(ctx, vfsObj, dirPath, linux.ModeRegular|mode); err != nil { - return nil, err - } - if _, err := dentryFromVD(t, root).openLowerAt(ctx, vfsObj, dirPath, linux.O_RDONLY|linux.O_DIRECTORY, linux.ModeRegular|mode); err != nil { - return nil, err - } - return openVerityAt(ctx, vfsObj, root, dirPath, linux.O_RDONLY|linux.O_DIRECTORY, mode) -} - -// newEmptyFileFD creates a new empty file in the verity mount, and returns the FD. -func newEmptyFileFD(ctx context.Context, t *testing.T, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, filePath string, mode linux.FileMode) (*vfs.FileDescription, error) { - // Create the file in the underlying file system. - _, err := dentryFromVD(t, root).openLowerAt(ctx, vfsObj, filePath, linux.O_RDWR|linux.O_CREAT|linux.O_EXCL, linux.ModeRegular|mode) - if err != nil { - return nil, err - } - // Now open the verity file descriptor. - fd, err := openVerityAt(ctx, vfsObj, root, filePath, linux.O_RDONLY, mode) - return fd, err -} - -// flipRandomBit randomly flips a bit in the file represented by fd. -func flipRandomBit(ctx context.Context, fd *vfs.FileDescription, size int) error { - 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 -} - -func enableVerity(ctx context.Context, t *testing.T, fd *vfs.FileDescription) { - t.Helper() - 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("enable verity: %v", err) - } -} - -// 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" - fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644) - if err != nil { - t.Fatalf("newFileFD: %v", err) - } - - // Ensure that the corresponding Merkle tree file is created. - if _, err = dentryFromFD(t, fd).openLowerMerkleAt(ctx, vfsObj, linux.O_RDONLY, linux.ModeRegular); err != nil { - t.Errorf("OpenAt Merkle tree file %s: %v", merklePrefix+filename, err) - } - - // Ensure the root merkle tree file is created. - if _, err = dentryFromVD(t, root).openLowerMerkleAt(ctx, vfsObj, linux.O_RDONLY, linux.ModeRegular); 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, t, vfsObj, root, filename, 0644) - if err != nil { - t.Fatalf("newFileFD: %v", err) - } - - // Enable verity on the file and confirm a normal read succeeds. - enableVerity(ctx, t, fd) - - 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, t, vfsObj, root, filename, 0644) - if err != nil { - t.Fatalf("newFileFD: %v", err) - } - - // Enable verity on the file and confirm a normal read succeeds. - enableVerity(ctx, t, fd) - - 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) - } - } -} - -// TestReadUnmodifiedEmptyFileSucceeds ensures that read from an untouched empty verity -// file succeeds after enabling verity for it. -func TestReadUnmodifiedEmptyFileSucceeds(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-empty-file" - fd, err := newEmptyFileFD(ctx, t, vfsObj, root, filename, 0644) - if err != nil { - t.Fatalf("newEmptyFileFD: %v", err) - } - - // Enable verity on the file and confirm a normal read succeeds. - enableVerity(ctx, t, fd) - - var buf []byte - n, err := fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{}) - if err != nil && err != io.EOF { - t.Fatalf("fd.Read: %v", err) - } - - if n != 0 { - t.Errorf("fd.Read got read length %d, expected 0", n) - } - } -} - -// 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, t, vfsObj, root, filename, 0644) - if err != nil { - t.Fatalf("newFileFD: %v", err) - } - - // Enable verity on the file and confirms a normal read succeeds. - enableVerity(ctx, t, fd) - - // Ensure reopening the verity enabled file succeeds. - if _, err = openVerityAt(ctx, vfsObj, root, filename, linux.O_RDONLY, 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, t, vfsObj, root, filename, 0644) - if err != nil { - t.Fatalf("newFileFD: %v", err) - } - - // Enable verity on the file and confirms a normal read succeeds. - enableVerity(ctx, t, fd) - - // Enable verity on the parent directory. - parentFD, err := openVerityAt(ctx, vfsObj, root, "", linux.O_RDONLY, linux.ModeRegular) - if err != nil { - t.Fatalf("OpenAt: %v", err) - } - enableVerity(ctx, t, parentFD) - - // Ensure open an unexpected file in the parent directory fails with - // ENOENT rather than verification failure. - if _, err = openVerityAt(ctx, vfsObj, root, filename+"abc", linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.ENOENT, err) { - 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, t, vfsObj, root, filename, 0644) - if err != nil { - t.Fatalf("newFileFD: %v", err) - } - - // Enable verity on the file. - enableVerity(ctx, t, fd) - - // Open a new lowerFD that's read/writable. - lowerFD, err := dentryFromFD(t, fd).openLowerAt(ctx, vfsObj, "", linux.O_RDWR, linux.ModeRegular) - if err != nil { - t.Fatalf("OpenAt: %v", err) - } - - if err := flipRandomBit(ctx, lowerFD, size); err != nil { - t.Fatalf("flipRandomBit: %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, t, vfsObj, root, filename, 0644) - if err != nil { - t.Fatalf("newFileFD: %v", err) - } - - // Enable verity on the file. - enableVerity(ctx, t, fd) - - // Open a new lowerFD that's read/writable. - lowerFD, err := dentryFromFD(t, fd).openLowerAt(ctx, vfsObj, "", linux.O_RDWR, linux.ModeRegular) - if err != nil { - t.Fatalf("OpenAt: %v", err) - } - - if err := flipRandomBit(ctx, lowerFD, size); err != nil { - t.Fatalf("flipRandomBit: %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, t, vfsObj, root, filename, 0644) - if err != nil { - t.Fatalf("newFileFD: %v", err) - } - - // Enable verity on the file. - enableVerity(ctx, t, fd) - - // Open a new lowerMerkleFD that's read/writable. - lowerMerkleFD, err := dentryFromFD(t, fd).openLowerMerkleAt(ctx, vfsObj, linux.O_RDWR, linux.ModeRegular) - 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 := flipRandomBit(ctx, lowerMerkleFD, int(stat.Size)); err != nil { - t.Fatalf("flipRandomBit: %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 { - 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, t, vfsObj, root, filename, 0644) - if err != nil { - t.Fatalf("newFileFD: %v", err) - } - - // Enable verity on the file. - enableVerity(ctx, t, fd) - - // Enable verity on the parent directory. - parentFD, err := openVerityAt(ctx, vfsObj, root, "", linux.O_RDONLY, linux.ModeRegular) - if err != nil { - t.Fatalf("OpenAt: %v", err) - } - enableVerity(ctx, t, parentFD) - - // Open a new lowerMerkleFD that's read/writable. - parentLowerMerkleFD, err := dentryFromFD(t, fd).parent.openLowerMerkleAt(ctx, vfsObj, linux.O_RDWR, linux.ModeRegular) - 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 := flipRandomBit(ctx, parentLowerMerkleFD, parentMerkleSize); err != nil { - t.Fatalf("flipRandomBit: %v", err) - } - - parentLowerMerkleFD.DecRef(ctx) - - // Ensure reopening the verity enabled file fails. - if _, err = openVerityAt(ctx, vfsObj, root, filename, linux.O_RDONLY, 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, t, vfsObj, root, filename, 0644) - if err != nil { - t.Fatalf("newFileFD: %v", err) - } - - // Enable verity on the file and confirm that stat succeeds. - enableVerity(ctx, t, fd) - 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, t, vfsObj, root, filename, 0644) - if err != nil { - t.Fatalf("newFileFD: %v", err) - } - - // Enable verity on the file. - enableVerity(ctx, t, fd) - - 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 { - name string - // The original file is removed if changeFile is true. - changeFile bool - // The Merkle tree file is removed if changeMerkleFile is true. - changeMerkleFile bool - }{ - { - name: "FileOnly", - changeFile: true, - changeMerkleFile: false, - }, - { - name: "MerkleOnly", - changeFile: false, - changeMerkleFile: true, - }, - { - name: "FileAndMerkle", - changeFile: true, - changeMerkleFile: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, 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, t, vfsObj, root, filename, 0644) - if err != nil { - t.Fatalf("newFileFD: %v", err) - } - - // Enable verity on the file. - enableVerity(ctx, t, fd) - - if tc.changeFile { - if err := dentryFromVD(t, root).unlinkLowerAt(ctx, vfsObj, filename); err != nil { - t.Fatalf("UnlinkAt: %v", err) - } - } - if tc.changeMerkleFile { - if err := dentryFromVD(t, root).unlinkLowerMerkleAt(ctx, vfsObj, filename); err != nil { - t.Fatalf("UnlinkAt: %v", err) - } - } - - // Ensure reopening the verity enabled file fails. - if _, err = openVerityAt(ctx, vfsObj, root, filename, linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.EIO, err) { - 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 { - name string - // The original file is renamed if changeFile is true. - changeFile bool - // The Merkle tree file is renamed if changeMerkleFile is true. - changeMerkleFile bool - }{ - { - name: "FileOnly", - changeFile: true, - changeMerkleFile: false, - }, - { - name: "MerkleOnly", - changeFile: false, - changeMerkleFile: true, - }, - { - name: "FileAndMerkle", - changeFile: true, - changeMerkleFile: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, 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, t, vfsObj, root, filename, 0644) - if err != nil { - t.Fatalf("newFileFD: %v", err) - } - - // Enable verity on the file. - enableVerity(ctx, t, fd) - - newFilename := "renamed-test-file" - if tc.changeFile { - if err := dentryFromVD(t, root).renameLowerAt(ctx, vfsObj, filename, newFilename); err != nil { - t.Fatalf("RenameAt: %v", err) - } - } - if tc.changeMerkleFile { - if err := dentryFromVD(t, root).renameLowerMerkleAt(ctx, vfsObj, filename, newFilename); err != nil { - t.Fatalf("UnlinkAt: %v", err) - } - } - - // Ensure reopening the verity enabled file fails. - if _, err = openVerityAt(ctx, vfsObj, root, filename, linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.EIO, err) { - t.Errorf("got OpenAt error: %v, expected EIO", err) - } - }) - } -} - -// TestUnmodifiedSymlinkFileReadSucceeds ensures that readlink() for an -// unmodified verity enabled symlink succeeds. -func TestUnmodifiedSymlinkFileReadSucceeds(t *testing.T) { - testCases := []struct { - name string - // The symlink target is a directory. - hasDirectoryTarget bool - // The symlink target is a directory and contains a regular file which will be - // used to test walking a symlink. - testWalk bool - }{ - { - name: "RegularFileTarget", - hasDirectoryTarget: false, - testWalk: false, - }, - { - name: "DirectoryTarget", - hasDirectoryTarget: true, - testWalk: false, - }, - { - name: "RegularFileInSymlinkDirectory", - hasDirectoryTarget: true, - testWalk: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - if tc.testWalk && !tc.hasDirectoryTarget { - t.Fatalf("Invalid test case: hasDirectoryTarget can't be false when testing symlink walk") - } - - vfsObj, root, ctx, err := newVerityRoot(t, SHA256) - if err != nil { - t.Fatalf("newVerityRoot: %v", err) - } - - var target string - if tc.hasDirectoryTarget { - target = "verity-test-dir" - if _, err := newDirFD(ctx, t, vfsObj, root, target, 0644); err != nil { - t.Fatalf("newDirFD: %v", err) - } - } else { - target = "verity-test-file" - if _, _, err := newFileFD(ctx, t, vfsObj, root, target, 0644); err != nil { - t.Fatalf("newFileFD: %v", err) - } - } - - if tc.testWalk { - fileInTargetDirectory := target + "/" + "verity-test-file" - if _, _, err := newFileFD(ctx, t, vfsObj, root, fileInTargetDirectory, 0644); err != nil { - t.Fatalf("newFileFD: %v", err) - } - } - - symlink := "verity-test-symlink" - if err := dentryFromVD(t, root).symlinkLowerAt(ctx, vfsObj, target, symlink); err != nil { - t.Fatalf("SymlinkAt: %v", err) - } - - fd, err := openVerityAt(ctx, vfsObj, root, symlink, linux.O_NOFOLLOW, linux.ModeRegular) - - if err != nil { - t.Fatalf("openVerityAt symlink: %v", err) - } - - enableVerity(ctx, t, fd) - - if _, err := vfsObj.ReadlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(symlink), - }); err != nil { - t.Fatalf("ReadlinkAt: %v", err) - } - - if tc.testWalk { - fileInSymlinkDirectory := symlink + "/verity-test-file" - // Ensure opening the verity enabled file in the symlink directory succeeds. - if _, err := openVerityAt(ctx, vfsObj, root, fileInSymlinkDirectory, linux.O_RDONLY, linux.ModeRegular); err != nil { - t.Errorf("open enabled file failed: %v", err) - } - } - }) - } -} - -// TestDeletedSymlinkFileReadFails ensures that reading value of a deleted verity enabled -// symlink fails. -func TestDeletedSymlinkFileReadFails(t *testing.T) { - testCases := []struct { - name string - // The original symlink is unlinked if deleteLink is true. - deleteLink bool - // The Merkle tree file is renamed if deleteMerkleFile is true. - deleteMerkleFile bool - // The symlink target is a directory. - hasDirectoryTarget bool - // The symlink target is a directory and contains a regular file which will be - // used to test walking a symlink. - testWalk bool - }{ - { - name: "DeleteLinkRegularFile", - deleteLink: true, - deleteMerkleFile: false, - hasDirectoryTarget: false, - testWalk: false, - }, - { - name: "DeleteMerkleRegFile", - deleteLink: false, - deleteMerkleFile: true, - hasDirectoryTarget: false, - testWalk: false, - }, - { - name: "DeleteLinkAndMerkleRegFile", - deleteLink: true, - deleteMerkleFile: true, - hasDirectoryTarget: false, - testWalk: false, - }, - { - name: "DeleteLinkDirectory", - deleteLink: true, - deleteMerkleFile: false, - hasDirectoryTarget: true, - testWalk: false, - }, - { - name: "DeleteMerkleDirectory", - deleteLink: false, - deleteMerkleFile: true, - hasDirectoryTarget: true, - testWalk: false, - }, - { - name: "DeleteLinkAndMerkleDirectory", - deleteLink: true, - deleteMerkleFile: true, - hasDirectoryTarget: true, - testWalk: false, - }, - { - name: "DeleteLinkDirectoryWalk", - deleteLink: true, - deleteMerkleFile: false, - hasDirectoryTarget: true, - testWalk: true, - }, - { - name: "DeleteMerkleDirectoryWalk", - deleteLink: false, - deleteMerkleFile: true, - hasDirectoryTarget: true, - testWalk: true, - }, - { - name: "DeleteLinkAndMerkleDirectoryWalk", - deleteLink: true, - deleteMerkleFile: true, - hasDirectoryTarget: true, - testWalk: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - if tc.testWalk && !tc.hasDirectoryTarget { - t.Fatalf("Invalid test case: hasDirectoryTarget can't be false when testing symlink walk") - } - - vfsObj, root, ctx, err := newVerityRoot(t, SHA256) - if err != nil { - t.Fatalf("newVerityRoot: %v", err) - } - - var target string - if tc.hasDirectoryTarget { - target = "verity-test-dir" - if _, err := newDirFD(ctx, t, vfsObj, root, target, 0644); err != nil { - t.Fatalf("newDirFD: %v", err) - } - } else { - target = "verity-test-file" - if _, _, err := newFileFD(ctx, t, vfsObj, root, target, 0644); err != nil { - t.Fatalf("newFileFD: %v", err) - } - } - - symlink := "verity-test-symlink" - if err := dentryFromVD(t, root).symlinkLowerAt(ctx, vfsObj, target, symlink); err != nil { - t.Fatalf("SymlinkAt: %v", err) - } - - fd, err := openVerityAt(ctx, vfsObj, root, symlink, linux.O_NOFOLLOW, linux.ModeRegular) - - if err != nil { - t.Fatalf("openVerityAt symlink: %v", err) - } - - if tc.testWalk { - fileInTargetDirectory := target + "/" + "verity-test-file" - if _, _, err := newFileFD(ctx, t, vfsObj, root, fileInTargetDirectory, 0644); err != nil { - t.Fatalf("newFileFD: %v", err) - } - } - - enableVerity(ctx, t, fd) - - if tc.deleteLink { - if err := dentryFromVD(t, root).unlinkLowerAt(ctx, vfsObj, symlink); err != nil { - t.Fatalf("UnlinkAt: %v", err) - } - } - if tc.deleteMerkleFile { - if err := dentryFromVD(t, root).unlinkLowerMerkleAt(ctx, vfsObj, symlink); err != nil { - t.Fatalf("UnlinkAt: %v", err) - } - } - if _, err := vfsObj.ReadlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(symlink), - }); !linuxerr.Equals(linuxerr.EIO, err) { - t.Fatalf("ReadlinkAt succeeded with modified symlink: %v", err) - } - - if tc.testWalk { - fileInSymlinkDirectory := symlink + "/verity-test-file" - // Ensure opening the verity enabled file in the symlink directory fails. - if _, err := openVerityAt(ctx, vfsObj, root, fileInSymlinkDirectory, linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.EIO, err) { - t.Errorf("Open succeeded with modified symlink: %v", err) - } - } - }) - } -} - -// TestModifiedSymlinkFileReadFails ensures that reading value of a modified verity enabled -// symlink fails. -func TestModifiedSymlinkFileReadFails(t *testing.T) { - testCases := []struct { - name string - // The symlink target is a directory. - hasDirectoryTarget bool - // The symlink target is a directory and contains a regular file which will be - // used to test walking a symlink. - testWalk bool - }{ - { - name: "RegularFileTarget", - hasDirectoryTarget: false, - testWalk: false, - }, - { - name: "DirectoryTarget", - hasDirectoryTarget: true, - testWalk: false, - }, - { - name: "RegularFileInSymlinkDirectory", - hasDirectoryTarget: true, - testWalk: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - if tc.testWalk && !tc.hasDirectoryTarget { - t.Fatalf("Invalid test case: hasDirectoryTarget can't be false when testing symlink walk") - } - - vfsObj, root, ctx, err := newVerityRoot(t, SHA256) - if err != nil { - t.Fatalf("newVerityRoot: %v", err) - } - - var target string - if tc.hasDirectoryTarget { - target = "verity-test-dir" - if _, err := newDirFD(ctx, t, vfsObj, root, target, 0644); err != nil { - t.Fatalf("newDirFD: %v", err) - } - } else { - target = "verity-test-file" - if _, _, err := newFileFD(ctx, t, vfsObj, root, target, 0644); err != nil { - t.Fatalf("newFileFD: %v", err) - } - } - - // Create symlink which points to target file. - symlink := "verity-test-symlink" - if err := dentryFromVD(t, root).symlinkLowerAt(ctx, vfsObj, target, symlink); err != nil { - t.Fatalf("SymlinkAt: %v", err) - } - - // Open symlink file to get the fd for ioctl in new step. - fd, err := openVerityAt(ctx, vfsObj, root, symlink, linux.O_NOFOLLOW, linux.ModeRegular) - if err != nil { - t.Fatalf("OpenAt symlink: %v", err) - } - - if tc.testWalk { - fileInTargetDirectory := target + "/" + "verity-test-file" - if _, _, err := newFileFD(ctx, t, vfsObj, root, fileInTargetDirectory, 0644); err != nil { - t.Fatalf("newFileFD: %v", err) - } - } - - enableVerity(ctx, t, fd) - - var newTarget string - if tc.hasDirectoryTarget { - newTarget = "verity-test-dir-new" - if _, err := newDirFD(ctx, t, vfsObj, root, newTarget, 0644); err != nil { - t.Fatalf("newDirFD: %v", err) - } - } else { - newTarget = "verity-test-file-new" - if _, _, err := newFileFD(ctx, t, vfsObj, root, newTarget, 0644); err != nil { - t.Fatalf("newFileFD: %v", err) - } - } - - // Unlink symlink->target. - if err := dentryFromVD(t, root).unlinkLowerAt(ctx, vfsObj, symlink); err != nil { - t.Fatalf("UnlinkAt: %v", err) - } - - // Link symlink->newTarget. - if err := dentryFromVD(t, root).symlinkLowerAt(ctx, vfsObj, newTarget, symlink); err != nil { - t.Fatalf("SymlinkAt: %v", err) - } - - // Freshen lower dentry for symlink. - symlinkVD, err := vfsObj.GetDentryAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(symlink), - }, &vfs.GetDentryOptions{}) - if err != nil { - t.Fatalf("Failed to get symlink dentry: %v", err) - } - symlinkDentry := dentryFromVD(t, symlinkVD) - - symlinkLowerVD, err := dentryFromVD(t, root).getLowerAt(ctx, vfsObj, symlink) - if err != nil { - t.Fatalf("Failed to get symlink lower dentry: %v", err) - } - symlinkDentry.lowerVD = symlinkLowerVD - - // Verify ReadlinkAt() fails. - if _, err := vfsObj.ReadlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(symlink), - }); !linuxerr.Equals(linuxerr.EIO, err) { - t.Fatalf("ReadlinkAt succeeded with modified symlink: %v", err) - } - - if tc.testWalk { - fileInSymlinkDirectory := symlink + "/verity-test-file" - // Ensure opening the verity enabled file in the symlink directory fails. - if _, err := openVerityAt(ctx, vfsObj, root, fileInSymlinkDirectory, linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.EIO, err) { - t.Errorf("Open succeeded with modified symlink: %v", err) - } - } - }) - } -} diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD index a8828a15b..5a7782c97 100644 --- a/runsc/boot/BUILD +++ b/runsc/boot/BUILD @@ -64,7 +64,6 @@ go_library( "//pkg/sentry/fsimpl/proc", "//pkg/sentry/fsimpl/sys", "//pkg/sentry/fsimpl/tmpfs", - "//pkg/sentry/fsimpl/verity", "//pkg/sentry/inet", "//pkg/sentry/kernel", "//pkg/sentry/kernel:uncaught_signal_go_proto", diff --git a/runsc/boot/vfs.go b/runsc/boot/vfs.go index d9f76c008..9ec78c3ed 100644 --- a/runsc/boot/vfs.go +++ b/runsc/boot/vfs.go @@ -16,7 +16,6 @@ package boot import ( "fmt" - "path" "path/filepath" "sort" "strconv" @@ -44,7 +43,6 @@ import ( "gvisor.dev/gvisor/pkg/sentry/fsimpl/proc" "gvisor.dev/gvisor/pkg/sentry/fsimpl/sys" "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/verity" "gvisor.dev/gvisor/pkg/sentry/inet" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" @@ -104,10 +102,6 @@ func registerFilesystems(k *kernel.Kernel) error { AllowUserMount: true, AllowUserList: true, }) - vfsObj.MustRegisterFilesystemType(verity.Name, &verity.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ - AllowUserList: true, - AllowUserMount: true, - }) vfsObj.MustRegisterFilesystemType(mqfs.Name, &mqfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ AllowUserMount: true, AllowUserList: true, @@ -687,12 +681,6 @@ func (c *containerMounter) getMountNameAndOptions(conf *config.Config, m *mountA internalData interface{} ) - verityData, verityOpts, verityRequested, remainingMOpts, err := parseVerityMountOptions(m.mount.Options) - if err != nil { - return "", nil, false, err - } - m.mount.Options = remainingMOpts - // Find filesystem name and FS specific data field. switch m.mount.Type { case devpts.Name, devtmpfs.Name, proc.Name: @@ -746,20 +734,6 @@ func (c *containerMounter) getMountNameAndOptions(conf *config.Config, m *mountA InternalData: internalData, } - if verityRequested { - verityData = verityData + "root_name=" + path.Base(m.mount.Destination) - verityOpts.LowerName = fsName - verityOpts.LowerGetFSOptions = opts.GetFilesystemOptions - fsName = verity.Name - opts = &vfs.MountOptions{ - GetFilesystemOptions: vfs.GetFilesystemOptions{ - Data: verityData, - InternalData: verityOpts, - }, - InternalMount: true, - } - } - return fsName, opts, useOverlay, nil } @@ -795,50 +769,6 @@ func parseKeyValue(s string) (string, string, bool) { return strings.TrimSpace(tokens[0]), strings.TrimSpace(tokens[1]), true } -// parseAndFilterOptions scans the provided mount options for verity-related -// mount options. It returns the parsed set of verity mount options, as well as -// the filtered set of mount options unrelated to verity. -func parseVerityMountOptions(mopts []string) (string, verity.InternalFilesystemOptions, bool, []string, error) { - nonVerity := []string{} - found := false - var rootHash string - verityOpts := verity.InternalFilesystemOptions{ - Action: verity.PanicOnViolation, - } - for _, o := range mopts { - if !strings.HasPrefix(o, "verity.") { - nonVerity = append(nonVerity, o) - continue - } - - k, v, ok := parseKeyValue(o) - if !ok { - return "", verityOpts, found, nonVerity, fmt.Errorf("invalid verity mount option with no value: %q", o) - } - - found = true - switch k { - case "verity.roothash": - rootHash = v - case "verity.action": - switch v { - case "error": - verityOpts.Action = verity.ErrorOnViolation - case "panic": - verityOpts.Action = verity.PanicOnViolation - default: - log.Warningf("Invalid verity action %q", v) - verityOpts.Action = verity.PanicOnViolation - } - default: - return "", verityOpts, found, nonVerity, fmt.Errorf("unknown verity mount option: %q", k) - } - } - verityOpts.AllowRuntimeEnable = len(rootHash) == 0 - verityData := "root_hash=" + rootHash + "," - return verityData, verityOpts, found, nonVerity, nil -} - // mountTmp mounts an internal tmpfs at '/tmp' if it's safe to do so. // Technically we don't have to mount tmpfs at /tmp, as we could just rely on // the host /tmp, but this is a nice optimization, and fixes some apps that call diff --git a/runsc/cli/main.go b/runsc/cli/main.go index 701beedbc..f9d552426 100644 --- a/runsc/cli/main.go +++ b/runsc/cli/main.go @@ -81,7 +81,6 @@ func Main(version string) { subcommands.Register(new(cmd.Spec), "") subcommands.Register(new(cmd.Start), "") subcommands.Register(new(cmd.State), "") - subcommands.Register(new(cmd.VerityPrepare), "") subcommands.Register(new(cmd.Wait), "") // Helpers. diff --git a/runsc/cmd/BUILD b/runsc/cmd/BUILD index c55ffb9bc..fc6e68e1d 100644 --- a/runsc/cmd/BUILD +++ b/runsc/cmd/BUILD @@ -37,7 +37,6 @@ go_library( "symbolize.go", "syscalls.go", "usage.go", - "verity_prepare.go", "wait.go", ], visibility = [ diff --git a/runsc/cmd/gofer.go b/runsc/cmd/gofer.go index 261b5d0b2..b7a4427f2 100644 --- a/runsc/cmd/gofer.go +++ b/runsc/cmd/gofer.go @@ -172,10 +172,6 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) filter.InstallUDSFilters() } - if conf.Verity { - filter.InstallXattrFilters() - } - if err := filter.Install(); err != nil { util.Fatalf("installing seccomp filters: %v", err) } @@ -204,8 +200,7 @@ func (g *Gofer) serveLisafs(spec *specs.Spec, conf *config.Config, root string) server := fsgofer.NewLisafsServer(fsgofer.Config{ // These are global options. Ignore readonly configuration, that is set on // a per connection basis. - HostUDS: conf.FSGoferHostUDS, - EnableVerityXattr: conf.Verity, + HostUDS: conf.FSGoferHostUDS, }) // Start with root mount, then add any other additional mount as needed. @@ -261,9 +256,8 @@ func (g *Gofer) serve9P(spec *specs.Spec, conf *config.Config, root string) subc // Start with root mount, then add any other additional mount as needed. ats := make([]p9.Attacher, 0, len(spec.Mounts)+1) ap, err := fsgofer.NewAttachPoint("/", fsgofer.Config{ - ROMount: spec.Root.Readonly || conf.Overlay, - HostUDS: conf.FSGoferHostUDS, - EnableVerityXattr: conf.Verity, + ROMount: spec.Root.Readonly || conf.Overlay, + HostUDS: conf.FSGoferHostUDS, }) if err != nil { util.Fatalf("creating attach point: %v", err) @@ -275,9 +269,8 @@ func (g *Gofer) serve9P(spec *specs.Spec, conf *config.Config, root string) subc for _, m := range spec.Mounts { if specutils.IsGoferMount(m) { cfg := fsgofer.Config{ - ROMount: isReadonlyMount(m.Options) || conf.Overlay, - HostUDS: conf.FSGoferHostUDS, - EnableVerityXattr: conf.Verity, + ROMount: isReadonlyMount(m.Options) || conf.Overlay, + HostUDS: conf.FSGoferHostUDS, } ap, err := fsgofer.NewAttachPoint(m.Destination, cfg) if err != nil { diff --git a/runsc/cmd/verity_prepare.go b/runsc/cmd/verity_prepare.go deleted file mode 100644 index 80131341a..000000000 --- a/runsc/cmd/verity_prepare.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2021 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 cmd - -import ( - "context" - "fmt" - "math/rand" - "os" - - "github.com/google/subcommands" - specs "github.com/opencontainers/runtime-spec/specs-go" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/runsc/cmd/util" - "gvisor.dev/gvisor/runsc/config" - "gvisor.dev/gvisor/runsc/flag" - "gvisor.dev/gvisor/runsc/specutils" -) - -// VerityPrepare implements subcommands.Commands for the "verity-prepare" -// command. It sets up a sandbox with a writable verity mount mapped to "--dir", -// and executes the verity measure tool specified by "--tool" in the sandbox. It -// is intended to prepare --dir to be mounted as a verity filesystem. -type VerityPrepare struct { - root string - tool string - dir string -} - -// Name implements subcommands.Command.Name. -func (*VerityPrepare) Name() string { - return "verity-prepare" -} - -// Synopsis implements subcommands.Command.Synopsis. -func (*VerityPrepare) Synopsis() string { - return "Generates the data structures necessary to enable verityfs on a filesystem." -} - -// Usage implements subcommands.Command.Usage. -func (*VerityPrepare) Usage() string { - return "verity-prepare --tool= --dir=" -} - -// SetFlags implements subcommands.Command.SetFlags. -func (c *VerityPrepare) SetFlags(f *flag.FlagSet) { - f.StringVar(&c.root, "root", "/", `path to the root directory, defaults to "/"`) - f.StringVar(&c.tool, "tool", "", "path to the verity measure_tool") - f.StringVar(&c.dir, "dir", "", "path to the directory to be hashed") -} - -// Execute implements subcommands.Command.Execute. -func (c *VerityPrepare) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - conf := args[0].(*config.Config) - waitStatus := args[1].(*unix.WaitStatus) - - hostname, err := os.Hostname() - if err != nil { - return util.Errorf("Error to retrieve hostname: %v", err) - } - - // Map the entire host file system. - absRoot, err := resolvePath(c.root) - if err != nil { - return util.Errorf("Error resolving root: %v", err) - } - - spec := &specs.Spec{ - Root: &specs.Root{ - Path: absRoot, - }, - Process: &specs.Process{ - Cwd: absRoot, - Args: []string{c.tool, "--path", "/verityroot", "--rawpath", "/rawroot"}, - Env: os.Environ(), - Capabilities: specutils.AllCapabilities(), - }, - Hostname: hostname, - Mounts: []specs.Mount{ - { - Source: c.dir, - Destination: "/verityroot", - Type: "bind", - Options: []string{"verity.roothash="}, - }, - { - Source: c.dir, - Destination: "/rawroot", - Type: "bind", - }, - }, - } - - cid := fmt.Sprintf("runsc-%06d", rand.Int31n(1000000)) - - // Force no networking, it is not necessary to run the verity measure tool. - conf.Network = config.NetworkNone - - conf.Verity = true - - return startContainerAndWait(spec, conf, cid, waitStatus) -} diff --git a/runsc/config/config.go b/runsc/config/config.go index df628f312..c4307fefe 100644 --- a/runsc/config/config.go +++ b/runsc/config/config.go @@ -73,9 +73,6 @@ type Config struct { // Overlay is whether to wrap the root filesystem in an overlay. Overlay bool `flag:"overlay"` - // Verity is whether there's one or more verity file system to mount. - Verity bool `flag:"verity"` - // FSGoferHostUDS enables the gofer to create and connect to host unix // domain sockets. FSGoferHostUDS bool `flag:"fsgofer-host-uds"` diff --git a/runsc/config/flags.go b/runsc/config/flags.go index 2ba2b24b8..9fe4e04d0 100644 --- a/runsc/config/flags.go +++ b/runsc/config/flags.go @@ -78,7 +78,6 @@ func RegisterFlags(flagSet *flag.FlagSet) { flagSet.Var(fileAccessTypePtr(FileAccessExclusive), "file-access", "specifies which filesystem validation to use for the root mount: exclusive (default), shared.") flagSet.Var(fileAccessTypePtr(FileAccessShared), "file-access-mounts", "specifies which filesystem validation to use for volumes other than the root mount: shared (default), exclusive.") flagSet.Bool("overlay", false, "wrap filesystem mounts with writable overlay. All modifications are stored in memory inside the sandbox.") - flagSet.Bool("verity", false, "specifies whether a verity file system will be mounted.") flagSet.Bool("fsgofer-host-uds", false, "allow the gofer to mount Unix Domain Sockets.") flagSet.Bool("vfs2", true, "DEPRECATED: this flag has no effect.") flagSet.Bool("fuse", false, "TEST ONLY; use while FUSE in VFSv2 is landing. This allows the use of the new experimental FUSE filesystem.") diff --git a/runsc/fsgofer/filter/filter.go b/runsc/fsgofer/filter/filter.go index 799f41a3f..e87fc81d9 100644 --- a/runsc/fsgofer/filter/filter.go +++ b/runsc/fsgofer/filter/filter.go @@ -36,9 +36,3 @@ func InstallUDSFilters() { // Add additional filters required for connecting to the host's sockets. allowedSyscalls.Merge(udsSyscalls) } - -// InstallXattrFilters extends the allowed syscalls to include xattr calls that -// are necessary for Verity enabled file systems. -func InstallXattrFilters() { - allowedSyscalls.Merge(xattrSyscalls) -} diff --git a/runsc/fsgofer/fsgofer.go b/runsc/fsgofer/fsgofer.go index e38dba15a..6a40b9bd6 100644 --- a/runsc/fsgofer/fsgofer.go +++ b/runsc/fsgofer/fsgofer.go @@ -52,14 +52,6 @@ const ( unixPathMax = 108 ) -// verityXattrs are the extended attributes used by verity file system. -var verityXattrs = map[string]struct{}{ - "user.merkle.offset": {}, - "user.merkle.size": {}, - "user.merkle.childrenOffset": {}, - "user.merkle.childrenSize": {}, -} - // join is equivalent to path.Join() but skips path.Clean() which is expensive. func join(parent, child string) string { return parent + "/" + child @@ -76,10 +68,6 @@ type Config struct { // HostUDS signals whether the gofer can create and connect to host // unix domain sockets. HostUDS bool - - // EnableVerityXattr allows access to extended attributes used by the - // verity file system. - EnableVerityXattr bool } type attachPoint struct { @@ -822,27 +810,11 @@ func (l *localFile) SetAttr(valid p9.SetAttrMask, attr p9.SetAttr) error { } func (l *localFile) GetXattr(name string, size uint64) (string, error) { - if !l.attachPoint.conf.EnableVerityXattr { - return "", unix.EOPNOTSUPP - } - if _, ok := verityXattrs[name]; !ok { - return "", unix.EOPNOTSUPP - } - buffer := make([]byte, size) - if _, err := unix.Fgetxattr(l.file.FD(), name, buffer); err != nil { - return "", err - } - return string(buffer), nil + return "", unix.EOPNOTSUPP } func (l *localFile) SetXattr(name string, value string, flags uint32) error { - if !l.attachPoint.conf.EnableVerityXattr { - return unix.EOPNOTSUPP - } - if _, ok := verityXattrs[name]; !ok { - return unix.EOPNOTSUPP - } - return unix.Fsetxattr(l.file.FD(), name, []byte(value), int(flags)) + return unix.EOPNOTSUPP } func (*localFile) ListXattr(uint64) (map[string]struct{}, error) { diff --git a/runsc/fsgofer/fsgofer_test.go b/runsc/fsgofer/fsgofer_test.go index 14411bedf..69c5ee0df 100644 --- a/runsc/fsgofer/fsgofer_test.go +++ b/runsc/fsgofer/fsgofer_test.go @@ -581,17 +581,6 @@ func TestSetGetDisabledXattr(t *testing.T) { }) } -func TestSetGetXattr(t *testing.T) { - runCustom(t, []uint32{unix.S_IFREG}, []Config{{ROMount: false, EnableVerityXattr: true}}, func(t *testing.T, s fileState) { - name := "user.merkle.offset" - value := "tmp" - err := SetGetXattr(s.file, name, value) - if err != nil { - t.Fatalf("%v: SetGetXattr failed, err: %v", s, err) - } - }) -} - func TestLink(t *testing.T) { if !specutils.HasCapabilities(capability.CAP_DAC_READ_SEARCH) { t.Skipf("Link test requires CAP_DAC_READ_SEARCH, running as %d", os.Getuid()) diff --git a/runsc/fsgofer/lisafs.go b/runsc/fsgofer/lisafs.go index 3da375608..fc96ddbdc 100644 --- a/runsc/fsgofer/lisafs.go +++ b/runsc/fsgofer/lisafs.go @@ -736,32 +736,12 @@ func (fd *controlFDLisa) Renamed() { // GetXattr implements lisafs.ControlFDImpl.GetXattr. func (fd *controlFDLisa) GetXattr(name string, size uint32, getValueBuf func(uint32) []byte) (uint16, error) { - if !fd.Conn().ServerImpl().(*LisafsServer).config.EnableVerityXattr { - return 0, unix.EOPNOTSUPP - } - if _, ok := verityXattrs[name]; !ok { - return 0, unix.EOPNOTSUPP - } - if size == 0 { - n, err := unix.Fgetxattr(fd.hostFD, name, nil) - if err != nil { - return 0, err - } - size = uint32(n) - } - n, err := unix.Fgetxattr(fd.hostFD, name, getValueBuf(size)) - return uint16(n), err + return 0, unix.EOPNOTSUPP } // SetXattr implements lisafs.ControlFDImpl.SetXattr. func (fd *controlFDLisa) SetXattr(name string, value string, flags uint32) error { - if !fd.Conn().ServerImpl().(*LisafsServer).config.EnableVerityXattr { - return unix.EOPNOTSUPP - } - if _, ok := verityXattrs[name]; !ok { - return unix.EOPNOTSUPP - } - return unix.Fsetxattr(fd.hostFD, name, []byte(value) /* sigh */, int(flags)) + return unix.EOPNOTSUPP } // ListXattr implements lisafs.ControlFDImpl.ListXattr. diff --git a/runsc/fsgofer/lisafs_test.go b/runsc/fsgofer/lisafs_test.go index 4653f9955..77bba9691 100644 --- a/runsc/fsgofer/lisafs_test.go +++ b/runsc/fsgofer/lisafs_test.go @@ -38,7 +38,7 @@ type tester struct{} // NewServer implements testsuite.Tester.NewServer. func (tester) NewServer(t *testing.T) *lisafs.Server { - return &fsgofer.NewLisafsServer(fsgofer.Config{HostUDS: true, EnableVerityXattr: true}).Server + return &fsgofer.NewLisafsServer(fsgofer.Config{HostUDS: true}).Server } // LinkSupported implements testsuite.Tester.LinkSupported. diff --git a/runsc/specutils/fs.go b/runsc/specutils/fs.go index ac20696ee..2eb92ac89 100644 --- a/runsc/specutils/fs.go +++ b/runsc/specutils/fs.go @@ -65,12 +65,6 @@ var optionsMap = map[string]mapping{ "sync": {set: true, val: unix.MS_SYNCHRONOUS}, } -// verityMountOptions is the set of valid verity mount option keys. -var verityMountOptions = map[string]struct{}{ - "verity.roothash": {}, - "verity.action": {}, -} - // propOptionsMap is similar to optionsMap, but it lists propagation options // that cannot be used together with other flags. var propOptionsMap = map[string]mapping{ @@ -140,8 +134,7 @@ func ValidateMountOptions(opts []string) error { } _, ok1 := optionsMap[o] _, ok2 := propOptionsMap[o] - _, ok3 := verityMountOptions[moptKey(o)] - if !ok1 && !ok2 && !ok3 { + if !ok1 && !ok2 { return fmt.Errorf("unknown mount option %q", o) } if err := validatePropagation(o); err != nil { diff --git a/test/perf/BUILD b/test/perf/BUILD index 4c8167689..08e04d8ad 100644 --- a/test/perf/BUILD +++ b/test/perf/BUILD @@ -146,38 +146,3 @@ syscall_test( debug = False, test = "//test/perf/linux:write_benchmark", ) - -syscall_test( - size = "large", - allow_native = False, - debug = False, - test = "//test/perf/linux:verity_open_benchmark", -) - -syscall_test( - size = "large", - allow_native = False, - debug = False, - test = "//test/perf/linux:verity_read_benchmark", -) - -syscall_test( - size = "large", - allow_native = False, - debug = False, - test = "//test/perf/linux:verity_randread_benchmark", -) - -syscall_test( - size = "large", - allow_native = False, - debug = False, - test = "//test/perf/linux:verity_open_read_close_benchmark", -) - -syscall_test( - size = "large", - allow_native = False, - debug = False, - test = "//test/perf/linux:verity_stat_benchmark", -) diff --git a/test/perf/linux/BUILD b/test/perf/linux/BUILD index a7a8a7d72..2ab3c955a 100644 --- a/test/perf/linux/BUILD +++ b/test/perf/linux/BUILD @@ -392,99 +392,3 @@ cc_binary( "//test/util:test_main", ], ) - -cc_binary( - name = "verity_open_benchmark", - testonly = 1, - srcs = [ - "verity_open_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:capability_util", - "//test/util:fs_util", - "//test/util:logging", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:verity_util", - ], -) - -cc_binary( - name = "verity_read_benchmark", - testonly = 1, - srcs = [ - "verity_read_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:capability_util", - "//test/util:fs_util", - "//test/util:logging", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:verity_util", - ], -) - -cc_binary( - name = "verity_randread_benchmark", - testonly = 1, - srcs = [ - "verity_randread_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:capability_util", - "//test/util:fs_util", - "//test/util:logging", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:verity_util", - ], -) - -cc_binary( - name = "verity_open_read_close_benchmark", - testonly = 1, - srcs = [ - "verity_open_read_close_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:capability_util", - "//test/util:fs_util", - "//test/util:logging", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:verity_util", - ], -) - -cc_binary( - name = "verity_stat_benchmark", - testonly = 1, - srcs = [ - "verity_stat_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:capability_util", - "//test/util:fs_util", - "//test/util:logging", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:verity_util", - "@com_google_absl//absl/strings", - ], -) diff --git a/test/perf/linux/verity_open_benchmark.cc b/test/perf/linux/verity_open_benchmark.cc deleted file mode 100644 index 026b6f101..000000000 --- a/test/perf/linux/verity_open_benchmark.cc +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2021 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. - -#include -#include -#include -#include - -#include -#include -#include - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/capability_util.h" -#include "test/util/fs_util.h" -#include "test/util/logging.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/verity_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -void BM_Open(benchmark::State& state) { - const int size = state.range(0); - std::vector cache; - std::vector targets; - - // Mount a tmpfs file system to be wrapped by a verity fs. - TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TEST_CHECK(mount("", dir.path().c_str(), "tmpfs", 0, "") == 0); - - for (int i = 0; i < size; i++) { - auto path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); - targets.emplace_back( - EnableTarget(std::string(Basename(path.path())), O_RDONLY)); - cache.emplace_back(std::move(path)); - } - - std::string verity_dir = - TEST_CHECK_NO_ERRNO_AND_VALUE(MountVerity(dir.path(), targets)); - - unsigned int seed = 1; - for (auto _ : state) { - const int chosen = rand_r(&seed) % size; - int fd = open(JoinPath(verity_dir, targets[chosen].path).c_str(), O_RDONLY); - TEST_CHECK(fd != -1); - close(fd); - } -} - -BENCHMARK(BM_Open)->Range(1, 128)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/verity_open_read_close_benchmark.cc b/test/perf/linux/verity_open_read_close_benchmark.cc deleted file mode 100644 index e77577f22..000000000 --- a/test/perf/linux/verity_open_read_close_benchmark.cc +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2021 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. - -#include -#include -#include -#include - -#include -#include -#include - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/capability_util.h" -#include "test/util/fs_util.h" -#include "test/util/logging.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/verity_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -void BM_VerityOpenReadClose(benchmark::State& state) { - const int size = state.range(0); - - // Mount a tmpfs file system to be wrapped by a verity fs. - TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TEST_CHECK(mount("", dir.path().c_str(), "tmpfs", 0, "") == 0); - - std::vector cache; - std::vector targets; - - for (int i = 0; i < size; i++) { - auto file = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(dir.path(), "some contents", 0644)); - targets.emplace_back( - EnableTarget(std::string(Basename(file.path())), O_RDONLY)); - cache.emplace_back(std::move(file)); - } - - std::string verity_dir = - TEST_CHECK_NO_ERRNO_AND_VALUE(MountVerity(dir.path(), targets)); - - char buf[1]; - unsigned int seed = 1; - for (auto _ : state) { - const int chosen = rand_r(&seed) % size; - int fd = open(JoinPath(verity_dir, targets[chosen].path).c_str(), O_RDONLY); - TEST_CHECK(fd != -1); - TEST_CHECK(read(fd, buf, 1) == 1); - close(fd); - } -} - -BENCHMARK(BM_VerityOpenReadClose)->Range(1000, 16384)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/verity_randread_benchmark.cc b/test/perf/linux/verity_randread_benchmark.cc deleted file mode 100644 index 4178cfad8..000000000 --- a/test/perf/linux/verity_randread_benchmark.cc +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2021 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. - -#include -#include -#include -#include -#include -#include - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/logging.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/verity_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Create a 1GB file that will be read from at random positions. This should -// invalid any performance gains from caching. -const uint64_t kFileSize = Megabytes(1024); - -// How many bytes to write at once to initialize the file used to read from. -const uint32_t kWriteSize = 65536; - -// Largest benchmarked read unit. -const uint32_t kMaxRead = Megabytes(64); - -// Global test state, initialized once per process lifetime. -struct GlobalState { - explicit GlobalState() { - // Mount a tmpfs file system to be wrapped by a verity fs. - tmp_dir_ = TempPath::CreateDir().ValueOrDie(); - TEST_CHECK(mount("", tmp_dir_.path().c_str(), "tmpfs", 0, "") == 0); - file_ = TempPath::CreateFileIn(tmp_dir_.path()).ValueOrDie(); - filename_ = std::string(Basename(file_.path())); - - FileDescriptor fd = Open(file_.path(), O_WRONLY).ValueOrDie(); - - // Try to minimize syscalls by using maximum size writev() requests. - std::vector buffer(kWriteSize); - RandomizeBuffer(buffer.data(), buffer.size()); - const std::vector> iovecs_list = - GenerateIovecs(kFileSize + kMaxRead, buffer.data(), buffer.size()); - for (const auto& iovecs : iovecs_list) { - TEST_CHECK(writev(fd.get(), iovecs.data(), iovecs.size()) >= 0); - } - verity_dir_ = - MountVerity(tmp_dir_.path(), {EnableTarget(filename_, O_RDONLY)}) - .ValueOrDie(); - } - TempPath tmp_dir_; - TempPath file_; - std::string verity_dir_; - std::string filename_; -}; - -GlobalState& GetGlobalState() { - // This gets created only once throughout the lifetime of the process. - // Use a dynamically allocated object (that is never deleted) to avoid order - // of destruction of static storage variables issues. - static GlobalState* const state = - // The actual file size is the maximum random seek range (kFileSize) + the - // maximum read size so we can read that number of bytes at the end of the - // file. - new GlobalState(); - return *state; -} - -void BM_VerityRandRead(benchmark::State& state) { - const int size = state.range(0); - - GlobalState& global_state = GetGlobalState(); - FileDescriptor verity_fd = ASSERT_NO_ERRNO_AND_VALUE(Open( - JoinPath(global_state.verity_dir_, global_state.filename_), O_RDONLY)); - std::vector buf(size); - - unsigned int seed = 1; - for (auto _ : state) { - TEST_CHECK(PreadFd(verity_fd.get(), buf.data(), buf.size(), - rand_r(&seed) % kFileSize) == size); - } - - state.SetBytesProcessed(static_cast(size) * - static_cast(state.iterations())); -} - -BENCHMARK(BM_VerityRandRead)->Range(1, kMaxRead)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/verity_read_benchmark.cc b/test/perf/linux/verity_read_benchmark.cc deleted file mode 100644 index 738b5ba45..000000000 --- a/test/perf/linux/verity_read_benchmark.cc +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2021 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. - -#include -#include -#include -#include - -#include -#include -#include - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/capability_util.h" -#include "test/util/fs_util.h" -#include "test/util/logging.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/verity_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -void BM_VerityRead(benchmark::State& state) { - const int size = state.range(0); - const std::string contents(size, 0); - - // Mount a tmpfs file system to be wrapped by a verity fs. - TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TEST_CHECK(mount("", dir.path().c_str(), "tmpfs", 0, "") == 0); - - auto path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - dir.path(), contents, TempPath::kDefaultFileMode)); - std::string filename = std::string(Basename(path.path())); - - std::string verity_dir = TEST_CHECK_NO_ERRNO_AND_VALUE( - MountVerity(dir.path(), {EnableTarget(filename, O_RDONLY)})); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(JoinPath(verity_dir, filename), O_RDONLY)); - std::vector buf(size); - for (auto _ : state) { - TEST_CHECK(PreadFd(fd.get(), buf.data(), buf.size(), 0) == size); - } - - state.SetBytesProcessed(static_cast(size) * - static_cast(state.iterations())); -} - -BENCHMARK(BM_VerityRead)->Range(1, 1 << 26)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/verity_stat_benchmark.cc b/test/perf/linux/verity_stat_benchmark.cc deleted file mode 100644 index b43a9e266..000000000 --- a/test/perf/linux/verity_stat_benchmark.cc +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2021 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. - -#include -#include -#include -#include - -#include -#include - -#include "gtest/gtest.h" -#include "absl/strings/str_cat.h" -#include "benchmark/benchmark.h" -#include "test/util/fs_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/verity_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Creates a file in a nested directory hierarchy at least `depth` directories -// deep, and stats that file multiple times. -void BM_VerityStat(benchmark::State& state) { - // Create nested directories with given depth. - int depth = state.range(0); - - // Mount a tmpfs file system to be wrapped by a verity fs. - TempPath top_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TEST_CHECK(mount("", top_dir.path().c_str(), "tmpfs", 0, "") == 0); - std::string dir_path = top_dir.path(); - std::string child_path = ""; - std::vector targets; - - while (depth-- > 0) { - // Don't use TempPath because it will make paths too long to use. - // - // The top_dir destructor will clean up this whole tree. - dir_path = JoinPath(dir_path, absl::StrCat(depth)); - ASSERT_NO_ERRNO(Mkdir(dir_path, 0755)); - child_path = JoinPath(child_path, Basename(dir_path)); - targets.emplace_back(EnableTarget(child_path, O_RDONLY)); - } - - // Create the file that will be stat'd. - const TempPath file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir_path)); - - targets.emplace_back( - EnableTarget(JoinPath(child_path, Basename(file.path())), O_RDONLY)); - - // Reverse the targets because verity should be enabled from the lowest level. - std::reverse(targets.begin(), targets.end()); - - std::string verity_dir = - TEST_CHECK_NO_ERRNO_AND_VALUE(MountVerity(top_dir.path(), targets)); - - struct stat st; - for (auto _ : state) { - ASSERT_THAT(stat(JoinPath(verity_dir, targets[0].path).c_str(), &st), - SyscallSucceeds()); - } -} - -BENCHMARK(BM_VerityStat)->Range(1, 100)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 9747c8656..b9fcf6f9d 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -225,10 +225,6 @@ syscall_test( test = "//test/syscalls/linux:getdents_test", ) -syscall_test( - test = "//test/syscalls/linux:verity_getdents_test", -) - syscall_test( test = "//test/syscalls/linux:getrandom_test", ) @@ -250,10 +246,6 @@ syscall_test( test = "//test/syscalls/linux:ioctl_test", ) -syscall_test( - test = "//test/syscalls/linux:verity_ioctl_test", -) - syscall_test( test = "//test/syscalls/linux:iptables_test", ) @@ -332,10 +324,6 @@ syscall_test( test = "//test/syscalls/linux:mmap_test", ) -syscall_test( - test = "//test/syscalls/linux:verity_mmap_test", -) - syscall_test( add_overlay = True, test = "//test/syscalls/linux:mount_test", @@ -926,10 +914,6 @@ syscall_test( test = "//test/syscalls/linux:symlink_test", ) -syscall_test( - test = "//test/syscalls/linux:verity_symlink_test", -) - syscall_test( add_overlay = True, test = "//test/syscalls/linux:sync_test", @@ -1071,10 +1055,6 @@ syscall_test( test = "//test/syscalls/linux:processes_test", ) -syscall_test( - test = "//test/syscalls/linux:verity_mount_test", -) - syscall_test( test = "//test/syscalls/linux:deleted_test", ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 36f3eff49..9919c710d 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -993,22 +993,6 @@ cc_binary( ], ) -cc_binary( - name = "verity_getdents_test", - testonly = 1, - srcs = ["verity_getdents.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - gtest, - "//test/util:fs_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:verity_util", - ], -) - cc_binary( name = "getrandom_test", testonly = 1, @@ -1060,23 +1044,6 @@ cc_binary( ], ) -cc_binary( - name = "verity_ioctl_test", - testonly = 1, - srcs = ["verity_ioctl.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - gtest, - "//test/util:fs_util", - "//test/util:mount_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:verity_util", - ], -) - cc_library( name = "iptables_types", testonly = 1, @@ -1360,23 +1327,6 @@ cc_binary( ], ) -cc_binary( - name = "verity_mmap_test", - testonly = 1, - srcs = ["verity_mmap.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - gtest, - "//test/util:fs_util", - "//test/util:memory_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:verity_util", - ], -) - cc_binary( name = "mount_test", testonly = 1, @@ -1400,21 +1350,6 @@ cc_binary( ], ) -cc_binary( - name = "verity_mount_test", - testonly = 1, - srcs = ["verity_mount.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:capability_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:verity_util", - ], -) - cc_binary( name = "mremap_test", testonly = 1, @@ -3852,23 +3787,6 @@ cc_binary( ], ) -cc_binary( - name = "verity_symlink_test", - testonly = 1, - srcs = ["verity_symlink.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - gtest, - "//test/util:fs_util", - "//test/util:mount_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:verity_util", - ], -) - cc_binary( name = "sync_test", testonly = 1, diff --git a/test/syscalls/linux/verity_getdents.cc b/test/syscalls/linux/verity_getdents.cc deleted file mode 100644 index 5764160c1..000000000 --- a/test/syscalls/linux/verity_getdents.cc +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2021 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. - -#include -#include -#include -#include -#include - -#include -#include - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/util/capability_util.h" -#include "test/util/fs_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/verity_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class GetDentsTest : public ::testing::Test { - protected: - void SetUp() override { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - // Mount a tmpfs file system, to be wrapped by a verity fs. - tmpfs_dir_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(mount("", tmpfs_dir_.path().c_str(), "tmpfs", 0, ""), - SyscallSucceeds()); - - // Create a new file in the tmpfs mount. - file_ = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(tmpfs_dir_.path(), kContents, 0777)); - filename_ = Basename(file_.path()); - } - - TempPath tmpfs_dir_; - TempPath file_; - std::string filename_; -}; - -TEST_F(GetDentsTest, GetDents) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - std::vector expect = {".", "..", filename_}; - EXPECT_NO_ERRNO(DirContains(verity_dir, expect, /*exclude=*/{})); -} - -TEST_F(GetDentsTest, Deleted) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - EXPECT_THAT(unlink(JoinPath(tmpfs_dir_.path(), filename_).c_str()), - SyscallSucceeds()); - - EXPECT_THAT(DirContains(verity_dir, /*expect=*/{}, /*exclude=*/{}), - PosixErrorIs(EIO, ::testing::_)); -} - -TEST_F(GetDentsTest, Renamed) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - std::string new_file_name = "renamed-" + filename_; - EXPECT_THAT(rename(JoinPath(tmpfs_dir_.path(), filename_).c_str(), - JoinPath(tmpfs_dir_.path(), new_file_name).c_str()), - SyscallSucceeds()); - - EXPECT_THAT(DirContains(verity_dir, /*expect=*/{}, /*exclude=*/{}), - PosixErrorIs(EIO, ::testing::_)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/verity_ioctl.cc b/test/syscalls/linux/verity_ioctl.cc deleted file mode 100644 index 050f2634a..000000000 --- a/test/syscalls/linux/verity_ioctl.cc +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright 2021 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. - -#include -#include -#include -#include -#include - -#include -#include - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/util/capability_util.h" -#include "test/util/fs_util.h" -#include "test/util/mount_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/verity_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class IoctlTest : public ::testing::Test { - protected: - void SetUp() override { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - // Mount a tmpfs file system, to be wrapped by a verity fs. - tmpfs_dir_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(mount("", tmpfs_dir_.path().c_str(), "tmpfs", 0, ""), - SyscallSucceeds()); - - // Create a new file in the tmpfs mount. - file_ = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(tmpfs_dir_.path(), kContents, 0777)); - filename_ = Basename(file_.path()); - } - - TempPath tmpfs_dir_; - TempPath file_; - std::string filename_; -}; - -TEST_F(IoctlTest, Enable) { - // Mount a verity fs on the existing tmpfs mount. - std::string mount_opts = "lower_path=" + tmpfs_dir_.path(); - auto const verity_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT( - mount("", verity_dir.path().c_str(), "verity", 0, mount_opts.c_str()), - SyscallSucceeds()); - - // Confirm that the verity flag is absent. - int flag = 0; - auto const fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(verity_dir.path(), filename_), O_RDONLY, 0777)); - ASSERT_THAT(ioctl(fd.get(), FS_IOC_GETFLAGS, &flag), SyscallSucceeds()); - EXPECT_EQ(flag & FS_VERITY_FL, 0); - - // Enable the file and confirm that the verity flag is present. - ASSERT_THAT(ioctl(fd.get(), FS_IOC_ENABLE_VERITY), SyscallSucceeds()); - ASSERT_THAT(ioctl(fd.get(), FS_IOC_GETFLAGS, &flag), SyscallSucceeds()); - EXPECT_EQ(flag & FS_VERITY_FL, FS_VERITY_FL); -} - -TEST_F(IoctlTest, Measure) { - // Mount a verity fs on the existing tmpfs mount. - std::string mount_opts = "lower_path=" + tmpfs_dir_.path(); - auto const verity_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT( - mount("", verity_dir.path().c_str(), "verity", 0, mount_opts.c_str()), - SyscallSucceeds()); - - // Confirm that the file cannot be measured. - auto const fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(verity_dir.path(), filename_), O_RDONLY, 0777)); - uint8_t digest_array[sizeof(struct fsverity_digest) + kMaxDigestSize] = {0}; - struct fsverity_digest* digest = - reinterpret_cast(digest_array); - digest->digest_size = kMaxDigestSize; - ASSERT_THAT(ioctl(fd.get(), FS_IOC_MEASURE_VERITY, digest), - SyscallFailsWithErrno(ENODATA)); - - // Enable the file and confirm that the file can be measured. - ASSERT_THAT(ioctl(fd.get(), FS_IOC_ENABLE_VERITY), SyscallSucceeds()); - ASSERT_THAT(ioctl(fd.get(), FS_IOC_MEASURE_VERITY, digest), - SyscallSucceeds()); - EXPECT_EQ(digest->digest_size, kDefaultDigestSize); -} - -TEST_F(IoctlTest, Mount) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - // Make sure the file can be open and read in the mounted verity fs. - auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777)); - char buf[sizeof(kContents)]; - EXPECT_THAT(ReadFd(verity_fd.get(), buf, sizeof(kContents)), - SyscallSucceeds()); -} - -TEST_F(IoctlTest, NonExistingFile) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - // Confirm that opening a non-existing file in the verity-enabled directory - // triggers the expected error instead of verification failure. - EXPECT_THAT( - open(JoinPath(verity_dir, filename_ + "abc").c_str(), O_RDONLY, 0777), - SyscallFailsWithErrno(ENOENT)); -} - -TEST_F(IoctlTest, ModifiedFile) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - // Modify the file and check verification failure upon reading from it. - auto const fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(tmpfs_dir_.path(), filename_), O_RDWR, 0777)); - ASSERT_NO_ERRNO(FlipRandomBit(fd.get(), sizeof(kContents) - 1)); - - auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777)); - char buf[sizeof(kContents)]; - EXPECT_THAT(pread(verity_fd.get(), buf, 16, 0), SyscallFailsWithErrno(EIO)); -} - -TEST_F(IoctlTest, ModifiedMerkle) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - // Modify the Merkle file and check verification failure upon opening the - // corresponding file. - auto const fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(MerklePath(JoinPath(tmpfs_dir_.path(), filename_)), O_RDWR, 0777)); - auto stat = ASSERT_NO_ERRNO_AND_VALUE(Fstat(fd.get())); - ASSERT_NO_ERRNO(FlipRandomBit(fd.get(), stat.st_size)); - - EXPECT_THAT(open(JoinPath(verity_dir, filename_).c_str(), O_RDONLY, 0777), - SyscallFailsWithErrno(EIO)); -} - -TEST_F(IoctlTest, ModifiedDirMerkle) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - // Modify the Merkle file for the parent directory and check verification - // failure upon opening the corresponding file. - auto const fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(MerkleRootPath(JoinPath(tmpfs_dir_.path(), "root")), O_RDWR, 0777)); - auto stat = ASSERT_NO_ERRNO_AND_VALUE(Fstat(fd.get())); - ASSERT_NO_ERRNO(FlipRandomBit(fd.get(), stat.st_size)); - - EXPECT_THAT(open(JoinPath(verity_dir, filename_).c_str(), O_RDONLY, 0777), - SyscallFailsWithErrno(EIO)); -} - -TEST_F(IoctlTest, Stat) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - struct stat st; - EXPECT_THAT(stat(JoinPath(verity_dir, filename_).c_str(), &st), - SyscallSucceeds()); -} - -TEST_F(IoctlTest, ModifiedStat) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - EXPECT_THAT(chmod(JoinPath(tmpfs_dir_.path(), filename_).c_str(), 0644), - SyscallSucceeds()); - struct stat st; - EXPECT_THAT(stat(JoinPath(verity_dir, filename_).c_str(), &st), - SyscallFailsWithErrno(EIO)); -} - -TEST_F(IoctlTest, DeleteFile) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - EXPECT_THAT(unlink(JoinPath(tmpfs_dir_.path(), filename_).c_str()), - SyscallSucceeds()); - EXPECT_THAT(open(JoinPath(verity_dir, filename_).c_str(), O_RDONLY, 0777), - SyscallFailsWithErrno(EIO)); -} - -TEST_F(IoctlTest, DeleteMerkle) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - EXPECT_THAT( - unlink(MerklePath(JoinPath(tmpfs_dir_.path(), filename_)).c_str()), - SyscallSucceeds()); - EXPECT_THAT(open(JoinPath(verity_dir, filename_).c_str(), O_RDONLY, 0777), - SyscallFailsWithErrno(EIO)); -} - -TEST_F(IoctlTest, RenameFile) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - std::string new_file_name = "renamed-" + filename_; - EXPECT_THAT(rename(JoinPath(tmpfs_dir_.path(), filename_).c_str(), - JoinPath(tmpfs_dir_.path(), new_file_name).c_str()), - SyscallSucceeds()); - EXPECT_THAT(open(JoinPath(verity_dir, filename_).c_str(), O_RDONLY, 0777), - SyscallFailsWithErrno(EIO)); -} - -TEST_F(IoctlTest, RenameMerkle) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - std::string new_file_name = "renamed-" + filename_; - EXPECT_THAT( - rename(MerklePath(JoinPath(tmpfs_dir_.path(), filename_)).c_str(), - MerklePath(JoinPath(tmpfs_dir_.path(), new_file_name)).c_str()), - SyscallSucceeds()); - EXPECT_THAT(open(JoinPath(verity_dir, filename_).c_str(), O_RDONLY, 0777), - SyscallFailsWithErrno(EIO)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/verity_mmap.cc b/test/syscalls/linux/verity_mmap.cc deleted file mode 100644 index d72afc742..000000000 --- a/test/syscalls/linux/verity_mmap.cc +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2021 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. - -#include -#include -#include -#include - -#include - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/util/capability_util.h" -#include "test/util/fs_util.h" -#include "test/util/memory_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/verity_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class MmapTest : public ::testing::Test { - protected: - void SetUp() override { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - // Mount a tmpfs file system, to be wrapped by a verity fs. - tmpfs_dir_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(mount("", tmpfs_dir_.path().c_str(), "tmpfs", 0, ""), - SyscallSucceeds()); - - // Create a new file in the tmpfs mount. - file_ = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(tmpfs_dir_.path(), kContents, 0777)); - filename_ = Basename(file_.path()); - } - - TempPath tmpfs_dir_; - TempPath file_; - std::string filename_; -}; - -TEST_F(MmapTest, MmapRead) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - // Make sure the file can be open and mmapped in the mounted verity fs. - auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777)); - - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(Mmap(nullptr, sizeof(kContents) - 1, PROT_READ, - MAP_SHARED, verity_fd.get(), 0)); - EXPECT_THAT(std::string(m.view()), ::testing::StrEq(kContents)); -} - -TEST_F(MmapTest, ModifiedBeforeMmap) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - // Modify the file and check verification failure upon mmapping. - auto const fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(tmpfs_dir_.path(), filename_), O_RDWR, 0777)); - ASSERT_NO_ERRNO(FlipRandomBit(fd.get(), sizeof(kContents) - 1)); - - auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777)); - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(Mmap(nullptr, sizeof(kContents) - 1, PROT_READ, - MAP_SHARED, verity_fd.get(), 0)); - - // Memory fault is expected when Translate fails. - EXPECT_EXIT(std::string(m.view()), ::testing::KilledBySignal(SIGSEGV), ""); -} - -TEST_F(MmapTest, ModifiedAfterMmap) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777)); - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(Mmap(nullptr, sizeof(kContents) - 1, PROT_READ, - MAP_SHARED, verity_fd.get(), 0)); - - // Modify the file after mapping and check verification failure upon mmapping. - auto const fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(tmpfs_dir_.path(), filename_), O_RDWR, 0777)); - ASSERT_NO_ERRNO(FlipRandomBit(fd.get(), sizeof(kContents) - 1)); - - // Memory fault is expected when Translate fails. - EXPECT_EXIT(std::string(m.view()), ::testing::KilledBySignal(SIGSEGV), ""); -} - -class MmapParamTest - : public MmapTest, - public ::testing::WithParamInterface> { - protected: - int prot() const { return std::get<0>(GetParam()); } - int flags() const { return std::get<1>(GetParam()); } -}; - -INSTANTIATE_TEST_SUITE_P( - WriteExecNoneSharedPrivate, MmapParamTest, - ::testing::Combine(::testing::ValuesIn({ - PROT_WRITE, - PROT_EXEC, - PROT_NONE, - }), - ::testing::ValuesIn({MAP_SHARED, MAP_PRIVATE}))); - -TEST_P(MmapParamTest, Mmap) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( - MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)})); - - // Make sure the file can be open and mmapped in the mounted verity fs. - auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777)); - - if (prot() == PROT_WRITE && flags() == MAP_SHARED) { - // Verity file system is read-only. - EXPECT_THAT( - reinterpret_cast(mmap(nullptr, sizeof(kContents) - 1, prot(), - flags(), verity_fd.get(), 0)), - SyscallFailsWithErrno(EACCES)); - } else { - Mapping const m = ASSERT_NO_ERRNO_AND_VALUE(Mmap( - nullptr, sizeof(kContents) - 1, prot(), flags(), verity_fd.get(), 0)); - if (prot() == PROT_NONE) { - // Memory mapped by MAP_NONE cannot be accessed. - EXPECT_EXIT(std::string(m.view()), ::testing::KilledBySignal(SIGSEGV), - ""); - } else { - EXPECT_THAT(std::string(m.view()), ::testing::StrEq(kContents)); - } - } -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/verity_mount.cc b/test/syscalls/linux/verity_mount.cc deleted file mode 100644 index 66f96ae55..000000000 --- a/test/syscalls/linux/verity_mount.cc +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2021 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. - -#include - -#include -#include - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/util/capability_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/verity_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Mount verity file system on an existing tmpfs mount. -TEST(MountTest, MountExisting) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - // Mount a new tmpfs file system. - auto const tmpfs_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(mount("", tmpfs_dir.path().c_str(), "tmpfs", 0, ""), - SyscallSucceeds()); - - // Mount a verity file system on the existing gofer mount. - auto const verity_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - std::string opts = "lower_path=" + tmpfs_dir.path(); - ASSERT_THAT(mount("", verity_dir.path().c_str(), "verity", 0, opts.c_str()), - SyscallSucceeds()); - auto const fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(verity_dir.path(), O_RDONLY, 0777)); - EXPECT_THAT(ioctl(fd.get(), FS_IOC_ENABLE_VERITY), SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/verity_symlink.cc b/test/syscalls/linux/verity_symlink.cc deleted file mode 100644 index 6bb976e70..000000000 --- a/test/syscalls/linux/verity_symlink.cc +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2021 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. - -#include -#include -#include -#include - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/util/capability_util.h" -#include "test/util/fs_util.h" -#include "test/util/mount_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/verity_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -const char kSymlink[] = "verity_symlink"; - -class SymlinkTest : public ::testing::Test { - protected: - void SetUp() override { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - // Mount a tmpfs file system, to be wrapped by a verity fs. - tmpfs_dir_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(mount("", tmpfs_dir_.path().c_str(), "tmpfs", 0, ""), - SyscallSucceeds()); - - // Create a new file in the tmpfs mount. - file_ = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(tmpfs_dir_.path(), kContents, 0777)); - filename_ = Basename(file_.path()); - - // Create a symlink to the file. - ASSERT_THAT(symlink(file_.path().c_str(), - JoinPath(tmpfs_dir_.path(), kSymlink).c_str()), - SyscallSucceeds()); - } - - TempPath tmpfs_dir_; - TempPath file_; - std::string filename_; -}; - -TEST_F(SymlinkTest, Success) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(MountVerity( - tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY), - EnableTarget(kSymlink, O_RDONLY | O_NOFOLLOW)})); - - char buf[256]; - EXPECT_THAT( - readlink(JoinPath(verity_dir, kSymlink).c_str(), buf, sizeof(buf)), - SyscallSucceeds()); - auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(verity_dir, kSymlink).c_str(), O_RDONLY, 0777)); - EXPECT_THAT(ReadFd(verity_fd.get(), buf, sizeof(kContents)), - SyscallSucceeds()); -} - -TEST_F(SymlinkTest, DeleteLink) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(MountVerity( - tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY), - EnableTarget(kSymlink, O_RDONLY | O_NOFOLLOW)})); - - ASSERT_THAT(unlink(JoinPath(tmpfs_dir_.path(), kSymlink).c_str()), - SyscallSucceeds()); - char buf[256]; - EXPECT_THAT( - readlink(JoinPath(verity_dir, kSymlink).c_str(), buf, sizeof(buf)), - SyscallFailsWithErrno(EIO)); - EXPECT_THAT(open(JoinPath(verity_dir, kSymlink).c_str(), O_RDONLY, 0777), - SyscallFailsWithErrno(EIO)); -} - -TEST_F(SymlinkTest, ModifyLink) { - std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(MountVerity( - tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY), - EnableTarget(kSymlink, O_RDONLY | O_NOFOLLOW)})); - - ASSERT_THAT(unlink(JoinPath(tmpfs_dir_.path(), kSymlink).c_str()), - SyscallSucceeds()); - - std::string newlink = "newlink"; - ASSERT_THAT(symlink(JoinPath(tmpfs_dir_.path(), newlink).c_str(), - JoinPath(tmpfs_dir_.path(), kSymlink).c_str()), - SyscallSucceeds()); - char buf[256]; - EXPECT_THAT( - readlink(JoinPath(verity_dir, kSymlink).c_str(), buf, sizeof(buf)), - SyscallFailsWithErrno(EIO)); - EXPECT_THAT(open(JoinPath(verity_dir, kSymlink).c_str(), O_RDONLY, 0777), - SyscallFailsWithErrno(EIO)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/util/BUILD b/test/util/BUILD index 2dcf71613..f4ce5ad77 100644 --- a/test/util/BUILD +++ b/test/util/BUILD @@ -409,19 +409,6 @@ cc_library( ], ) -cc_library( - name = "verity_util", - testonly = 1, - srcs = ["verity_util.cc"], - hdrs = ["verity_util.h"], - deps = [ - ":fs_util", - ":mount_util", - ":posix_error", - ":temp_path", - ], -) - cc_library( name = "socket_util", testonly = 1, diff --git a/test/util/verity_util.cc b/test/util/verity_util.cc deleted file mode 100644 index b7d1cb212..000000000 --- a/test/util/verity_util.cc +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2021 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. - -#include "test/util/verity_util.h" - -#include "test/util/fs_util.h" -#include "test/util/mount_util.h" -#include "test/util/temp_path.h" - -namespace gvisor { -namespace testing { - -std::string BytesToHexString(uint8_t bytes[], int size) { - std::stringstream ss; - ss << std::hex; - for (int i = 0; i < size; ++i) { - ss << std::setw(2) << std::setfill('0') << static_cast(bytes[i]); - } - return ss.str(); -} - -std::string MerklePath(absl::string_view path) { - return JoinPath(Dirname(path), - std::string(kMerklePrefix) + std::string(Basename(path))); -} - -std::string MerkleRootPath(absl::string_view path) { - return JoinPath(Dirname(path), - std::string(kMerkleRootPrefix) + std::string(Basename(path))); -} - -PosixError FlipRandomBit(int fd, int size) { - // Generate a random offset in the file. - srand(time(nullptr)); - unsigned int seed = 0; - int random_offset = rand_r(&seed) % size; - - // Read a random byte and flip a bit in it. - char buf[1]; - RETURN_ERROR_IF_SYSCALL_FAIL(PreadFd(fd, buf, 1, random_offset)); - buf[0] ^= 1; - RETURN_ERROR_IF_SYSCALL_FAIL(PwriteFd(fd, buf, 1, random_offset)); - return NoError(); -} - -PosixErrorOr MountVerity(std::string lower_dir, - std::vector targets) { - // Mount a verity fs on the existing mount. - std::string mount_opts = "lower_path=" + lower_dir; - ASSIGN_OR_RETURN_ERRNO(TempPath verity_dir, TempPath::CreateDir()); - RETURN_ERROR_IF_SYSCALL_FAIL( - mount("", verity_dir.path().c_str(), "verity", 0, mount_opts.c_str())); - - for (const EnableTarget& target : targets) { - ASSIGN_OR_RETURN_ERRNO( - auto target_fd, - Open(JoinPath(verity_dir.path(), target.path), target.flags, 0777)); - RETURN_ERROR_IF_SYSCALL_FAIL(ioctl(target_fd.get(), FS_IOC_ENABLE_VERITY)); - } - - ASSIGN_OR_RETURN_ERRNO(auto dir_fd, Open(verity_dir.path(), O_RDONLY, 0777)); - RETURN_ERROR_IF_SYSCALL_FAIL(ioctl(dir_fd.get(), FS_IOC_ENABLE_VERITY)); - - // Measure the root hash. - uint8_t digest_array[sizeof(struct fsverity_digest) + kMaxDigestSize] = {0}; - struct fsverity_digest* digest = - reinterpret_cast(digest_array); - digest->digest_size = kMaxDigestSize; - RETURN_ERROR_IF_SYSCALL_FAIL( - ioctl(dir_fd.get(), FS_IOC_MEASURE_VERITY, digest)); - - // Mount a verity fs with specified root hash. - mount_opts += - ",root_hash=" + BytesToHexString(digest->digest, digest->digest_size); - ASSIGN_OR_RETURN_ERRNO(TempPath verity_with_hash_dir, TempPath::CreateDir()); - RETURN_ERROR_IF_SYSCALL_FAIL(mount("", verity_with_hash_dir.path().c_str(), - "verity", 0, mount_opts.c_str())); - - // Verity directories should not be deleted. Release the TempPath objects to - // prevent those directories from being deleted by the destructor. - verity_dir.release(); - return verity_with_hash_dir.release(); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/verity_util.h b/test/util/verity_util.h deleted file mode 100644 index ebb78b4bb..000000000 --- a/test/util/verity_util.h +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2021 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. - -#ifndef GVISOR_TEST_UTIL_VERITY_UTIL_H_ -#define GVISOR_TEST_UTIL_VERITY_UTIL_H_ - -#include - -#include - -#include "test/util/posix_error.h" - -namespace gvisor { -namespace testing { - -#ifndef FS_IOC_ENABLE_VERITY -#define FS_IOC_ENABLE_VERITY 1082156677 -#endif - -#ifndef FS_IOC_MEASURE_VERITY -#define FS_IOC_MEASURE_VERITY 3221513862 -#endif - -#ifndef FS_VERITY_FL -#define FS_VERITY_FL 1048576 -#endif - -#ifndef FS_IOC_GETFLAGS -#define FS_IOC_GETFLAGS 2148034049 -#endif - -struct fsverity_digest { - unsigned short digest_algorithm; - unsigned short digest_size; /* input/output */ - unsigned char digest[]; -}; - -struct EnableTarget { - std::string path; - int flags; - - EnableTarget(std::string path, int flags) : path(path), flags(flags) {} -}; - -constexpr int kMaxDigestSize = 64; -constexpr int kDefaultDigestSize = 32; -constexpr char kContents[] = "foobarbaz"; -constexpr char kMerklePrefix[] = ".merkle.verity."; -constexpr char kMerkleRootPrefix[] = ".merkleroot.verity."; - -// Get the Merkle tree file path for |path|. -std::string MerklePath(absl::string_view path); - -// Get the root Merkle tree file path for |path|. -std::string MerkleRootPath(absl::string_view path); - -// Provide a function to convert bytes to hex string, since -// absl::BytesToHexString does not seem to be compatible with golang -// hex.DecodeString used in verity due to zero-padding. -std::string BytesToHexString(uint8_t bytes[], int size); - -// Flip a random bit in the file represented by fd. -PosixError FlipRandomBit(int fd, int size); - -// Mount a verity on the tmpfs and enable both the file and the direcotry. Then -// mount a new verity with measured root hash. -PosixErrorOr MountVerity(std::string tmpfs_dir, - std::vector targets); - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_VERITY_UTIL_H_ diff --git a/tools/verity/BUILD b/tools/verity/BUILD deleted file mode 100644 index 77d16359c..000000000 --- a/tools/verity/BUILD +++ /dev/null @@ -1,15 +0,0 @@ -load("//tools:defs.bzl", "go_binary") - -licenses(["notice"]) - -go_binary( - name = "measure_tool", - srcs = [ - "measure_tool.go", - "measure_tool_unsafe.go", - ], - pure = True, - deps = [ - "//pkg/abi/linux", - ], -) diff --git a/tools/verity/measure_tool.go b/tools/verity/measure_tool.go deleted file mode 100644 index 4a0bc497a..000000000 --- a/tools/verity/measure_tool.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2021 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. - -// This binary can be used to run a measurement of the verity file system, -// generate the corresponding Merkle tree files, and return the root hash. -package main - -import ( - "flag" - "io/ioutil" - "log" - "os" - "strings" - "syscall" - - "gvisor.dev/gvisor/pkg/abi/linux" -) - -var path = flag.String("path", "", "path to the verity file system.") -var rawpath = flag.String("rawpath", "", "path to the raw file system.") - -const maxDigestSize = 64 - -type digest struct { - metadata linux.DigestMetadata - digest [maxDigestSize]byte -} - -func main() { - flag.Parse() - if *path == "" { - log.Fatalf("no path provided") - } - if *rawpath == "" { - log.Fatalf("no rawpath provided") - } - // TODO(b/182315468): Optimize the Merkle tree generate process to - // allow only updating certain files/directories. - if err := clearMerkle(*rawpath); err != nil { - log.Fatalf("Failed to clear merkle files in %s: %v", *rawpath, err) - } - if err := enableDir(*path); err != nil { - log.Fatalf("Failed to enable file system %s: %v", *path, err) - } - // Print the root hash of the file system to stdout. - if err := measure(*path); err != nil { - log.Fatalf("Failed to measure file system %s: %v", *path, err) - } -} - -func clearMerkle(path string) error { - files, err := ioutil.ReadDir(path) - if err != nil { - return err - } - - for _, file := range files { - if file.IsDir() { - if err := clearMerkle(path + "/" + file.Name()); err != nil { - return err - } - } else if strings.HasPrefix(file.Name(), ".merkle.verity") { - if err := os.Remove(path + "/" + file.Name()); err != nil { - return err - } - } - } - return nil -} - -// enableDir enables verity features on all the files and sub-directories within -// path. -func enableDir(path string) error { - files, err := ioutil.ReadDir(path) - if err != nil { - return err - } - for _, file := range files { - if file.IsDir() { - // For directories, first enable its children. - if err := enableDir(path + "/" + file.Name()); err != nil { - return err - } - } else if file.Mode().IsRegular() { - // For regular files, open and enable verity feature. - f, err := os.Open(path + "/" + file.Name()) - if err != nil { - return err - } - var p uintptr - if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(f.Fd()), uintptr(linux.FS_IOC_ENABLE_VERITY), p); err != 0 { - return err - } - } - } - // Once all children are enabled, enable the parent directory. - f, err := os.Open(path) - if err != nil { - return err - } - var p uintptr - if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(f.Fd()), uintptr(linux.FS_IOC_ENABLE_VERITY), p); err != 0 { - return err - } - return nil -} diff --git a/tools/verity/measure_tool_unsafe.go b/tools/verity/measure_tool_unsafe.go deleted file mode 100644 index d1864ed54..000000000 --- a/tools/verity/measure_tool_unsafe.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2021 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 main - -import ( - "encoding/hex" - "fmt" - "os" - "syscall" - "unsafe" - - "gvisor.dev/gvisor/pkg/abi/linux" -) - -// measure prints the hash of path to stdout. -func measure(path string) error { - f, err := os.Open(path) - if err != nil { - return err - } - var digest digest - digest.metadata.DigestSize = maxDigestSize - if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(f.Fd()), uintptr(linux.FS_IOC_MEASURE_VERITY), uintptr(unsafe.Pointer(&digest))); err != 0 { - return err - } - fmt.Fprintf(os.Stdout, "%s\n", hex.EncodeToString(digest.digest[:digest.metadata.DigestSize])) - return err -}