Add a function to load seccomp filters from a file descriptor.

This allows for easier unit testing, since we don't need to rely on
data files. It will also allow using Minijail for seccomp testing in
CTS.

Refactor seccomp filter parsing in libminijail.c to avoid
duplicating code.

Bug: 25949727

Change-Id: Iff21591cecc1783d141df912dafbe341ac83ed50
diff --git a/libminijail.c b/libminijail.c
index 60a7c4f..8f63b85 100644
--- a/libminijail.c
+++ b/libminijail.c
@@ -643,33 +643,78 @@
 	return minijail_mount(j, src, dest, "", flags);
 }
 
-void API minijail_parse_seccomp_filters(struct minijail *j, const char *path)
+static int seccomp_should_parse_filters(struct minijail *j)
 {
 	if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, NULL)) {
+		/*
+		 * |errno| will be set to EINVAL when seccomp has not been
+		 * compiled into the kernel. On certain platforms and kernel
+		 * versions this is not a fatal failure. In that case, and only
+		 * in that case, disable seccomp and skip loading the filters.
+		 */
 		if ((errno == EINVAL) && seccomp_can_softfail()) {
-			warn("not loading seccomp filter,"
+			warn("not loading seccomp filters,"
 			     " seccomp not supported");
 			j->flags.seccomp_filter = 0;
 			j->flags.log_seccomp_filter = 0;
 			j->filter_len = 0;
 			j->filter_prog = NULL;
 			j->flags.no_new_privs = 0;
+			return 0;
 		}
+		/*
+		 * If |errno| != EINVAL or seccomp_can_softfail() is false,
+		 * we can proceed. Worst case scenario minijail_enter() will
+		 * abort() if seccomp fails.
+		 */
 	}
+	return 1;
+}
+
+static int parse_seccomp_filters(struct minijail *j, FILE *policy_file)
+{
+	struct sock_fprog *fprog = malloc(sizeof(struct sock_fprog));
+	if (compile_filter(policy_file, fprog, j->flags.log_seccomp_filter)) {
+		free(fprog);
+		return -1;
+	}
+
+	j->filter_len = fprog->len;
+	j->filter_prog = fprog;
+	return 0;
+}
+
+void API minijail_parse_seccomp_filters(struct minijail *j, const char *path)
+{
+	if (!seccomp_should_parse_filters(j))
+		return;
+
 	FILE *file = fopen(path, "r");
 	if (!file) {
 		pdie("failed to open seccomp filter file '%s'", path);
 	}
 
-	struct sock_fprog *fprog = malloc(sizeof(struct sock_fprog));
-	if (compile_filter(file, fprog, j->flags.log_seccomp_filter)) {
+	if (parse_seccomp_filters(j, file) != 0) {
 		die("failed to compile seccomp filter BPF program in '%s'",
 		    path);
 	}
+	fclose(file);
+}
 
-	j->filter_len = fprog->len;
-	j->filter_prog = fprog;
+void API minijail_parse_seccomp_filters_from_fd(struct minijail *j, int fd)
+{
+	if (!seccomp_should_parse_filters(j))
+		return;
 
+	FILE *file = fdopen(fd, "r");
+	if (!file) {
+		pdie("failed to associate stream with fd %d", fd);
+	}
+
+	if (parse_seccomp_filters(j, file) != 0) {
+		die("failed to compile seccomp filter BPF program from fd %d",
+		    fd);
+	}
 	fclose(file);
 }
 
diff --git a/libminijail.h b/libminijail.h
index a25c01a..62005c8 100644
--- a/libminijail.h
+++ b/libminijail.h
@@ -51,6 +51,7 @@
 void minijail_no_new_privs(struct minijail *j);
 void minijail_use_seccomp_filter(struct minijail *j);
 void minijail_parse_seccomp_filters(struct minijail *j, const char *path);
+void minijail_parse_seccomp_filters_from_fd(struct minijail *j, int fd);
 void minijail_log_seccomp_filter_failures(struct minijail *j);
 /* 'minijail_use_caps' and 'minijail_capbset_drop' are mutually exclusive. */
 void minijail_use_caps(struct minijail *j, uint64_t capmask);
diff --git a/syscall_filter.h b/syscall_filter.h
index 37c5b6e..73f3e8a 100644
--- a/syscall_filter.h
+++ b/syscall_filter.h
@@ -29,8 +29,7 @@
 				     unsigned int label_id,
 				     struct bpf_labels *labels,
 				     int log_failures);
-int compile_filter(FILE *policy_file, struct sock_fprog *prog,
-		   int log_failures);
+int compile_filter(FILE *policy_file, struct sock_fprog *prog, int log_failures);
 
 int flatten_block_list(struct filter_block *head, struct sock_filter *filter,
 		       size_t index, size_t cap);
diff --git a/syscall_filter_unittest.c b/syscall_filter_unittest.c
index 48b891a..c318d3e 100644
--- a/syscall_filter_unittest.c
+++ b/syscall_filter_unittest.c
@@ -533,7 +533,7 @@
 	unsigned int id = 0;
 
 	struct filter_block *block =
-			compile_section(nr, fragment, id, &self->labels, NO_LOGGING);
+		compile_section(nr, fragment, id, &self->labels, NO_LOGGING);
 	ASSERT_EQ(block, NULL);
 
 	fragment = "arg0 == 0 && arg1 == 1; return errno";
@@ -675,18 +675,59 @@
 
 FIXTURE(filter) {};
 
