diff options
author | Chong Cai <chongc@google.com> | 2021-04-22 11:10:35 -0700 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2021-04-22 11:12:56 -0700 |
commit | dbfdb31e8a014e5e11092de121e825b21c2804c3 (patch) | |
tree | 09961c16aa872d3238050d37626b1f512342b34a /test/syscalls | |
parent | 47bc115158397024841aa3747be7558b2c317cbb (diff) |
Add verity tests for modified file/Merkle file
PiperOrigin-RevId: 369909691
Diffstat (limited to 'test/syscalls')
-rw-r--r-- | test/syscalls/linux/verity_ioctl.cc | 160 |
1 files changed, 125 insertions, 35 deletions
diff --git a/test/syscalls/linux/verity_ioctl.cc b/test/syscalls/linux/verity_ioctl.cc index a81fe5724..48c69ead3 100644 --- a/test/syscalls/linux/verity_ioctl.cc +++ b/test/syscalls/linux/verity_ioctl.cc @@ -13,7 +13,9 @@ // limitations under the License. #include <stdint.h> +#include <stdlib.h> #include <sys/mount.h> +#include <time.h> #include <iomanip> #include <sstream> @@ -56,6 +58,8 @@ struct fsverity_digest { constexpr int kMaxDigestSize = 64; constexpr int kDefaultDigestSize = 32; constexpr char kContents[] = "foobarbaz"; +constexpr char kMerklePrefix[] = ".merkle.verity."; +constexpr char kMerkleRootPrefix[] = ".merkleroot.verity."; class IoctlTest : public ::testing::Test { protected: @@ -92,6 +96,68 @@ std::string BytesToHexString(uint8_t bytes[], int size) { 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))); +} + +// Flip a random bit in the file represented by fd. +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(); +} + +// Mount a verity on the tmpfs and enable both the file and the direcotry. Then +// mount a new verity with measured root hash. +PosixErrorOr<std::string> MountVerity(std::string tmpfs_dir, + std::string filename) { + // Mount a verity fs on the existing tmpfs mount. + std::string mount_opts = "lower_path=" + tmpfs_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())); + + // Enable both the file and the directory. + ASSIGN_OR_RETURN_ERRNO( + auto fd, Open(JoinPath(verity_dir.path(), filename), O_RDONLY, 0777)); + RETURN_ERROR_IF_SYSCALL_FAIL(ioctl(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<struct fsverity_digest*>(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(); +} + TEST_F(IoctlTest, Enable) { // Mount a verity fs on the existing tmpfs mount. std::string mount_opts = "lower_path=" + tmpfs_dir_.path(); @@ -139,47 +205,71 @@ TEST_F(IoctlTest, Measure) { } TEST_F(IoctlTest, Mount) { - // Mount a verity fs on the existing tmpfs mount. - std::string mount_opts = "lower_path=" + tmpfs_dir_.path(); - auto verity_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT( - mount("", verity_dir.path().c_str(), "verity", 0, mount_opts.c_str()), - SyscallSucceeds()); + std::string verity_dir = + ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); - // Enable both the file and the directory. - auto const fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(verity_dir.path(), filename_), O_RDONLY, 0777)); - ASSERT_THAT(ioctl(fd.get(), FS_IOC_ENABLE_VERITY), SyscallSucceeds()); - auto const dir_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(verity_dir.path(), O_RDONLY, 0777)); - ASSERT_THAT(ioctl(dir_fd.get(), FS_IOC_ENABLE_VERITY), SyscallSucceeds()); - - // Measure the root hash. - uint8_t digest_array[sizeof(struct fsverity_digest) + kMaxDigestSize] = {0}; - struct fsverity_digest* digest = - reinterpret_cast<struct fsverity_digest*>(digest_array); - digest->digest_size = kMaxDigestSize; - ASSERT_THAT(ioctl(dir_fd.get(), FS_IOC_MEASURE_VERITY, digest), + // 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()); +} - // Mount a verity fs with specified root hash. - mount_opts += - ",root_hash=" + BytesToHexString(digest->digest, digest->digest_size); - auto verity_with_hash_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(mount("", verity_with_hash_dir.path().c_str(), "verity", 0, - mount_opts.c_str()), - SyscallSucceeds()); +TEST_F(IoctlTest, NonExistingFile) { + std::string verity_dir = + ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + + // 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(), filename_)); + + // 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)); - // 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_with_hash_dir.path(), filename_), O_RDONLY, 0777)); - char buf[16]; - EXPECT_THAT(ReadFd(fd.get(), buf, sizeof(kContents)), SyscallSucceeds()); + Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777)); + char buf[sizeof(kContents)]; + EXPECT_THAT(pread(verity_fd.get(), buf, 16, 0), SyscallFailsWithErrno(EIO)); +} - // Verity directories should not be deleted. Release the TempPath objects to - // prevent those directories from being deleted by the destructor. - verity_dir.release(); - verity_with_hash_dir.release(); +TEST_F(IoctlTest, ModifiedMerkle) { + std::string verity_dir = + ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + + // 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(), filename_)); + + // 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)); } } // namespace |