diff options
-rw-r--r-- | test/syscalls/BUILD | 4 | ||||
-rw-r--r-- | test/syscalls/linux/BUILD | 17 | ||||
-rw-r--r-- | test/syscalls/linux/verity_getdents.cc | 12 | ||||
-rw-r--r-- | test/syscalls/linux/verity_ioctl.cc | 44 | ||||
-rw-r--r-- | test/syscalls/linux/verity_mmap.cc | 16 | ||||
-rw-r--r-- | test/syscalls/linux/verity_symlink.cc | 117 | ||||
-rw-r--r-- | test/util/verity_util.cc | 13 | ||||
-rw-r--r-- | test/util/verity_util.h | 12 |
8 files changed, 196 insertions, 39 deletions
diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index de08091af..213c7e96c 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -884,6 +884,10 @@ syscall_test( ) syscall_test( + test = "//test/syscalls/linux:verity_symlink_test", +) + +syscall_test( add_overlay = True, test = "//test/syscalls/linux:sync_test", ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 5ca655803..984122bb5 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -3723,6 +3723,23 @@ 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, # Android does not support syncfs in r22. diff --git a/test/syscalls/linux/verity_getdents.cc b/test/syscalls/linux/verity_getdents.cc index 093595dd3..2eafc3dd3 100644 --- a/test/syscalls/linux/verity_getdents.cc +++ b/test/syscalls/linux/verity_getdents.cc @@ -58,16 +58,16 @@ class GetDentsTest : public ::testing::Test { }; TEST_F(GetDentsTest, GetDents) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); std::vector<std::string> 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(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); EXPECT_THAT(unlink(JoinPath(tmpfs_dir_.path(), filename_).c_str()), SyscallSucceeds()); @@ -77,8 +77,8 @@ TEST_F(GetDentsTest, Deleted) { } TEST_F(GetDentsTest, Renamed) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); std::string new_file_name = "renamed-" + filename_; EXPECT_THAT(rename(JoinPath(tmpfs_dir_.path(), filename_).c_str(), diff --git a/test/syscalls/linux/verity_ioctl.cc b/test/syscalls/linux/verity_ioctl.cc index be91b23d0..e7e4fa64b 100644 --- a/test/syscalls/linux/verity_ioctl.cc +++ b/test/syscalls/linux/verity_ioctl.cc @@ -105,8 +105,8 @@ TEST_F(IoctlTest, Measure) { } TEST_F(IoctlTest, Mount) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); // Make sure the file can be open and read in the mounted verity fs. auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( @@ -117,8 +117,8 @@ TEST_F(IoctlTest, Mount) { } TEST_F(IoctlTest, NonExistingFile) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); // Confirm that opening a non-existing file in the verity-enabled directory // triggers the expected error instead of verification failure. @@ -128,8 +128,8 @@ TEST_F(IoctlTest, NonExistingFile) { } TEST_F(IoctlTest, ModifiedFile) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); // Modify the file and check verification failure upon reading from it. auto const fd = ASSERT_NO_ERRNO_AND_VALUE( @@ -143,8 +143,8 @@ TEST_F(IoctlTest, ModifiedFile) { } TEST_F(IoctlTest, ModifiedMerkle) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); // Modify the Merkle file and check verification failure upon opening the // corresponding file. @@ -158,8 +158,8 @@ TEST_F(IoctlTest, ModifiedMerkle) { } TEST_F(IoctlTest, ModifiedDirMerkle) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); // Modify the Merkle file for the parent directory and check verification // failure upon opening the corresponding file. @@ -173,8 +173,8 @@ TEST_F(IoctlTest, ModifiedDirMerkle) { } TEST_F(IoctlTest, Stat) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); struct stat st; EXPECT_THAT(stat(JoinPath(verity_dir, filename_).c_str(), &st), @@ -182,8 +182,8 @@ TEST_F(IoctlTest, Stat) { } TEST_F(IoctlTest, ModifiedStat) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); EXPECT_THAT(chmod(JoinPath(tmpfs_dir_.path(), filename_).c_str(), 0644), SyscallSucceeds()); @@ -193,8 +193,8 @@ TEST_F(IoctlTest, ModifiedStat) { } TEST_F(IoctlTest, DeleteFile) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); EXPECT_THAT(unlink(JoinPath(tmpfs_dir_.path(), filename_).c_str()), SyscallSucceeds()); @@ -203,8 +203,8 @@ TEST_F(IoctlTest, DeleteFile) { } TEST_F(IoctlTest, DeleteMerkle) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); EXPECT_THAT( unlink(MerklePath(JoinPath(tmpfs_dir_.path(), filename_)).c_str()), @@ -214,8 +214,8 @@ TEST_F(IoctlTest, DeleteMerkle) { } TEST_F(IoctlTest, RenameFile) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); std::string new_file_name = "renamed-" + filename_; EXPECT_THAT(rename(JoinPath(tmpfs_dir_.path(), filename_).c_str(), @@ -226,8 +226,8 @@ TEST_F(IoctlTest, RenameFile) { } TEST_F(IoctlTest, RenameMerkle) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); std::string new_file_name = "renamed-" + filename_; EXPECT_THAT( diff --git a/test/syscalls/linux/verity_mmap.cc b/test/syscalls/linux/verity_mmap.cc index dde74cc91..09ced6eb3 100644 --- a/test/syscalls/linux/verity_mmap.cc +++ b/test/syscalls/linux/verity_mmap.cc @@ -57,8 +57,8 @@ class MmapTest : public ::testing::Test { }; TEST_F(MmapTest, MmapRead) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); // Make sure the file can be open and mmapped in the mounted verity fs. auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( @@ -71,8 +71,8 @@ TEST_F(MmapTest, MmapRead) { } TEST_F(MmapTest, ModifiedBeforeMmap) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); // Modify the file and check verification failure upon mmapping. auto const fd = ASSERT_NO_ERRNO_AND_VALUE( @@ -90,8 +90,8 @@ TEST_F(MmapTest, ModifiedBeforeMmap) { } TEST_F(MmapTest, ModifiedAfterMmap) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777)); @@ -126,8 +126,8 @@ INSTANTIATE_TEST_SUITE_P( ::testing::ValuesIn({MAP_SHARED, MAP_PRIVATE}))); TEST_P(MmapParamTest, Mmap) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); // Make sure the file can be open and mmapped in the mounted verity fs. auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( diff --git a/test/syscalls/linux/verity_symlink.cc b/test/syscalls/linux/verity_symlink.cc new file mode 100644 index 000000000..bbf5375cb --- /dev/null +++ b/test/syscalls/linux/verity_symlink.cc @@ -0,0 +1,117 @@ +// 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 <stdint.h> +#include <stdlib.h> +#include <sys/mount.h> +#include <sys/stat.h> + +#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 { + // Verity is implemented in VFS2. + SKIP_IF(IsRunningWithVFS1()); + + 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(), filename_, + {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(), filename_, + {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(), filename_, + {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/verity_util.cc b/test/util/verity_util.cc index f1b4c251b..501d7c2cf 100644 --- a/test/util/verity_util.cc +++ b/test/util/verity_util.cc @@ -55,17 +55,26 @@ PosixError FlipRandomBit(int fd, int size) { } PosixErrorOr<std::string> MountVerity(std::string tmpfs_dir, - std::string filename) { + std::string filename, + std::vector<EnableTarget> targets) { // 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. + // Enable the file, symlink(if provided) 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)); + + 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)); diff --git a/test/util/verity_util.h b/test/util/verity_util.h index 18743ecd6..44863f322 100644 --- a/test/util/verity_util.h +++ b/test/util/verity_util.h @@ -17,6 +17,8 @@ #include <stdint.h> +#include <vector> + #include "test/util/posix_error.h" namespace gvisor { @@ -44,6 +46,13 @@ struct fsverity_digest { 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"; @@ -67,7 +76,8 @@ 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<std::string> MountVerity(std::string tmpfs_dir, - std::string filename); + std::string filename, + std::vector<EnableTarget> targets); } // namespace testing } // namespace gvisor |