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)