-/*
- * When compiling for Android, disable tests that require data files.
- * TODO(b/259497279): Re-enable this.
- */
-#if !defined(__ANDROID__)
 FIXTURE_SETUP(filter) {}
 FIXTURE_TEARDOWN(filter) {}
 
+FILE *write_policy_to_pipe(const char *policy, size_t len) {
+	int pipefd[2];
+	if (pipe(pipefd) == -1) {
+		pwarn("pipe(pipefd) failed");
+		return NULL;
+	}
+
+	size_t i = 0;
+	unsigned int attempts = 0;
+	ssize_t ret;
+	while (i < len) {
+		ret = write(pipefd[1], &policy[i], len - i);
+		if (ret == -1) {
+			close(pipefd[0]);
+			close(pipefd[1]);
+			return NULL;
+		}
+
+		/* If we write 0 bytes three times in a row, fail. */
+		if (ret == 0) {
+			if (++attempts >= 3) {
+				close(pipefd[0]);
+				close(pipefd[1]);
+				warn("write() returned 0 three times in a row");
+				return NULL;
+			}
+			continue;
+		}
+
+		attempts = 0;
+		i += (size_t)ret;
+	}
+
+	close(pipefd[1]);
+	return fdopen(pipefd[0], "r");
+}
+
 TEST_F(filter, seccomp_mode1) {
 	struct sock_fprog actual;
-	FILE *policy = fopen("test/seccomp.policy", "r");
-	int res = compile_filter(policy, &actual, NO_LOGGING);
+	const char *policy =
+		"read: 1\n"
+		"write: 1\n"
+		"rt_sigreturn: 1\n"
+		"exit: 1\n";
+
+	FILE *policy_file = write_policy_to_pipe(policy, strlen(policy));
+	ASSERT_NE(policy_file, NULL);
+
+	int res = compile_filter(policy_file, &actual, NO_LOGGING);
+	fclose(policy_file);
 
 	/*
 	 * Checks return value, filter length, and that the filter
@@ -710,13 +751,21 @@
 			SECCOMP_RET_KILL);
 
 	free(actual.filter);
-	fclose(policy);
 }
 
 TEST_F(filter, seccomp_read_write) {
 	struct sock_fprog actual;
-	FILE *policy = fopen("test/stdin_stdout.policy", "r");
-	int res = compile_filter(policy, &actual, NO_LOGGING);
+	const char *policy =
+		"read: arg0 == 0\n"
+		"write: arg0 == 1 || arg0 == 2\n"
+		"rt_sigreturn: 1\n"
+		"exit: 1\n";
+
+	FILE *policy_file = write_policy_to_pipe(policy, strlen(policy));
+	ASSERT_NE(policy_file, NULL);
+
+	int res = compile_filter(policy_file, &actual, NO_LOGGING);
+	fclose(policy_file);
 
 	/*
 	 * Checks return value, filter length, and that the filter
@@ -743,36 +792,51 @@
 			SECCOMP_RET_KILL);
 
 	free(actual.filter);
-	fclose(policy);
 }
 
-TEST_F(filter, invalid) {
+TEST_F(filter, invalid_name) {
 	struct sock_fprog actual;
+	const char *policy = "notasyscall: 1\n";
 
-	FILE *policy = fopen("test/invalid_syscall_name.policy", "r");
-	int res = compile_filter(policy, &actual, NO_LOGGING);
-	ASSERT_NE(res, 0);
-	fclose(policy);
+	FILE *policy_file = write_policy_to_pipe(policy, strlen(policy));
+	ASSERT_NE(policy_file, NULL);
 
-	policy = fopen("test/invalid_arg_filter.policy", "r");
-	res = compile_filter(policy, &actual, NO_LOGGING);
+	int res = compile_filter(policy_file, &actual, NO_LOGGING);
+	fclose(policy_file);
 	ASSERT_NE(res, 0);
-	fclose(policy);
+}
+
+TEST_F(filter, invalid_arg) {
+	struct sock_fprog actual;
+	const char *policy = "open: argnn ==\n";
+
+	FILE *policy_file = write_policy_to_pipe(policy, strlen(policy));
+	ASSERT_NE(policy_file, NULL);
+
+	int res = compile_filter(policy_file, &actual, NO_LOGGING);
+	fclose(policy_file);
+	ASSERT_NE(res, 0);
 }
 
 TEST_F(filter, nonexistent) {
 	struct sock_fprog actual;
-
-	FILE *policy = fopen("test/nonexistent-file.policy", "r");
-	int res = compile_filter(policy, &actual, NO_LOGGING);
+	int res = compile_filter(NULL, &actual, NO_LOGGING);
 	ASSERT_NE(res, 0);
 }
 
 TEST_F(filter, log) {
 	struct sock_fprog actual;
+	const char *policy =
+		"read: 1\n"
+		"write: 1\n"
+		"rt_sigreturn: 1\n"
+		"exit: 1\n";
 
-	FILE *policy = fopen("test/seccomp.policy", "r");
-	int res = compile_filter(policy, &actual, USE_LOGGING);
+	FILE *policy_file = write_policy_to_pipe(policy, strlen(policy));
+	ASSERT_NE(policy_file, NULL);
+
+	int res = compile_filter(policy_file, &actual, USE_LOGGING);
+	fclose(policy_file);
 
 	size_t i;
 	size_t index = 0;
@@ -804,8 +868,6 @@
 			SECCOMP_RET_TRAP);
 
 	free(actual.filter);
-	fclose(policy);
 }
-#endif	/* __ANDROID__ */
 
 TEST_HARNESS_MAIN