diff options
Diffstat (limited to 'test/util')
-rw-r--r-- | test/util/BUILD | 25 | ||||
-rw-r--r-- | test/util/cgroup_util.cc | 14 | ||||
-rw-r--r-- | test/util/cgroup_util.h | 2 | ||||
-rw-r--r-- | test/util/mount_util.cc | 32 | ||||
-rw-r--r-- | test/util/mount_util.h | 10 | ||||
-rw-r--r-- | test/util/mount_util_test.cc | 47 | ||||
-rw-r--r-- | test/util/test_util.h | 9 | ||||
-rw-r--r-- | test/util/verity_util.cc | 93 | ||||
-rw-r--r-- | test/util/verity_util.h | 75 |
9 files changed, 294 insertions, 13 deletions
diff --git a/test/util/BUILD b/test/util/BUILD index 6feda0e26..cc83221ea 100644 --- a/test/util/BUILD +++ b/test/util/BUILD @@ -149,6 +149,18 @@ cc_library( ], ) +cc_test( + name = "mount_util_test", + size = "small", + srcs = ["mount_util_test.cc"], + deps = [ + ":mount_util", + ":test_main", + ":test_util", + gtest, + ], +) + cc_library( name = "save_util", testonly = 1, @@ -389,3 +401,16 @@ cc_library( "@com_google_absl//absl/strings", ], ) + +cc_library( + name = "verity_util", + testonly = 1, + srcs = ["verity_util.cc"], + hdrs = ["verity_util.h"], + deps = [ + ":fs_util", + ":mount_util", + ":posix_error", + ":temp_path", + ], +) diff --git a/test/util/cgroup_util.cc b/test/util/cgroup_util.cc index 04d4f8de0..977993f41 100644 --- a/test/util/cgroup_util.cc +++ b/test/util/cgroup_util.cc @@ -142,6 +142,20 @@ PosixError Mounter::Unmount(const Cgroup& c) { return NoError(); } +void Mounter::release(const Cgroup& c) { + auto mp = mountpoints_.find(c.id()); + if (mp != mountpoints_.end()) { + mp->second.release(); + mountpoints_.erase(mp); + } + + auto m = mounts_.find(c.id()); + if (m != mounts_.end()) { + m->second.Release(); + mounts_.erase(m); + } +} + constexpr char kProcCgroupsHeader[] = "#subsys_name\thierarchy\tnum_cgroups\tenabled"; diff --git a/test/util/cgroup_util.h b/test/util/cgroup_util.h index b797a8b24..e3f696a89 100644 --- a/test/util/cgroup_util.h +++ b/test/util/cgroup_util.h @@ -83,6 +83,8 @@ class Mounter { PosixError Unmount(const Cgroup& c); + void release(const Cgroup& c); + private: // The destruction order of these members avoids errors during cleanup. We // first unmount (by executing the mounts_ cleanups), then delete the diff --git a/test/util/mount_util.cc b/test/util/mount_util.cc index a79ce6420..48640d6a1 100644 --- a/test/util/mount_util.cc +++ b/test/util/mount_util.cc @@ -26,9 +26,14 @@ namespace testing { PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntries() { std::string content; RETURN_IF_ERRNO(GetContents("/proc/self/mounts", &content)); + return ProcSelfMountsEntriesFrom(content); +} +PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntriesFrom( + const std::string& content) { std::vector<ProcMountsEntry> entries; - std::vector<std::string> lines = absl::StrSplit(content, '\n'); + std::vector<std::string> lines = + absl::StrSplit(content, absl::ByChar('\n'), absl::AllowEmpty()); std::cerr << "<contents of /proc/self/mounts>" << std::endl; for (const std::string& line : lines) { std::cerr << line << std::endl; @@ -47,11 +52,11 @@ PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntries() { ProcMountsEntry entry; std::vector<std::string> fields = - absl::StrSplit(line, absl::ByChar(' '), absl::SkipEmpty()); + absl::StrSplit(line, absl::ByChar(' '), absl::AllowEmpty()); if (fields.size() != 6) { - return PosixError(EINVAL, - absl::StrFormat("Not enough tokens, got %d, line: %s", - fields.size(), line)); + return PosixError( + EINVAL, absl::StrFormat("Not enough tokens, got %d, content: <<%s>>", + fields.size(), content)); } entry.spec = fields[0]; @@ -71,9 +76,14 @@ PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntries() { PosixErrorOr<std::vector<ProcMountInfoEntry>> ProcSelfMountInfoEntries() { std::string content; RETURN_IF_ERRNO(GetContents("/proc/self/mountinfo", &content)); + return ProcSelfMountInfoEntriesFrom(content); +} +PosixErrorOr<std::vector<ProcMountInfoEntry>> ProcSelfMountInfoEntriesFrom( + const std::string& content) { std::vector<ProcMountInfoEntry> entries; - std::vector<std::string> lines = absl::StrSplit(content, '\n'); + std::vector<std::string> lines = + absl::StrSplit(content, absl::ByChar('\n'), absl::AllowEmpty()); std::cerr << "<contents of /proc/self/mountinfo>" << std::endl; for (const std::string& line : lines) { std::cerr << line << std::endl; @@ -92,12 +102,12 @@ PosixErrorOr<std::vector<ProcMountInfoEntry>> ProcSelfMountInfoEntries() { ProcMountInfoEntry entry; std::vector<std::string> fields = - absl::StrSplit(line, absl::ByChar(' '), absl::SkipEmpty()); + absl::StrSplit(line, absl::ByChar(' '), absl::AllowEmpty()); if (fields.size() < 10 || fields.size() > 11) { return PosixError( - EINVAL, - absl::StrFormat("Unexpected number of tokens, got %d, line: %s", - fields.size(), line)); + EINVAL, absl::StrFormat( + "Unexpected number of tokens, got %d, content: <<%s>>", + fields.size(), content)); } ASSIGN_OR_RETURN_ERRNO(entry.id, Atoi<uint64_t>(fields[0])); @@ -142,7 +152,7 @@ absl::flat_hash_map<std::string, std::string> ParseMountOptions( std::string mopts) { absl::flat_hash_map<std::string, std::string> entries; const std::vector<std::string> tokens = - absl::StrSplit(mopts, absl::ByChar(','), absl::SkipEmpty()); + absl::StrSplit(mopts, absl::ByChar(','), absl::AllowEmpty()); for (const auto& token : tokens) { std::vector<std::string> kv = absl::StrSplit(token, absl::MaxSplits('=', 1)); diff --git a/test/util/mount_util.h b/test/util/mount_util.h index b75a490fb..3f8a1c0f1 100644 --- a/test/util/mount_util.h +++ b/test/util/mount_util.h @@ -58,6 +58,11 @@ struct ProcMountsEntry { // ProcSelfMountsEntries returns a parsed representation of /proc/self/mounts. PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntries(); +// ProcSelfMountsEntries returns a parsed representation of mounts from the +// provided content. +PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntriesFrom( + const std::string& content); + struct ProcMountInfoEntry { uint64_t id; uint64_t parent_id; @@ -76,6 +81,11 @@ struct ProcMountInfoEntry { // /proc/self/mountinfo. PosixErrorOr<std::vector<ProcMountInfoEntry>> ProcSelfMountInfoEntries(); +// ProcSelfMountInfoEntriesFrom returns a parsed representation of +// mountinfo from the provided content. +PosixErrorOr<std::vector<ProcMountInfoEntry>> ProcSelfMountInfoEntriesFrom( + const std::string&); + // Interprets the input string mopts as a comma separated list of mount // options. A mount option can either be just a value, or a key=value pair. For // example, the string "rw,relatime,fd=7" will be parsed into a map like { "rw": diff --git a/test/util/mount_util_test.cc b/test/util/mount_util_test.cc new file mode 100644 index 000000000..2bcb6cc43 --- /dev/null +++ b/test/util/mount_util_test.cc @@ -0,0 +1,47 @@ +// Copyright 2018 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 "test/util/mount_util.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +TEST(ParseMounts, Mounts) { + auto entries = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntriesFrom( + R"proc(sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0 +proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 + /mnt tmpfs rw,noexec 0 0 +)proc")); + EXPECT_EQ(entries.size(), 3); +} + +TEST(ParseMounts, MountInfo) { + auto entries = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntriesFrom( + R"proc(22 28 0:20 / /sys rw,relatime shared:7 - sysfs sysfs rw +23 28 0:21 / /proc rw,relatime shared:14 - proc proc rw +2007 8844 0:278 / /mnt rw,noexec - tmpfs rw,mode=123,uid=268601820,gid=5000 +)proc")); + EXPECT_EQ(entries.size(), 3); +} + +} // namespace + +} // namespace testing +} // namespace gvisor diff --git a/test/util/test_util.h b/test/util/test_util.h index 876ff58db..bcbb388ed 100644 --- a/test/util/test_util.h +++ b/test/util/test_util.h @@ -272,10 +272,15 @@ PosixErrorOr<std::vector<OpenFd>> GetOpenFDs(); // Returns the number of hard links to a path. PosixErrorOr<uint64_t> Links(const std::string& path); +inline uint64_t ns_elapsed(const struct timespec& begin, + const struct timespec& end) { + return (end.tv_sec - begin.tv_sec) * 1000000000 + + (end.tv_nsec - begin.tv_nsec); +} + inline uint64_t ms_elapsed(const struct timespec& begin, const struct timespec& end) { - return (end.tv_sec - begin.tv_sec) * 1000 + - (end.tv_nsec - begin.tv_nsec) / 1000000; + return ns_elapsed(begin, end) / 1000000; } namespace internal { diff --git a/test/util/verity_util.cc b/test/util/verity_util.cc new file mode 100644 index 000000000..f1b4c251b --- /dev/null +++ b/test/util/verity_util.cc @@ -0,0 +1,93 @@ +// 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 "test/util/verity_util.h" + +#include "test/util/fs_util.h" +#include "test/util/mount_util.h" +#include "test/util/temp_path.h" + +namespace gvisor { +namespace testing { + +std::string BytesToHexString(uint8_t bytes[], int size) { + std::stringstream ss; + ss << std::hex; + for (int i = 0; i < size; ++i) { + ss << std::setw(2) << std::setfill('0') << static_cast<int>(bytes[i]); + } + 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))); +} + +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(); +} + +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(); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/verity_util.h b/test/util/verity_util.h new file mode 100644 index 000000000..18743ecd6 --- /dev/null +++ b/test/util/verity_util.h @@ -0,0 +1,75 @@ +// 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. + +#ifndef GVISOR_TEST_UTIL_VERITY_UTIL_H_ +#define GVISOR_TEST_UTIL_VERITY_UTIL_H_ + +#include <stdint.h> + +#include "test/util/posix_error.h" + +namespace gvisor { +namespace testing { + +#ifndef FS_IOC_ENABLE_VERITY +#define FS_IOC_ENABLE_VERITY 1082156677 +#endif + +#ifndef FS_IOC_MEASURE_VERITY +#define FS_IOC_MEASURE_VERITY 3221513862 +#endif + +#ifndef FS_VERITY_FL +#define FS_VERITY_FL 1048576 +#endif + +#ifndef FS_IOC_GETFLAGS +#define FS_IOC_GETFLAGS 2148034049 +#endif + +struct fsverity_digest { + unsigned short digest_algorithm; + unsigned short digest_size; /* input/output */ + unsigned char digest[]; +}; + +constexpr int kMaxDigestSize = 64; +constexpr int kDefaultDigestSize = 32; +constexpr char kContents[] = "foobarbaz"; +constexpr char kMerklePrefix[] = ".merkle.verity."; +constexpr char kMerkleRootPrefix[] = ".merkleroot.verity."; + +// Get the Merkle tree file path for |path|. +std::string MerklePath(absl::string_view path); + +// Get the root Merkle tree file path for |path|. +std::string MerkleRootPath(absl::string_view path); + +// Provide a function to convert bytes to hex string, since +// absl::BytesToHexString does not seem to be compatible with golang +// hex.DecodeString used in verity due to zero-padding. +std::string BytesToHexString(uint8_t bytes[], int size); + +// Flip a random bit in the file represented by fd. +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); + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_VERITY_UTIL_H_ |