mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add verity tests for modified file/Merkle file
PiperOrigin-RevId: 369909691
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user