aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJorge Lucangeli Obes <jorgelo@google.com>2017-03-27 20:04:04 +0000
committerandroid-build-merger <android-build-merger@google.com>2017-03-27 20:04:04 +0000
commit3772d3674bda9b5d0beecb4c689e347ea5ce174b (patch)
tree8dddb9922153a5686d555ba66c52466c4f92a717
parent2ed4130a89b1a4e69781a92080d0d32c4b05be93 (diff)
parent379130180580668ab3cd36561e3c9fdd36a13036 (diff)
downloadminijail-3772d3674bda9b5d0beecb4c689e347ea5ce174b.tar.gz
Implement @include functionality for seccomp policy files. am: bce4ccb48c am: 85b8238493android-vts-8.0_r2android-vts-8.0_r1oreo-dev
am: 3791301805 Change-Id: Icc24c16e58eb7c578a01e8949a1c76ad2c9712a6
-rw-r--r--syscall_filter.c120
-rw-r--r--syscall_filter.h3
-rw-r--r--syscall_filter_unittest.cc245
-rw-r--r--test/nested.policy1
4 files changed, 348 insertions, 21 deletions
diff --git a/syscall_filter.c b/syscall_filter.c
index 3318052..956fe21 100644
--- a/syscall_filter.c
+++ b/syscall_filter.c
@@ -164,6 +164,11 @@ unsigned int success_lbl(struct bpf_labels *labels, int nr)
return get_label_id(labels, lbl_str);
}
+int is_implicit_relative_path(const char *filename)
+{
+ return filename[0] != '/' && (filename[0] != '.' || filename[1] != '/');
+}
+
int compile_atom(struct filter_block *head, char *atom,
struct bpf_labels *labels, int nr, int grp_idx)
{
@@ -428,9 +433,57 @@ struct filter_block *compile_policy_line(int nr, const char *policy_line,
return head;
}
+int parse_include_statement(char *policy_line, unsigned int include_level,
+ const char **ret_filename)
+{
+ if (strncmp("@include", policy_line, strlen("@include")) != 0) {
+ warn("invalid statement '%s'", policy_line);
+ return -1;
+ }
+
+ if (policy_line[strlen("@include")] != ' ') {
+ warn("invalid include statement '%s'", policy_line);
+ return -1;
+ }
+
+ /*
+ * Disallow nested includes: only the initial policy file can have
+ * @include statements.
+ * Nested includes are not currently necessary and make the policy
+ * harder to understand.
+ */
+ if (include_level > 0) {
+ warn("@include statement nested too deep");
+ return -1;
+ }
+
+ char *statement = policy_line;
+ /* Discard "@include" token. */
+ (void)strsep(&statement, " ");
+
+ /*
+ * compile_filter() below receives a FILE*, so it's not trivial to open
+ * included files relative to the initial policy filename.
+ * To avoid mistakes, force the included file path to be absolute
+ * (start with '/'), or to explicitly load the file relative to CWD by
+ * using './'.
+ */
+ const char *filename = statement;
+ if (is_implicit_relative_path(filename)) {
+ warn("compile_file: implicit relative path '%s' not supported, "
+ "use './%s'",
+ filename, filename);
+ return -1;
+ }
+
+ *ret_filename = filename;
+ return 0;
+}
+
int compile_file(FILE *policy_file, struct filter_block *head,
struct filter_block **arg_blocks, struct bpf_labels *labels,
- int use_ret_trap, int allow_logging)
+ int use_ret_trap, int allow_logging,
+ unsigned int include_level)
{
/*
* Loop through all the lines in the policy file.
@@ -442,27 +495,62 @@ int compile_file(FILE *policy_file, struct filter_block *head,
*/
char *line = NULL;
size_t len = 0;
+ int ret = 0;
+
while (getline(&line, &len, policy_file) != -1) {
char *policy_line = line;
- char *syscall_name = strsep(&policy_line, ":");
- int nr = -1;
-
- syscall_name = strip(syscall_name);
+ policy_line = strip(policy_line);
/* Allow comments and empty lines. */
- if (*syscall_name == '#' || *syscall_name == '\0') {
+ if (*policy_line == '#' || *policy_line == '\0') {
/* Reuse |line| in the next getline() call. */
continue;
}
+ /* Allow @include statements. */
+ if (*policy_line == '@') {
+ const char *filename = NULL;
+ if (parse_include_statement(policy_line, include_level,
+ &filename) != 0) {
+ warn("compile_file: failed to parse include "
+ "statement");
+ ret = -1;
+ goto free_line;
+ }
+
+ FILE *included_file = fopen(filename, "re");
+ if (included_file == NULL) {
+ pwarn("compile_file: fopen('%s') failed",
+ filename);
+ ret = -1;
+ goto free_line;
+ }
+ if (compile_file(included_file, head, arg_blocks,
+ labels, use_ret_trap, allow_logging,
+ ++include_level) == -1) {
+ warn("compile_file: '@include %s' failed",
+ filename);
+ fclose(included_file);
+ ret = -1;
+ goto free_line;
+ }
+ fclose(included_file);
+ continue;
+ }
+
+ /*
+ * If it's not a comment, or an empty line, or an @include
+ * statement, treat |policy_line| as a regular policy line.
+ */
+ char *syscall_name = strsep(&policy_line, ":");
policy_line = strip(policy_line);
if (*policy_line == '\0') {
warn("compile_file: empty policy line");
- free(line);
- return -1;
+ ret = -1;
+ goto free_line;
}
- nr = lookup_syscall(syscall_name);
+ int nr = lookup_syscall(syscall_name);
if (nr < 0) {
warn("compile_file: nonexistent syscall '%s'",
syscall_name);
@@ -481,8 +569,8 @@ int compile_file(FILE *policy_file, struct filter_block *head,
/* Reuse |line| in the next getline() call. */
continue;
}
- free(line);
- return -1;
+ ret = -1;
+ goto free_line;
}
/*
@@ -511,8 +599,8 @@ int compile_file(FILE *policy_file, struct filter_block *head,
if (*arg_blocks) {
free_block_list(*arg_blocks);
}
- free(line);
- return -1;
+ ret = -1;
+ goto free_line;
}
if (*arg_blocks) {
@@ -523,8 +611,10 @@ int compile_file(FILE *policy_file, struct filter_block *head,
}
/* Reuse |line| in the next getline() call. */
}
+
+free_line:
free(line);
- return 0;
+ return ret;
}
int compile_filter(FILE *initial_file, struct sock_fprog *prog,
@@ -556,7 +646,7 @@ int compile_filter(FILE *initial_file, struct sock_fprog *prog,
allow_logging_syscalls(head);
if (compile_file(initial_file, head, &arg_blocks, &labels, use_ret_trap,
- allow_logging) != 0) {
+ allow_logging, 0 /* include_level */) != 0) {
warn("compile_filter: compile_file() failed");
free_block_list(head);
free_block_list(arg_blocks);
diff --git a/syscall_filter.h b/syscall_filter.h
index 3bfbfee..d15e8a9 100644
--- a/syscall_filter.h
+++ b/syscall_filter.h
@@ -32,7 +32,8 @@ struct filter_block *compile_policy_line(int nr, const char *policy_line,
int do_ret_trap);
int compile_file(FILE *policy_file, struct filter_block *head,
struct filter_block **arg_blocks, struct bpf_labels *labels,
- int use_ret_trap, int allow_logging);
+ int use_ret_trap, int allow_logging,
+ unsigned int include_level);
int compile_filter(FILE *policy_file, struct sock_fprog *prog, int do_ret_trap,
int add_logging_syscalls);
diff --git a/syscall_filter_unittest.cc b/syscall_filter_unittest.cc
index 4a80976..85c8a55 100644
--- a/syscall_filter_unittest.cc
+++ b/syscall_filter_unittest.cc
@@ -948,7 +948,6 @@ class FileTest : public ::testing::Test {
};
TEST_F(FileTest, seccomp_mode1) {
- // struct sock_fprog actual;
const char *policy =
"read: 1\n"
"write: 1\n"
@@ -958,7 +957,7 @@ TEST_F(FileTest, seccomp_mode1) {
FILE *policy_file = write_policy_to_pipe(policy, strlen(policy));
ASSERT_NE(policy_file, nullptr);
int res = compile_file(
- policy_file, head_, &arg_blocks_, &labels_, USE_RET_KILL, NO_LOGGING);
+ policy_file, head_, &arg_blocks_, &labels_, USE_RET_KILL, NO_LOGGING, 0);
fclose(policy_file);
/*
@@ -993,7 +992,7 @@ TEST_F(FileTest, seccomp_read) {
FILE *policy_file = write_policy_to_pipe(policy, strlen(policy));
ASSERT_NE(policy_file, nullptr);
int res = compile_file(
- policy_file, head_, &arg_blocks_, &labels_, USE_RET_KILL, NO_LOGGING);
+ policy_file, head_, &arg_blocks_, &labels_, USE_RET_KILL, NO_LOGGING, 0);
fclose(policy_file);
/*
@@ -1174,7 +1173,7 @@ TEST(FilterTest, missing_atom) {
struct sock_fprog actual;
const char* policy = "open:\n";
- FILE* policy_file = write_policy_to_pipe(policy, strlen(policy));
+ FILE *policy_file = write_policy_to_pipe(policy, strlen(policy));
ASSERT_NE(policy_file, nullptr);
int res = compile_filter(policy_file, &actual, USE_RET_KILL, NO_LOGGING);
@@ -1186,7 +1185,7 @@ TEST(FilterTest, whitespace_atom) {
struct sock_fprog actual;
const char* policy = "open:\t \n";
- FILE* policy_file = write_policy_to_pipe(policy, strlen(policy));
+ FILE *policy_file = write_policy_to_pipe(policy, strlen(policy));
ASSERT_NE(policy_file, nullptr);
int res = compile_filter(policy_file, &actual, USE_RET_KILL, NO_LOGGING);
@@ -1315,3 +1314,239 @@ TEST(FilterTest, allow_log_but_kill) {
free(actual.filter);
}
+
+TEST(FilterTest, include_invalid_token) {
+ struct sock_fprog actual;
+ const char *invalid_token = "@unclude ./test/seccomp.policy\n";
+
+ FILE *policy_file =
+ write_policy_to_pipe(invalid_token, strlen(invalid_token));
+ ASSERT_NE(policy_file, nullptr);
+ int res = compile_filter(policy_file, &actual, USE_RET_KILL, NO_LOGGING);
+ fclose(policy_file);
+ EXPECT_NE(res, 0);
+}
+
+TEST(FilterTest, include_no_space) {
+ struct sock_fprog actual;
+ const char *no_space = "@includetest/seccomp.policy\n";
+
+ FILE *policy_file = write_policy_to_pipe(no_space, strlen(no_space));
+ ASSERT_NE(policy_file, nullptr);
+ int res = compile_filter(policy_file, &actual, USE_RET_KILL, NO_LOGGING);
+ fclose(policy_file);
+ EXPECT_NE(res, 0);
+}
+
+TEST(FilterTest, include_double_token) {
+ struct sock_fprog actual;
+ const char *double_token = "@includeinclude ./test/seccomp.policy\n";
+
+ FILE *policy_file = write_policy_to_pipe(double_token, strlen(double_token));
+ ASSERT_NE(policy_file, nullptr);
+ int res = compile_filter(policy_file, &actual, USE_RET_KILL, NO_LOGGING);
+ fclose(policy_file);
+ EXPECT_NE(res, 0);
+}
+
+TEST(FilterTest, include_no_file) {
+ struct sock_fprog actual;
+ const char *no_file = "@include\n";
+
+ FILE *policy_file = write_policy_to_pipe(no_file, strlen(no_file));
+ ASSERT_NE(policy_file, nullptr);
+ int res = compile_filter(policy_file, &actual, USE_RET_KILL, NO_LOGGING);
+ fclose(policy_file);
+ EXPECT_NE(res, 0);
+}
+
+TEST(FilterTest, include_space_no_file) {
+ struct sock_fprog actual;
+ const char *space_no_file = "@include \n";
+
+ FILE *policy_file =
+ write_policy_to_pipe(space_no_file, strlen(space_no_file));
+ ASSERT_NE(policy_file, nullptr);
+ int res = compile_filter(policy_file, &actual, USE_RET_KILL, NO_LOGGING);
+ fclose(policy_file);
+ EXPECT_NE(res, 0);
+}
+
+TEST(FilterTest, include_implicit_relative_path) {
+ struct sock_fprog actual;
+ const char *implicit_relative_path = "@include test/seccomp.policy\n";
+
+ FILE *policy_file = write_policy_to_pipe(implicit_relative_path,
+ strlen(implicit_relative_path));
+ ASSERT_NE(policy_file, nullptr);
+ int res = compile_filter(policy_file, &actual, USE_RET_KILL, NO_LOGGING);
+ fclose(policy_file);
+ EXPECT_NE(res, 0);
+}
+
+TEST(FilterTest, include_extra_text) {
+ struct sock_fprog actual;
+ const char *extra_text = "@include /some/file: sneaky comment\n";
+
+ FILE *policy_file =
+ write_policy_to_pipe(extra_text, strlen(extra_text));
+ ASSERT_NE(policy_file, nullptr);
+ int res = compile_filter(policy_file, &actual, USE_RET_KILL, NO_LOGGING);
+ fclose(policy_file);
+ EXPECT_NE(res, 0);
+}
+
+TEST(FilterTest, include_split_filename) {
+ struct sock_fprog actual;
+ const char *split_filename = "@include /some/file:colon.policy\n";
+
+ FILE *policy_file =
+ write_policy_to_pipe(split_filename, strlen(split_filename));
+ ASSERT_NE(policy_file, nullptr);
+ int res = compile_filter(policy_file, &actual, USE_RET_KILL, NO_LOGGING);
+ fclose(policy_file);
+ EXPECT_NE(res, 0);
+}
+
+TEST(FilterTest, include_nonexistent_file) {
+ struct sock_fprog actual;
+ const char *include_policy = "@include ./nonexistent.policy\n";
+
+ FILE *policy_file =
+ write_policy_to_pipe(include_policy, strlen(include_policy));
+ ASSERT_NE(policy_file, nullptr);
+
+ int res = compile_filter(policy_file, &actual, USE_RET_KILL, NO_LOGGING);
+ fclose(policy_file);
+
+ ASSERT_NE(res, 0);
+}
+
+// TODO(jorgelo): Android unit tests don't currently support data files.
+// Re-enable by creating a temporary policy file at runtime.
+#if !defined(__ANDROID__)
+
+TEST(FilterTest, include) {
+ struct sock_fprog compiled_plain;
+ struct sock_fprog compiled_with_include;
+
+ const char *policy_plain =
+ "read: 1\n"
+ "write: 1\n"
+ "rt_sigreturn: 1\n"
+ "exit: 1\n";
+
+ const char *policy_with_include = "@include ./test/seccomp.policy\n";
+
+ FILE *file_plain = write_policy_to_pipe(policy_plain, strlen(policy_plain));
+ ASSERT_NE(file_plain, nullptr);
+ int res_plain =
+ compile_filter(file_plain, &compiled_plain, USE_RET_KILL, NO_LOGGING);
+ fclose(file_plain);
+
+ FILE *file_with_include =
+ write_policy_to_pipe(policy_with_include, strlen(policy_with_include));
+ ASSERT_NE(file_with_include, nullptr);
+ int res_with_include = compile_filter(
+ file_with_include, &compiled_with_include, USE_RET_KILL, NO_LOGGING);
+ fclose(file_with_include);
+
+ /*
+ * Checks that filter length is the same for a plain policy and an equivalent
+ * policy with an @include statement. Also checks that the filter generated
+ * from the policy with an @include statement is exactly the same as one
+ * generated from a plain policy.
+ */
+ ASSERT_EQ(res_plain, 0);
+ ASSERT_EQ(res_with_include, 0);
+
+ EXPECT_EQ(compiled_plain.len, 13);
+ EXPECT_EQ(compiled_with_include.len, 13);
+
+ EXPECT_ARCH_VALIDATION(compiled_with_include.filter);
+ EXPECT_EQ_STMT(compiled_with_include.filter + ARCH_VALIDATION_LEN,
+ BPF_LD + BPF_W + BPF_ABS,
+ syscall_nr);
+ EXPECT_ALLOW_SYSCALL(compiled_with_include.filter + ARCH_VALIDATION_LEN + 1,
+ __NR_read);
+ EXPECT_ALLOW_SYSCALL(compiled_with_include.filter + ARCH_VALIDATION_LEN + 3,
+ __NR_write);
+ EXPECT_ALLOW_SYSCALL(compiled_with_include.filter + ARCH_VALIDATION_LEN + 5,
+ __NR_rt_sigreturn);
+ EXPECT_ALLOW_SYSCALL(compiled_with_include.filter + ARCH_VALIDATION_LEN + 7,
+ __NR_exit);
+ EXPECT_EQ_STMT(compiled_with_include.filter + ARCH_VALIDATION_LEN + 9,
+ BPF_RET + BPF_K,
+ SECCOMP_RET_KILL);
+
+ free(compiled_plain.filter);
+ free(compiled_with_include.filter);
+}
+
+TEST(FilterTest, include_same_syscalls) {
+ struct sock_fprog actual;
+ const char *policy =
+ "read: 1\n"
+ "write: 1\n"
+ "rt_sigreturn: 1\n"
+ "exit: 1\n"
+ "@include ./test/seccomp.policy\n";
+
+ FILE *policy_file =
+ write_policy_to_pipe(policy, strlen(policy));
+ ASSERT_NE(policy_file, nullptr);
+
+ int res = compile_filter(policy_file, &actual, USE_RET_KILL, NO_LOGGING);
+ fclose(policy_file);
+
+ ASSERT_EQ(res, 0);
+ EXPECT_EQ(actual.len,
+ ARCH_VALIDATION_LEN + 1 /* load syscall nr */ +
+ 2 * 8 /* check syscalls twice */ + 1 /* filter return */);
+ free(actual.filter);
+}
+
+TEST(FilterTest, include_invalid_policy) {
+ struct sock_fprog actual;
+ const char *policy =
+ "read: 1\n"
+ "write: 1\n"
+ "rt_sigreturn: 1\n"
+ "exit: 1\n"
+ "@include ./test/invalid_syscall_name.policy\n";
+
+ FILE *policy_file =
+ write_policy_to_pipe(policy, strlen(policy));
+ ASSERT_NE(policy_file, nullptr);
+
+ /* Ensure the included (invalid) policy file exists. */
+ FILE *included_file = fopen("./test/invalid_syscall_name.policy", "r");
+ ASSERT_NE(included_file, nullptr);
+ fclose(included_file);
+
+ int res = compile_filter(policy_file, &actual, USE_RET_KILL, NO_LOGGING);
+ fclose(policy_file);
+
+ ASSERT_NE(res, 0);
+}
+
+TEST(FilterTest, include_nested) {
+ struct sock_fprog actual;
+ const char *policy = "@include ./test/nested.policy\n";
+
+ FILE *policy_file =
+ write_policy_to_pipe(policy, strlen(policy));
+ ASSERT_NE(policy_file, nullptr);
+
+ /* Ensure the policy file exists. */
+ FILE *included_file = fopen("./test/nested.policy", "r");
+ ASSERT_NE(included_file, nullptr);
+ fclose(included_file);
+
+ int res = compile_filter(policy_file, &actual, USE_RET_KILL, NO_LOGGING);
+ fclose(policy_file);
+
+ ASSERT_NE(res, 0);
+}
+
+#endif // !__ANDROID__
diff --git a/test/nested.policy b/test/nested.policy
new file mode 100644
index 0000000..df4491b
--- /dev/null
+++ b/test/nested.policy
@@ -0,0 +1 @@
+@include ./test/nested.policy