minijail: Add a way to allow arbitrary fd redirects
This change allows for the parent to redirect arbtitrary file
descriptors into the child, in a way that works even when the
minijail_close_open_fds() is used.
This can be used to pass in additional pipes to the jailed process.
Bug: 63904978
Test: make tests
Change-Id: Ia47eec575c92a08eb5380cc15dc4561572a209b3
diff --git a/libminijail.c b/libminijail.c
index ac76faf..89fb609 100644
--- a/libminijail.c
+++ b/libminijail.c
@@ -77,6 +77,8 @@
#define MAX_RLIMITS 32 /* Currently there are 15 supported by Linux. */
+#define MAX_PRESERVED_FDS 10
+
/* Keyctl commands. */
#define KEYCTL_JOIN_SESSION_KEYRING 1
@@ -103,6 +105,11 @@
struct hook *next;
};
+struct preserved_fd {
+ int parent_fd;
+ int child_fd;
+};
+
struct minijail {
/*
* WARNING: if you add a flag here you need to make sure it's
@@ -176,6 +183,8 @@
uint64_t securebits_skip_mask;
struct hook *hooks_head;
struct hook *hooks_tail;
+ struct preserved_fd preserved_fds[MAX_PRESERVED_FDS];
+ size_t preserved_fd_count;
};
/*
@@ -788,6 +797,18 @@
return 0;
}
+int API minijail_preserve_fd(struct minijail *j, int parent_fd, int child_fd)
+{
+ if (parent_fd < 0 || child_fd < 0)
+ return -EINVAL;
+ if (j->preserved_fd_count >= MAX_PRESERVED_FDS)
+ return -ENOMEM;
+ j->preserved_fds[j->preserved_fd_count].parent_fd = parent_fd;
+ j->preserved_fds[j->preserved_fd_count].child_fd = child_fd;
+ j->preserved_fd_count++;
+ return 0;
+}
+
static void clear_seccomp_options(struct minijail *j)
{
j->flags.seccomp_filter = 0;
@@ -2010,6 +2031,32 @@
return 0;
}
+static int redirect_fds(struct minijail *j)
+{
+ size_t i, i2;
+ int closeable;
+ for (i = 0; i < j->preserved_fd_count; i++) {
+ if (dup2(j->preserved_fds[i].parent_fd,
+ j->preserved_fds[i].child_fd) == -1) {
+ return -1;
+ }
+ }
+ /*
+ * After all fds have been duped, we are now free to close all parent
+ * fds that are *not* child fds.
+ */
+ for (i = 0; i < j->preserved_fd_count; i++) {
+ closeable = true;
+ for (i2 = 0; i2 < j->preserved_fd_count; i2++) {
+ closeable &= j->preserved_fds[i].parent_fd !=
+ j->preserved_fds[i2].child_fd;
+ }
+ if (closeable)
+ close(j->preserved_fds[i].parent_fd);
+ }
+ return 0;
+}
+
int minijail_run_internal(struct minijail *j, const char *filename,
char *const argv[], pid_t *pchild_pid,
int *pstdin_fd, int *pstdout_fd, int *pstderr_fd,
@@ -2311,9 +2358,10 @@
}
if (j->flags.close_open_fds) {
- const size_t kMaxInheritableFdsSize = 10;
+ const size_t kMaxInheritableFdsSize = 10 + MAX_PRESERVED_FDS;
int inheritable_fds[kMaxInheritableFdsSize];
size_t size = 0;
+ size_t i;
if (use_preload) {
inheritable_fds[size++] = pipe_fds[0];
inheritable_fds[size++] = pipe_fds[1];
@@ -2334,11 +2382,21 @@
inheritable_fds[size++] = stderr_fds[0];
inheritable_fds[size++] = stderr_fds[1];
}
+ for (i = 0; i < j->preserved_fd_count; i++) {
+ /*
+ * Preserve all parent_fds. They will be dup2(2)-ed in
+ * the child later.
+ */
+ inheritable_fds[size++] = j->preserved_fds[i].parent_fd;
+ }
if (close_open_fds(inheritable_fds, size) < 0)
die("failed to close open file descriptors");
}
+ if (redirect_fds(j))
+ die("failed to set up fd redirections");
+
if (sync_child)
wait_for_parent_setup(child_sync_pipe_fds);
diff --git a/libminijail.h b/libminijail.h
index 372c1a4..a783067 100644
--- a/libminijail.h
+++ b/libminijail.h
@@ -238,6 +238,17 @@
minijail_hook_event_t event);
/*
+ * minijail_preserve_fd: preserves @parent_fd and makes it available as
+ * @child_fd in the child process. @parent_fd will be closed if no other
+ * redirect has claimed it as a @child_fd. This works even if
+ * minijail_close_open_fds() is invoked.
+ * @j minijail to add the fd to
+ * @parent_fd the fd in the parent process
+ * @child_fd the fd that will be available in the child process
+ */
+int minijail_preserve_fd(struct minijail *j, int parent_fd, int child_fd);
+
+/*
* Lock this process into the given minijail. Note that this procedure cannot
* fail, since there is no way to undo privilege-dropping; therefore, if any
* part of the privilege-drop fails, minijail_enter() will abort the entire
diff --git a/libminijail_unittest.cc b/libminijail_unittest.cc
index caaa138..77775b5 100644
--- a/libminijail_unittest.cc
+++ b/libminijail_unittest.cc
@@ -308,6 +308,56 @@
minijail_destroy(j);
}
+TEST(Test, test_minijail_preserve_fd) {
+ int mj_run_ret;
+ int status;
+#if defined(__ANDROID__)
+ char filename[] = "/system/bin/cat";
+#else
+ char filename[] = "/bin/cat";
+#endif
+ char *argv[2];
+ char teststr[] = "test\n";
+ size_t teststr_len = strlen(teststr);
+ int read_pipe[2];
+ int write_pipe[2];
+ char buf[1024];
+
+ struct minijail *j = minijail_new();
+
+ status = pipe(read_pipe);
+ ASSERT_EQ(status, 0);
+ status = pipe(write_pipe);
+ ASSERT_EQ(status, 0);
+
+ status = minijail_preserve_fd(j, write_pipe[0], STDIN_FILENO);
+ ASSERT_EQ(status, 0);
+ status = minijail_preserve_fd(j, read_pipe[1], STDOUT_FILENO);
+ ASSERT_EQ(status, 0);
+ minijail_close_open_fds(j);
+
+ argv[0] = filename;
+ argv[1] = NULL;
+ mj_run_ret = minijail_run_no_preload(j, argv[0], argv);
+ EXPECT_EQ(mj_run_ret, 0);
+
+ close(write_pipe[0]);
+ status = write(write_pipe[1], teststr, teststr_len);
+ EXPECT_EQ(status, (int)teststr_len);
+ close(write_pipe[1]);
+
+ close(read_pipe[1]);
+ status = read(read_pipe[0], buf, 8);
+ EXPECT_EQ(status, (int)teststr_len);
+ buf[teststr_len] = 0;
+ EXPECT_EQ(strcmp(buf, teststr), 0);
+
+ status = minijail_wait(j);
+ EXPECT_EQ(status, 0);
+
+ minijail_destroy(j);
+}
+
TEST(Test, parse_size) {
size_t size;