Make Temporary{Dir,File} available for all unittest targets.
These scoped classes are generally useful for unit tests, so make them
available for all unit tests.
In particular, we're going to use them for a test to validate an
upcoming fix that will prevent Minijail from following symlinks in
bind mount sources and destinations.
Bug: 219093918
Test: New unit test, existing tests pass.
Change-Id: I5667d0030af083aea94803555fcac6635c1638cc
diff --git a/libminijail_unittest.cc b/libminijail_unittest.cc
index c20e32b..35d5ca5 100644
--- a/libminijail_unittest.cc
+++ b/libminijail_unittest.cc
@@ -26,6 +26,7 @@
#include "libminijail-private.h"
#include "libminijail.h"
#include "scoped_minijail.h"
+#include "unittest_util.h"
#include "util.h"
namespace {
@@ -1006,6 +1007,35 @@
minijail_destroy(j);
}
+// Test that bind mounting with a symlink works (but we're about to make this
+// fail).
+TEST(Test, test_bind_mount_symlink) {
+ TemporaryDir dir;
+ ASSERT_TRUE(dir.is_valid());
+
+ // minijail_bind() expects absolute paths, but TemporaryDir::path can return
+ // relative paths on Linux.
+ std::string path = dir.path;
+ if (!is_android()) {
+ std::string cwd(getcwd(NULL, 0));
+ path = cwd + "/" + path;
+ }
+
+ std::string path_src = path + "/src";
+ std::string path_dest = path + "/dest";
+ std::string path_sym = path + "/symlink";
+
+ EXPECT_EQ(mkdir(path_src.c_str(), 0700), 0);
+ EXPECT_EQ(mkdir(path_dest.c_str(), 0700), 0);
+ EXPECT_EQ(symlink(path_src.c_str(), path_sym.c_str()), 0);
+
+ ScopedMinijail j(minijail_new());
+ int bind_res = minijail_bind(j.get(), path_sym.c_str(), path_dest.c_str(),
+ 0 /*writable*/);
+ EXPECT_EQ(bind_res, 0);
+ EXPECT_EQ(unlink(path_sym.c_str()), 0);
+}
+
namespace {
// Tests that require userns access.
diff --git a/system_unittest.cc b/system_unittest.cc
index 92b7ab9..19b9099 100644
--- a/system_unittest.cc
+++ b/system_unittest.cc
@@ -20,6 +20,7 @@
#include <string>
#include "system.h"
+#include "unittest_util.h"
namespace {
@@ -35,91 +36,6 @@
// A random character device that should exist.
constexpr const char kValidCharDev[] = "/dev/null";
-constexpr bool is_android() {
-#if defined(__ANDROID__)
- return true;
-#else
- return false;
-#endif
-}
-
-// Returns a template path that can be used as an argument to mkstemp / mkdtemp.
-constexpr const char* temp_path_pattern() {
- if (is_android())
- return "/data/local/tmp/minijail.tests.XXXXXX";
- else
- return "minijail.tests.XXXXXX";
-}
-
-// Recursively deletes the subtree rooted at |path|.
-bool rmdir_recursive(const std::string& path) {
- auto callback = [](const char* child, const struct stat*, int file_type,
- struct FTW*) -> int {
- if (file_type == FTW_DP) {
- if (rmdir(child) == -1) {
- fprintf(stderr, "rmdir(%s): %s", child, strerror(errno));
- return -1;
- }
- } else if (file_type == FTW_F) {
- if (unlink(child) == -1) {
- fprintf(stderr, "unlink(%s): %s", child, strerror(errno));
- return -1;
- }
- }
- return 0;
- };
-
- return nftw(path.c_str(), callback, 128, FTW_DEPTH) == 0;
-}
-
-// Creates a temporary directory that will be cleaned up upon leaving scope.
-class TemporaryDir {
- public:
- TemporaryDir() : path(temp_path_pattern()) {
- if (mkdtemp(const_cast<char*>(path.c_str())) == nullptr)
- path.clear();
- }
- ~TemporaryDir() {
- if (!is_valid())
- return;
- rmdir_recursive(path.c_str());
- }
-
- bool is_valid() const { return !path.empty(); }
-
- std::string path;
-
- private:
- TemporaryDir(const TemporaryDir&) = delete;
- TemporaryDir& operator=(const TemporaryDir&) = delete;
-};
-
-// Creates a named temporary file that will be cleaned up upon leaving scope.
-class TemporaryFile {
- public:
- TemporaryFile() : path(temp_path_pattern()) {
- int fd = mkstemp(const_cast<char*>(path.c_str()));
- if (fd == -1) {
- path.clear();
- return;
- }
- close(fd);
- }
- ~TemporaryFile() {
- if (!is_valid())
- return;
- unlink(path.c_str());
- }
-
- bool is_valid() const { return !path.empty(); }
-
- std::string path;
-
- private:
- TemporaryFile(const TemporaryFile&) = delete;
- TemporaryFile& operator=(const TemporaryFile&) = delete;
-};
-
} // namespace
TEST(secure_noroot_set_and_locked, zero_mask) {
diff --git a/unittest_util.h b/unittest_util.h
new file mode 100644
index 0000000..54993d7
--- /dev/null
+++ b/unittest_util.h
@@ -0,0 +1,103 @@
+/* unittest_util.h
+ * Copyright 2022 The ChromiumOS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Utility functions for unit tests.
+ */
+
+#include <ftw.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+namespace {
+
+constexpr bool is_android_constexpr() {
+#if defined(__ANDROID__)
+ return true;
+#else
+ return false;
+#endif
+}
+
+// Returns a template path that can be used as an argument to mkstemp / mkdtemp.
+constexpr const char* temp_path_pattern() {
+ if (is_android_constexpr())
+ return "/data/local/tmp/minijail.tests.XXXXXX";
+ else
+ return "minijail.tests.XXXXXX";
+}
+
+// Recursively deletes the subtree rooted at |path|.
+bool rmdir_recursive(const std::string& path) {
+ auto callback = [](const char* child, const struct stat*, int file_type,
+ struct FTW*) -> int {
+ if (file_type == FTW_DP) {
+ if (rmdir(child) == -1) {
+ fprintf(stderr, "rmdir(%s): %s\n", child, strerror(errno));
+ return -1;
+ }
+ } else if (file_type == FTW_F) {
+ if (unlink(child) == -1) {
+ fprintf(stderr, "unlink(%s): %s\n", child, strerror(errno));
+ return -1;
+ }
+ }
+ return 0;
+ };
+
+ return nftw(path.c_str(), callback, 128, FTW_DEPTH) == 0;
+}
+
+} // namespace
+
+// Creates a temporary directory that will be cleaned up upon leaving scope.
+class TemporaryDir {
+ public:
+ TemporaryDir() : path(temp_path_pattern()) {
+ if (mkdtemp(const_cast<char*>(path.c_str())) == nullptr)
+ path.clear();
+ }
+ ~TemporaryDir() {
+ if (!is_valid())
+ return;
+ rmdir_recursive(path.c_str());
+ }
+
+ bool is_valid() const { return !path.empty(); }
+
+ std::string path;
+
+ private:
+ TemporaryDir(const TemporaryDir&) = delete;
+ TemporaryDir& operator=(const TemporaryDir&) = delete;
+};
+
+// Creates a named temporary file that will be cleaned up upon leaving scope.
+class TemporaryFile {
+ public:
+ TemporaryFile() : path(temp_path_pattern()) {
+ int fd = mkstemp(const_cast<char*>(path.c_str()));
+ if (fd == -1) {
+ path.clear();
+ return;
+ }
+ close(fd);
+ }
+ ~TemporaryFile() {
+ if (!is_valid())
+ return;
+ unlink(path.c_str());
+ }
+
+ bool is_valid() const { return !path.empty(); }
+
+ std::string path;
+
+ private:
+ TemporaryFile(const TemporaryFile&) = delete;
+ TemporaryFile& operator=(const TemporaryFile&) = delete;
+};