summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChong Cai <chongc@google.com>2021-04-22 11:10:35 -0700
committergVisor bot <gvisor-bot@google.com>2021-04-22 11:12:56 -0700
commitdbfdb31e8a014e5e11092de121e825b21c2804c3 (patch)
tree09961c16aa872d3238050d37626b1f512342b34a
parent47bc115158397024841aa3747be7558b2c317cbb (diff)
Add verity tests for modified file/Merkle file
PiperOrigin-RevId: 369909691
-rw-r--r--test/syscalls/linux/verity_ioctl.cc160
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