minijail: Add minijail_add_hook()
This allows callers to add hooks to be invoked at various events during
minijail setup. This is useful to e.g. setup SELinux contexts,
networking in the new namespace, and install other LSM-related stuff.
Bug: 63904978
Test: make tests
Change-Id: I3e773715ec1842db8071f5e993ee4bdcbe2d0082
diff --git a/libminijail.c b/libminijail.c
index 4d61b0d..ac76faf 100644
--- a/libminijail.c
+++ b/libminijail.c
@@ -96,6 +96,13 @@
struct mountpoint *next;
};
+struct hook {
+ minijail_hook_t hook;
+ void *payload;
+ minijail_hook_event_t event;
+ struct hook *next;
+};
+
struct minijail {
/*
* WARNING: if you add a flag here you need to make sure it's
@@ -167,6 +174,8 @@
struct minijail_rlimit rlimits[MAX_RLIMITS];
size_t rlimit_count;
uint64_t securebits_skip_mask;
+ struct hook *hooks_head;
+ struct hook *hooks_tail;
};
/*
@@ -753,6 +762,32 @@
return minijail_mount(j, src, dest, "", flags);
}
+int API minijail_add_hook(struct minijail *j, minijail_hook_t hook,
+ void *payload, minijail_hook_event_t event)
+{
+ struct hook *c;
+
+ if (hook == NULL)
+ return -EINVAL;
+ if (event >= MINIJAIL_HOOK_EVENT_MAX)
+ return -EINVAL;
+ c = calloc(1, sizeof(*c));
+ if (!c)
+ return -ENOMEM;
+
+ c->hook = hook;
+ c->payload = payload;
+ c->event = event;
+
+ if (j->hooks_tail)
+ j->hooks_tail->next = c;
+ else
+ j->hooks_head = c;
+ j->hooks_tail = c;
+
+ return 0;
+}
+
static void clear_seccomp_options(struct minijail *j)
{
j->flags.seccomp_filter = 0;
@@ -978,6 +1013,8 @@
j->mounts_head = NULL;
j->mounts_tail = NULL;
j->filter_prog = NULL;
+ j->hooks_head = NULL;
+ j->hooks_tail = NULL;
if (j->user) { /* stale pointer */
char *user = consumestr(&serialized, &length);
@@ -1632,6 +1669,42 @@
}
}
+static const char *lookup_hook_name(minijail_hook_event_t event)
+{
+ switch (event) {
+ case MINIJAIL_HOOK_EVENT_PRE_DROP_CAPS:
+ return "pre-drop-caps";
+ case MINIJAIL_HOOK_EVENT_PRE_EXECVE:
+ return "pre-execve";
+ case MINIJAIL_HOOK_EVENT_MAX:
+ /*
+ * Adding this in favor of a default case to force the
+ * compiler to error out if a new enum value is added.
+ */
+ break;
+ }
+ return "unknown";
+}
+
+static void run_hooks_or_die(const struct minijail *j,
+ minijail_hook_event_t event)
+{
+ int rc;
+ int hook_index = 0;
+ for (struct hook *c = j->hooks_head; c; c = c->next) {
+ if (c->event != event)
+ continue;
+ rc = c->hook(c->payload);
+ if (rc != 0) {
+ errno = -rc;
+ pdie("%s hook (index %d) failed",
+ lookup_hook_name(event), hook_index);
+ }
+ /* Only increase the index within the same hook event type. */
+ ++hook_index;
+ }
+}
+
void API minijail_enter(const struct minijail *j)
{
/*
@@ -1715,6 +1788,8 @@
if (j->flags.remount_proc_ro && remount_proc_readonly(j))
pdie("remount");
+ run_hooks_or_die(j, MINIJAIL_HOOK_EVENT_PRE_DROP_CAPS);
+
/*
* If we're only dropping capabilities from the bounding set, but not
* from the thread's (permitted|inheritable|effective) sets, do it now.
@@ -2025,6 +2100,10 @@
}
}
+ if (use_preload && j->hooks_head != NULL) {
+ die("Minijail hooks are not supported with LD_PRELOAD");
+ }
+
/*
* Make the process group ID of this process equal to its PID.
* In the non-interactive case (e.g. when the parent process is started
@@ -2350,6 +2429,8 @@
}
}
+ run_hooks_or_die(j, MINIJAIL_HOOK_EVENT_PRE_EXECVE);
+
/*
* If we aren't pid-namespaced, or the jailed program asked to be init:
* calling process
@@ -2430,6 +2511,12 @@
free(m);
}
j->mounts_tail = NULL;
+ while (j->hooks_head) {
+ struct hook *c = j->hooks_head;
+ j->hooks_head = c->next;
+ free(c);
+ }
+ j->hooks_tail = NULL;
if (j->user)
free(j->user);
if (j->suppl_gid_list)
diff --git a/libminijail.h b/libminijail.h
index ff06348..372c1a4 100644
--- a/libminijail.h
+++ b/libminijail.h
@@ -30,6 +30,31 @@
struct minijail;
+/*
+ * A hook that can be used to execute code at various events during minijail
+ * setup in the forked process. These can only be used if the jailed process is
+ * not going to be invoked with LD_PRELOAD.
+ *
+ * If the return value is non-zero, it will be interpreted as -errno and the
+ * process will abort.
+ */
+typedef int (*minijail_hook_t)(void *context);
+
+/*
+ * The events during minijail setup in which hooks can run. All the events are
+ * run in the new process.
+ */
+typedef enum {
+ /* The hook will run just before dropping capabilities. */
+ MINIJAIL_HOOK_EVENT_PRE_DROP_CAPS,
+
+ /* The hook will run just before calling execve(2). */
+ MINIJAIL_HOOK_EVENT_PRE_EXECVE,
+
+ /* Sentinel for error checking. Must be last. */
+ MINIJAIL_HOOK_EVENT_MAX,
+} minijail_hook_event_t;
+
/* Allocates a new minijail with no restrictions. */
struct minijail *minijail_new(void);
@@ -200,6 +225,19 @@
int writeable);
/*
+ * minijail_add_hook: adds @hook to the list of hooks that will be
+ * invoked when @event is reached during minijail setup. The caller is
+ * responsible for the lifetime of @payload.
+ * @j minijail to add the hook to
+ * @hook the function that will be invoked
+ * @payload an opaque pointer
+ * @event the event that will trigger the hook
+ */
+int minijail_add_hook(struct minijail *j,
+ minijail_hook_t hook, void *payload,
+ minijail_hook_event_t event);
+
+/*
* 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 d5ff6a8..caaa138 100644
--- a/libminijail_unittest.cc
+++ b/libminijail_unittest.cc
@@ -273,6 +273,41 @@
close(dev_null);
}
+static int early_exit(void* payload) {
+ exit(static_cast<int>(reinterpret_cast<intptr_t>(payload)));
+}
+
+TEST(Test, test_minijail_callback) {
+ pid_t pid;
+ int mj_run_ret;
+ int status;
+#if defined(__ANDROID__)
+ char filename[] = "/system/bin/cat";
+#else
+ char filename[] = "/bin/cat";
+#endif
+ char *argv[2];
+ int exit_code = 42;
+
+ struct minijail *j = minijail_new();
+
+ status =
+ minijail_add_hook(j, &early_exit, reinterpret_cast<void *>(exit_code),
+ MINIJAIL_HOOK_EVENT_PRE_DROP_CAPS);
+ EXPECT_EQ(status, 0);
+
+ argv[0] = filename;
+ argv[1] = NULL;
+ mj_run_ret = minijail_run_pid_pipes_no_preload(j, argv[0], argv, &pid, NULL,
+ NULL, NULL);
+ EXPECT_EQ(mj_run_ret, 0);
+
+ status = minijail_wait(j);
+ EXPECT_EQ(status, exit_code);
+
+ minijail_destroy(j);
+}
+
TEST(Test, parse_size) {
size_t size;