diff options
author | Jorge Lucangeli Obes <jorgelo@google.com> | 2019-06-12 14:45:06 -0400 |
---|---|---|
committer | Jorge Lucangeli Obes <jorgelo@google.com> | 2019-06-24 10:02:41 -0400 |
commit | 32201f8a02ad582aa08b8a71ff2938dc7bc038d1 (patch) | |
tree | ca5061c1290ac2d7df69c09959d70040cbcc8e01 | |
parent | e1a868923c2daf9a7ed3b9300f5e18ca295bf0e8 (diff) | |
download | minijail-32201f8a02ad582aa08b8a71ff2938dc7bc038d1.tar.gz |
Add support for SECCOMP_RET_LOG.
Detect at runtime whether SECCOMP_RET_LOG is available and use that for
logging.
Bug: chromium:934859
Test: New unit tests.
Test: On 4.14 device, minijail0 -S -L test/seccomp.policy -- /bin/true.
Test: audit.log shows failing syscall, binary exits successfully.
Test: On <4.14 device, behaves as before.
Change-Id: Ic9da1b5dae2b4b1df50e9d3e6f18c816e93bff87
-rw-r--r-- | libminijail.c | 136 | ||||
-rw-r--r-- | minijail0.1 | 12 | ||||
-rw-r--r-- | minijail0_cli.c | 5 | ||||
-rw-r--r-- | parse_seccomp_policy.cc | 8 | ||||
-rw-r--r-- | syscall_filter.c | 2 | ||||
-rw-r--r-- | syscall_filter_unittest.cc | 192 | ||||
-rw-r--r-- | syscall_filter_unittest_macros.h | 14 | ||||
-rw-r--r-- | system.c | 58 | ||||
-rw-r--r-- | system.h | 3 | ||||
-rw-r--r-- | system_unittest.cc | 5 | ||||
-rw-r--r-- | util.h | 8 |
11 files changed, 366 insertions, 77 deletions
diff --git a/libminijail.c b/libminijail.c index 9b54be3..6608cdd 100644 --- a/libminijail.c +++ b/libminijail.c @@ -382,6 +382,17 @@ void API minijail_set_seccomp_filter_tsync(struct minijail *j) die("minijail_set_seccomp_filter_tsync() must be called " "before minijail_parse_seccomp_filters()"); } + + if (j->flags.seccomp_filter_logging && !seccomp_ret_log_available()) { + /* + * If SECCOMP_RET_LOG is not available, we don't want to use + * SECCOMP_RET_TRAP to both kill the entire process and report + * failing syscalls, since it will be brittle. Just bail. + */ + die("SECCOMP_RET_LOG not available, cannot use logging and " + "thread sync at the same time"); + } + j->flags.seccomp_filter_tsync = 1; } @@ -391,11 +402,23 @@ void API minijail_log_seccomp_filter_failures(struct minijail *j) die("minijail_log_seccomp_filter_failures() must be called " "before minijail_parse_seccomp_filters()"); } -#ifdef ALLOW_DEBUG_LOGGING - j->flags.seccomp_filter_logging = 1; -#else - warn("non-debug build: ignoring request to enable seccomp logging"); -#endif + + if (j->flags.seccomp_filter_tsync && !seccomp_ret_log_available()) { + /* + * If SECCOMP_RET_LOG is not available, we don't want to use + * SECCOMP_RET_TRAP to both kill the entire process and report + * failing syscalls, since it will be brittle. Just bail. + */ + die("SECCOMP_RET_LOG not available, cannot use logging and " + "thread sync at the same time"); + } + + if (debug_logging_allowed()) { + j->flags.seccomp_filter_logging = 1; + } else { + warn("non-debug build: ignoring request to enable seccomp " + "logging"); + } } void API minijail_use_caps(struct minijail *j, uint64_t capmask) @@ -932,12 +955,17 @@ static int seccomp_should_use_filters(struct minijail *j) } static int set_seccomp_filters_internal(struct minijail *j, - struct sock_fprog *filter, bool owned) + const struct sock_fprog *filter, + bool owned) { struct sock_fprog *fprog; if (owned) { - fprog = filter; + /* + * If |owned| is true, it's OK to cast away the const-ness since + * we'll own the pointer going forward. + */ + fprog = (struct sock_fprog *)filter; } else { fprog = malloc(sizeof(struct sock_fprog)); if (!fprog) @@ -962,48 +990,48 @@ static int set_seccomp_filters_internal(struct minijail *j, return 0; } -void API minijail_set_seccomp_filters(struct minijail *j, - const struct sock_fprog *filter) +static int parse_seccomp_filters(struct minijail *j, const char *filename, + FILE *policy_file) { - if (!seccomp_should_use_filters(j)) - return; + struct sock_fprog *fprog = malloc(sizeof(struct sock_fprog)); + if (!fprog) + return -ENOMEM; - if (j->flags.seccomp_filter_logging) { - die("minijail_log_seccomp_filter_failures() is incompatible " - "with minijail_set_seccomp_filters()"); - } + struct filter_options filteropts; /* - * set_seccomp_filters_internal() can only fail with ENOMEM. - * Furthermore, since we won't own the incoming filter, it will not be - * modified. + * Figure out filter options. + * Allow logging? */ - if (set_seccomp_filters_internal(j, (struct sock_fprog *)filter, - false) < 0) { - die("failed to copy seccomp filter"); + filteropts.allow_logging = + debug_logging_allowed() && j->flags.seccomp_filter_logging; + + /* What to do on a blocked system call? */ + if (filteropts.allow_logging) { + if (seccomp_ret_log_available()) + filteropts.action = ACTION_RET_LOG; + else + filteropts.action = ACTION_RET_TRAP; + } else { + if (j->flags.seccomp_filter_tsync) + filteropts.action = ACTION_RET_TRAP; + else + filteropts.action = ACTION_RET_KILL; } -} -static int parse_seccomp_filters(struct minijail *j, const char *filename, - FILE *policy_file) -{ - struct sock_fprog *fprog = malloc(sizeof(struct sock_fprog)); - if (!fprog) - return -ENOMEM; - int use_ret_trap = - j->flags.seccomp_filter_tsync || j->flags.seccomp_filter_logging; + /* + * If SECCOMP_RET_LOG is not available, need to allow extra syscalls + * for logging. + */ + filteropts.allow_syscalls_for_logging = + filteropts.allow_logging && !seccomp_ret_log_available(); - struct filter_options filteropts = { - .action = use_ret_trap ? ACTION_RET_TRAP : ACTION_RET_KILL, - .allow_logging = j->flags.seccomp_filter_logging, - .allow_syscalls_for_logging = j->flags.seccomp_filter_logging, - }; if (compile_filter(filename, policy_file, fprog, &filteropts)) { free(fprog); return -1; } - return set_seccomp_filters_internal(j, fprog, true); + return set_seccomp_filters_internal(j, fprog, true /* owned */); } void API minijail_parse_seccomp_filters(struct minijail *j, const char *path) @@ -1051,6 +1079,27 @@ void API minijail_parse_seccomp_filters_from_fd(struct minijail *j, int fd) fclose(file); } +void API minijail_set_seccomp_filters(struct minijail *j, + const struct sock_fprog *filter) +{ + if (!seccomp_should_use_filters(j)) + return; + + if (j->flags.seccomp_filter_logging) { + die("minijail_log_seccomp_filter_failures() is incompatible " + "with minijail_set_seccomp_filters()"); + } + + /* + * set_seccomp_filters_internal() can only fail with ENOMEM. + * Furthermore, since we won't own the incoming filter, it will not be + * modified. + */ + if (set_seccomp_filters_internal(j, filter, false /* owned */) < 0) { + die("failed to set seccomp filter"); + } +} + int API minijail_use_alt_syscall(struct minijail *j, const char *table) { j->alt_syscall_table = strdup(table); @@ -1962,13 +2011,16 @@ static void set_seccomp_filter(const struct minijail *j) if (j->flags.seccomp_filter) { if (j->flags.seccomp_filter_logging) { - /* - * If logging seccomp filter failures, - * install the SIGSYS handler first. - */ - if (install_sigsys_handler()) - pdie("failed to install SIGSYS handler"); warn("logging seccomp filter failures"); + if (!seccomp_ret_log_available()) { + /* + * If SECCOMP_RET_LOG is not available, + * install the SIGSYS handler first. + */ + if (install_sigsys_handler()) + pdie( + "failed to install SIGSYS handler"); + } } else if (j->flags.seccomp_filter_tsync) { /* * If setting thread sync, diff --git a/minijail0.1 b/minijail0.1 index a18454e..d23f14a 100644 --- a/minijail0.1 +++ b/minijail0.1 @@ -157,9 +157,13 @@ Run inside a new IPC namespace. This option makes the program's System V IPC namespace independent. .TP \fB-L\fR -Report blocked syscalls to syslog when using seccomp filter. This option will -force certain syscalls to be allowed in order to achieve this, depending on the -system. +Report blocked syscalls when using a seccomp filter. On kernels with support for +SECCOMP_RET_LOG, every blocked syscall will be reported through the audit +subsystem (see \fBseccomp\fR(2) for more details on SECCOMP_RET_LOG +availability.) On all other kernels, the first failing syscall will be logged to +syslog. This latter case will also force certain syscalls to be allowed in order +to write to syslog. Note: this option is disabled and ignored for release +builds. .TP \fB-m[<uid> <loweruid> <count>[,<uid> <loweruid> <count>]]\fR Set the uid mapping of a user namespace (implies \fB-pU\fR). Same arguments as @@ -345,4 +349,4 @@ The Chromium OS Authors <chromiumos-dev@chromium.org> Copyright \(co 2011 The Chromium OS Authors License BSD-like. .SH "SEE ALSO" -\fBlibminijail.h\fR \fBminijail0\fR(5) +\fBlibminijail.h\fR \fBminijail0\fR(5) \fBseccomp\fR(2) diff --git a/minijail0_cli.c b/minijail0_cli.c index b0b6518..1b92b09 100644 --- a/minijail0_cli.c +++ b/minijail0_cli.c @@ -505,8 +505,9 @@ static void usage(const char *progn) " -K: Do not change share mode of any existing mounts.\n" " -K<mode>: Mark all existing mounts as <mode> instead of MS_PRIVATE.\n" " -l: Enter new IPC namespace.\n" - " -L: Report blocked syscalls to syslog when using seccomp filter.\n" - " Forces the following syscalls to be allowed:\n" + " -L: Report blocked syscalls when using seccomp filter.\n" + " If the kernel does not support SECCOMP_RET_LOG,\n" + " forces the following syscalls to be allowed:\n" " ", progn); /* clang-format on */ for (i = 0; i < log_syscalls_len; i++) diff --git a/parse_seccomp_policy.cc b/parse_seccomp_policy.cc index c563a4a..38fcbee 100644 --- a/parse_seccomp_policy.cc +++ b/parse_seccomp_policy.cc @@ -78,8 +78,14 @@ int main(int argc, char** argv) { if (!f) pdie("fopen(%s) failed", argv[1]); + struct filter_options fopts { + .action = ACTION_RET_KILL, + .allow_logging = 0, + .allow_syscalls_for_logging = 0, + }; + struct sock_fprog fp; - int res = compile_filter(argv[1], f, &fp, 0, 0); + int res = compile_filter(argv[1], f, &fp, &fopts); fclose(f); if (res != 0) die("compile_filter failed"); diff --git a/syscall_filter.c b/syscall_filter.c index 4f8abe5..163cb0b 100644 --- a/syscall_filter.c +++ b/syscall_filter.c @@ -699,6 +699,7 @@ int compile_file(const char *filename, FILE *policy_file, free_block_list(*arg_blocks); *arg_blocks = NULL; } + warn("could not allocate filter block"); ret = -1; goto free_line; } @@ -717,6 +718,7 @@ int compile_file(const char *filename, FILE *policy_file, free_block_list(*arg_blocks); *arg_blocks = NULL; } + warn("getmultiline() failed"); ret = -1; } diff --git a/syscall_filter_unittest.cc b/syscall_filter_unittest.cc index 595c4cd..b82c2e3 100644 --- a/syscall_filter_unittest.cc +++ b/syscall_filter_unittest.cc @@ -38,22 +38,21 @@ enum ret_trap { }; enum use_logging { - NO_LOGGING = 0, - USE_LOGGING = 1, + NO_LOGGING = 0, + USE_SIGSYS_LOGGING = 1, + USE_RET_LOG_LOGGING = 2, }; int test_compile_filter( std::string filename, FILE* policy_file, struct sock_fprog* prog, - enum ret_trap use_ret_trap = USE_RET_KILL, + enum block_action action = ACTION_RET_KILL, enum use_logging allow_logging = NO_LOGGING) { - enum block_action action = - (use_ret_trap == USE_RET_TRAP) ? ACTION_RET_TRAP : ACTION_RET_KILL; struct filter_options filteropts { .action = action, - .allow_logging = allow_logging == USE_LOGGING, - .allow_syscalls_for_logging = allow_logging == USE_LOGGING, + .allow_logging = allow_logging != NO_LOGGING, + .allow_syscalls_for_logging = allow_logging == USE_SIGSYS_LOGGING, }; return compile_filter(filename.c_str(), policy_file, prog, &filteropts); } @@ -64,15 +63,13 @@ int test_compile_file( struct filter_block* head, struct filter_block** arg_blocks, struct bpf_labels* labels, - enum ret_trap use_ret_trap = USE_RET_KILL, + enum block_action action = ACTION_RET_KILL, enum use_logging allow_logging = NO_LOGGING, unsigned int include_level = 0) { - enum block_action action = - (use_ret_trap == USE_RET_TRAP) ? ACTION_RET_TRAP : ACTION_RET_KILL; struct filter_options filteropts { .action = action, - .allow_logging = allow_logging == USE_LOGGING, - .allow_syscalls_for_logging = allow_logging == USE_LOGGING, + .allow_logging = allow_logging != NO_LOGGING, + .allow_syscalls_for_logging = allow_logging == USE_SIGSYS_LOGGING, }; return compile_file(filename.c_str(), policy_file, head, arg_blocks, labels, &filteropts, include_level); @@ -84,10 +81,9 @@ struct filter_block* test_compile_policy_line( std::string policy_line, unsigned int label_id, struct bpf_labels* labels, - enum ret_trap do_ret_trap = USE_RET_KILL) { - return compile_policy_line( - state, nr, policy_line.c_str(), label_id, labels, - do_ret_trap == USE_RET_TRAP ? ACTION_RET_TRAP : ACTION_RET_KILL); + enum block_action action = ACTION_RET_KILL) { + return compile_policy_line(state, nr, policy_line.c_str(), label_id, + labels, action); } } // namespace @@ -536,6 +532,86 @@ TEST_F(ArgFilterTest, arg0_equals) { free_block_list(block); } +TEST_F(ArgFilterTest, arg0_equals_trap) { + std::string fragment = "arg0 == 0"; + + struct filter_block* block = test_compile_policy_line( + &state_, nr_, fragment, id_, &labels_, ACTION_RET_TRAP); + + ASSERT_NE(block, nullptr); + size_t exp_total_len = 1 + (BPF_ARG_COMP_LEN + 1) + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block* curr_block = block; + ASSERT_NE(curr_block, nullptr); + EXPECT_EQ(curr_block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison. */ + curr_block = curr_block->next; + EXPECT_COMP(curr_block); + + /* Third block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, nullptr); + EXPECT_GROUP_END(curr_block); + + /* Fourth block is SECCOMP_RET_TRAP. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, nullptr); + EXPECT_TRAP(curr_block); + + /* Fifth block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, nullptr); + EXPECT_ALLOW(curr_block); + + EXPECT_EQ(curr_block->next, nullptr); + + free_block_list(block); +} + +TEST_F(ArgFilterTest, arg0_equals_log) { + std::string fragment = "arg0 == 0"; + + struct filter_block* block = test_compile_policy_line( + &state_, nr_, fragment, id_, &labels_, ACTION_RET_LOG); + + ASSERT_NE(block, nullptr); + size_t exp_total_len = 1 + (BPF_ARG_COMP_LEN + 1) + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block* curr_block = block; + ASSERT_NE(curr_block, nullptr); + EXPECT_EQ(curr_block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison. */ + curr_block = curr_block->next; + EXPECT_COMP(curr_block); + + /* Third block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, nullptr); + EXPECT_GROUP_END(curr_block); + + /* Fourth block is SECCOMP_RET_LOG. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, nullptr); + EXPECT_LOG(curr_block); + + /* Fifth block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, nullptr); + EXPECT_ALLOW(curr_block); + + EXPECT_EQ(curr_block->next, nullptr); + + free_block_list(block); +} + TEST_F(ArgFilterTest, arg0_short_gt_ge_comparisons) { for (std::string fragment : {"arg1 < 0xff", "arg1 <= 0xff", "arg1 > 0xff", "arg1 >= 0xff"}) { @@ -942,7 +1018,7 @@ TEST_F(ArgFilterTest, log_no_ret_error) { struct filter_block* block = test_compile_policy_line(&state_, nr_, fragment, id_, &labels_, - USE_RET_TRAP); + ACTION_RET_TRAP); ASSERT_NE(block, nullptr); size_t exp_total_len = 1 + (BPF_ARG_COMP_LEN + 1) + 2 + 1 + 2; @@ -1026,7 +1102,7 @@ TEST_F(ArgFilterTest, no_log_bad_ret_error) { struct filter_block* block = test_compile_policy_line(&state_, nr_, fragment, id_, &labels_, - USE_RET_TRAP); + ACTION_RET_TRAP); ASSERT_NE(block, nullptr); size_t exp_total_len = 1 + (BPF_ARG_COMP_LEN + 1) + 2 + 1 + 2; EXPECT_EQ(block->total_len, exp_total_len); @@ -1358,7 +1434,8 @@ TEST(FilterTest, seccomp_mode1_trap) { FILE* policy_file = write_policy_to_pipe(policy); ASSERT_NE(policy_file, nullptr); - int res = test_compile_filter("policy", policy_file, &actual, USE_RET_TRAP); + int res = + test_compile_filter("policy", policy_file, &actual, ACTION_RET_TRAP); fclose(policy_file); /* @@ -1385,6 +1462,66 @@ TEST(FilterTest, seccomp_mode1_trap) { free(actual.filter); } +TEST(FilterTest, seccomp_mode1_log) { + struct sock_fprog actual; + std::string policy = + "read: 1\n" + "write: 1\n" + "rt_sigreturn: 1\n" + "exit: 1\n"; + + FILE* policy_file = write_policy_to_pipe(policy); + ASSERT_NE(policy_file, nullptr); + + int res = test_compile_filter("policy", policy_file, &actual, ACTION_RET_LOG, + USE_RET_LOG_LOGGING); + fclose(policy_file); + + /* + * Checks return value, filter length, and that the filter + * validates arch, loads syscall number, and + * only allows expected syscalls. + */ + ASSERT_EQ(res, 0); + EXPECT_EQ(actual.len, 13); + EXPECT_ARCH_VALIDATION(actual.filter); + EXPECT_EQ_STMT(actual.filter + ARCH_VALIDATION_LEN, + BPF_LD+BPF_W+BPF_ABS, syscall_nr); + EXPECT_ALLOW_SYSCALL(actual.filter + ARCH_VALIDATION_LEN + 1, + __NR_read); + EXPECT_ALLOW_SYSCALL(actual.filter + ARCH_VALIDATION_LEN + 3, + __NR_write); + EXPECT_ALLOW_SYSCALL(actual.filter + ARCH_VALIDATION_LEN + 5, + __NR_rt_sigreturn); + EXPECT_ALLOW_SYSCALL(actual.filter + ARCH_VALIDATION_LEN + 7, + __NR_exit); + EXPECT_EQ_STMT(actual.filter + ARCH_VALIDATION_LEN + 9, BPF_RET+BPF_K, + SECCOMP_RET_LOG); + + free(actual.filter); +} + +TEST(FilterTest, seccomp_mode1_log_fails) { + struct sock_fprog actual; + std::string policy = + "read: 1\n" + "write: 1\n" + "rt_sigreturn: 1\n" + "exit: 1\n"; + + FILE* policy_file = write_policy_to_pipe(policy); + ASSERT_NE(policy_file, nullptr); + + int res = test_compile_filter("policy", policy_file, &actual, ACTION_RET_LOG, + NO_LOGGING); + fclose(policy_file); + + /* + * ACTION_RET_LOG should never be used without allowing logging. + */ + ASSERT_EQ(res, -1); +} + TEST(FilterTest, seccomp_read_write) { struct sock_fprog actual; std::string policy = @@ -1525,8 +1662,8 @@ TEST(FilterTest, log) { FILE* policy_file = write_policy_to_pipe(policy); ASSERT_NE(policy_file, nullptr); - int res = test_compile_filter("policy", policy_file, &actual, USE_RET_TRAP, - USE_LOGGING); + int res = test_compile_filter("policy", policy_file, &actual, ACTION_RET_TRAP, + USE_SIGSYS_LOGGING); fclose(policy_file); size_t i; @@ -1572,8 +1709,8 @@ TEST(FilterTest, allow_log_but_kill) { FILE* policy_file = write_policy_to_pipe(policy); ASSERT_NE(policy_file, nullptr); - int res = test_compile_filter("policy", policy_file, &actual, USE_RET_KILL, - USE_LOGGING); + int res = test_compile_filter("policy", policy_file, &actual, ACTION_RET_KILL, + USE_SIGSYS_LOGGING); fclose(policy_file); size_t i; @@ -1581,7 +1718,7 @@ TEST(FilterTest, allow_log_but_kill) { /* * Checks return value, filter length, and that the filter * validates arch, loads syscall number, only allows expected syscalls, - * and returns TRAP on failure. + * and kills on failure. * NOTE(jorgelo): the filter is longer since we add the syscalls needed * for logging. */ @@ -1726,7 +1863,7 @@ TEST(FilterTest, include) { FILE* file_plain = write_policy_to_pipe(policy_plain); ASSERT_NE(file_plain, nullptr); int res_plain = test_compile_filter("policy", file_plain, &compiled_plain, - USE_RET_KILL); + ACTION_RET_KILL); fclose(file_plain); std::string policy_with_include = @@ -1734,9 +1871,8 @@ TEST(FilterTest, include) { FILE* file_with_include = write_policy_to_pipe(policy_with_include); ASSERT_NE(file_with_include, nullptr); - int res_with_include = - test_compile_filter("policy", file_with_include, &compiled_with_include, - USE_RET_KILL); + int res_with_include = test_compile_filter( + "policy", file_with_include, &compiled_with_include, ACTION_RET_KILL); fclose(file_with_include); /* diff --git a/syscall_filter_unittest_macros.h b/syscall_filter_unittest_macros.h index 4923fc4..3972cb4 100644 --- a/syscall_filter_unittest_macros.h +++ b/syscall_filter_unittest_macros.h @@ -3,6 +3,11 @@ * found in the LICENSE file. */ +#ifndef SYSCALL_FILTER_UNITTEST_MACROS_H +#define SYSCALL_FILTER_UNITTEST_MACROS_H + +#include "bpf.h" + /* BPF testing macros. */ #define EXPECT_EQ_BLOCK(_block, _code, _k, _jt, _jf) \ do { \ @@ -76,6 +81,13 @@ do { \ BPF_RET+BPF_K, SECCOMP_RET_TRAP); \ } while (0) +#define EXPECT_LOG(_block) \ +do { \ + EXPECT_EQ((_block)->len, 1U); \ + EXPECT_EQ_STMT((_block)->instrs, \ + BPF_RET+BPF_K, SECCOMP_RET_LOG); \ +} while (0) + #define EXPECT_ALLOW(_block) \ do { \ EXPECT_EQ((_block)->len, 2U); \ @@ -107,3 +119,5 @@ do { \ EXPECT_EQ_BLOCK(&(_filter)[1], \ BPF_JMP+BPF_JA, (_id), (_jt), (_jf)); \ } while (0) + +#endif // SYSCALL_FILTER_UNITTEST_MACROS_H @@ -464,3 +464,61 @@ int lookup_group(const char *group, gid_t *gid) *gid = pgr->gr_gid; return 0; } + +static int seccomp_action_is_available(const char *wanted) +{ + if (is_android()) { + /* + * Accessing |actions_avail| is generating SELinux denials, so + * skip for now. + * TODO(crbug.com/978022, jorgelo): Remove once the denial is + * fixed. + */ + return 0; + } + const char actions_avail_path[] = + "/proc/sys/kernel/seccomp/actions_avail"; + FILE *f = fopen(actions_avail_path, "re"); + + if (!f) { + pwarn("fopen(%s) failed", actions_avail_path); + return 0; + } + + char *actions_avail = NULL; + size_t buf_size = 0; + if (getline(&actions_avail, &buf_size, f) < 0) { + pwarn("getline() failed"); + free(actions_avail); + return 0; + } + + /* + * This is just substring search, which means that partial matches will + * match too (e.g. "action" would match "longaction"). There are no + * seccomp actions which include other actions though, so we're good for + * now. Eventually we might want to split the string by spaces. + */ + return strstr(actions_avail, wanted) != NULL; +} + +int seccomp_ret_log_available(void) +{ + static int ret_log_available = -1; + + if (ret_log_available == -1) + ret_log_available = seccomp_action_is_available("log"); + + return ret_log_available; +} + +int seccomp_ret_kill_process_available(void) +{ + static int ret_kill_process_available = -1; + + if (ret_kill_process_available == -1) + ret_kill_process_available = + seccomp_action_is_available("kill_process"); + + return ret_kill_process_available; +} @@ -60,6 +60,9 @@ int setup_mount_destination(const char *source, const char *dest, uid_t uid, int lookup_user(const char *user, uid_t *uid, gid_t *gid); int lookup_group(const char *group, gid_t *gid); +int seccomp_ret_log_available(void); +int seccomp_ret_kill_process_available(void); + #ifdef __cplusplus }; /* extern "C" */ #endif diff --git a/system_unittest.cc b/system_unittest.cc index 36d8dc3..470421d 100644 --- a/system_unittest.cc +++ b/system_unittest.cc @@ -362,3 +362,8 @@ TEST(setup_mount_destination, create_char_dev) { // We check it's a directory by deleting it as such. EXPECT_EQ(0, rmdir(child_dev.c_str())); } + +TEST(seccomp_actions_available, smoke) { + seccomp_ret_log_available(); + seccomp_ret_kill_process_available(); +} @@ -134,6 +134,14 @@ static inline bool running_with_asan(void) return compiled_with_asan() || &__asan_init != 0 || &__hwasan_init != 0; } +static inline bool debug_logging_allowed(void) { +#if defined(ALLOW_DEBUG_LOGGING) + return true; +#else + return false; +#endif +} + int lookup_syscall(const char *name); const char *lookup_syscall_name(int nr); |