Add verity tests for modified file/Merkle file

PiperOrigin-RevId: 369909691
This commit is contained in:
Chong Cai
2021-04-22 11:12:56 -07:00
committed by gVisor bot
parent 47bc115158
commit dbfdb31e8a
+127 -37
View File
@@ -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());
// 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),
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());
std::string verity_dir =
ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_));
// 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(ReadFd(verity_fd.get(), buf, sizeof(kContents)),
SyscallSucceeds());
}
// 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, 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));
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(), 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