summaryrefslogtreecommitdiffhomepage
path: root/test/util
diff options
context:
space:
mode:
Diffstat (limited to 'test/util')
-rw-r--r--test/util/BUILD25
-rw-r--r--test/util/cgroup_util.cc14
-rw-r--r--test/util/cgroup_util.h2
-rw-r--r--test/util/mount_util.cc32
-rw-r--r--test/util/mount_util.h10
-rw-r--r--test/util/mount_util_test.cc47
-rw-r--r--test/util/test_util.h9
-rw-r--r--test/util/verity_util.cc93
-rw-r--r--test/util/verity_util.h75
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_