aboutsummaryrefslogtreecommitdiff
path: root/syscall_filter.c
diff options
context:
space:
mode:
Diffstat (limited to 'syscall_filter.c')
-rw-r--r--syscall_filter.c120
1 files changed, 105 insertions, 15 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);