syscall_filter: Implement flag set inclusion.
When filtering syscalls that take flags as an argument, we usually want
to allow a small set of "safe" flags. This is hard to express with the
current language.
Implement this by adding a "flag set inclusion" mode using the 'in'
keyword. This works by allowing the syscall as long as the passed
flags, when viewed as a set, are included in the set of flags described
by the policy.
Also, clang-format all of bpf.c.
Bug: 31997910
Test: syscall_filter_unittest
Change-Id: I121af56b176bd3260904d367fd92d47a16bb3dcb
diff --git a/bpf.c b/bpf.c
index ce754a7..b618866 100644
--- a/bpf.c
+++ b/bpf.c
@@ -15,9 +15,9 @@
size_t bpf_validate_arch(struct sock_filter *filter)
{
struct sock_filter *curr_block = filter;
- set_bpf_stmt(curr_block++, BPF_LD+BPF_W+BPF_ABS, arch_nr);
- set_bpf_jump(curr_block++,
- BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, SKIP, NEXT);
+ set_bpf_stmt(curr_block++, BPF_LD + BPF_W + BPF_ABS, arch_nr);
+ set_bpf_jump(curr_block++, BPF_JMP + BPF_JEQ + BPF_K, ARCH_NR, SKIP,
+ NEXT);
set_bpf_ret_kill(curr_block++);
return curr_block - filter;
}
@@ -26,16 +26,16 @@
size_t bpf_allow_syscall(struct sock_filter *filter, int nr)
{
struct sock_filter *curr_block = filter;
- set_bpf_jump(curr_block++, BPF_JMP+BPF_JEQ+BPF_K, nr, NEXT, SKIP);
- set_bpf_stmt(curr_block++, BPF_RET+BPF_K, SECCOMP_RET_ALLOW);
+ set_bpf_jump(curr_block++, BPF_JMP + BPF_JEQ + BPF_K, nr, NEXT, SKIP);
+ set_bpf_stmt(curr_block++, BPF_RET + BPF_K, SECCOMP_RET_ALLOW);
return curr_block - filter;
}
-size_t bpf_allow_syscall_args(struct sock_filter *filter,
- int nr, unsigned int id)
+size_t bpf_allow_syscall_args(struct sock_filter *filter, int nr,
+ unsigned int id)
{
struct sock_filter *curr_block = filter;
- set_bpf_jump(curr_block++, BPF_JMP+BPF_JEQ+BPF_K, nr, NEXT, SKIP);
+ set_bpf_jump(curr_block++, BPF_JMP + BPF_JEQ + BPF_K, nr, NEXT, SKIP);
set_bpf_jump_lbl(curr_block++, id);
return curr_block - filter;
}
@@ -44,16 +44,16 @@
#if defined(BITS32)
size_t bpf_load_arg(struct sock_filter *filter, int argidx)
{
- set_bpf_stmt(filter, BPF_LD+BPF_W+BPF_ABS, LO_ARG(argidx));
+ set_bpf_stmt(filter, BPF_LD + BPF_W + BPF_ABS, LO_ARG(argidx));
return 1U;
}
#elif defined(BITS64)
size_t bpf_load_arg(struct sock_filter *filter, int argidx)
{
struct sock_filter *curr_block = filter;
- set_bpf_stmt(curr_block++, BPF_LD+BPF_W+BPF_ABS, LO_ARG(argidx));
+ set_bpf_stmt(curr_block++, BPF_LD + BPF_W + BPF_ABS, LO_ARG(argidx));
set_bpf_stmt(curr_block++, BPF_ST, 0); /* lo -> M[0] */
- set_bpf_stmt(curr_block++, BPF_LD+BPF_W+BPF_ABS, HI_ARG(argidx));
+ set_bpf_stmt(curr_block++, BPF_LD + BPF_W + BPF_ABS, HI_ARG(argidx));
set_bpf_stmt(curr_block++, BPF_ST, 1); /* hi -> M[1] */
return curr_block - filter;
}
@@ -61,10 +61,10 @@
/* Size-aware equality comparison. */
size_t bpf_comp_jeq32(struct sock_filter *filter, unsigned long c,
- unsigned char jt, unsigned char jf)
+ unsigned char jt, unsigned char jf)
{
unsigned int lo = (unsigned int)(c & 0xFFFFFFFF);
- set_bpf_jump(filter, BPF_JMP+BPF_JEQ+BPF_K, lo, jt, jf);
+ set_bpf_jump(filter, BPF_JMP + BPF_JEQ + BPF_K, lo, jt, jf);
return 1U;
}
@@ -73,8 +73,8 @@
* We jump true when *both* comparisons are true.
*/
#if defined(BITS64)
-size_t bpf_comp_jeq64(struct sock_filter *filter, uint64_t c,
- unsigned char jt, unsigned char jf)
+size_t bpf_comp_jeq64(struct sock_filter *filter, uint64_t c, unsigned char jt,
+ unsigned char jf)
{
unsigned int lo = (unsigned int)(c & 0xFFFFFFFF);
unsigned int hi = (unsigned int)(c >> 32);
@@ -83,7 +83,7 @@
/* bpf_load_arg leaves |hi| in A */
curr_block += bpf_comp_jeq32(curr_block, hi, NEXT, SKIPN(2) + jf);
- set_bpf_stmt(curr_block++, BPF_LD+BPF_MEM, 0); /* swap in |lo| */
+ set_bpf_stmt(curr_block++, BPF_LD + BPF_MEM, 0); /* swap in |lo| */
curr_block += bpf_comp_jeq32(curr_block, lo, jt, jf);
return curr_block - filter;
@@ -92,10 +92,10 @@
/* Size-aware bitwise AND. */
size_t bpf_comp_jset32(struct sock_filter *filter, unsigned long mask,
- unsigned char jt, unsigned char jf)
+ unsigned char jt, unsigned char jf)
{
unsigned int mask_lo = (unsigned int)(mask & 0xFFFFFFFF);
- set_bpf_jump(filter, BPF_JMP+BPF_JSET+BPF_K, mask_lo, jt, jf);
+ set_bpf_jump(filter, BPF_JMP + BPF_JSET + BPF_K, mask_lo, jt, jf);
return 1U;
}
@@ -105,7 +105,7 @@
*/
#if defined(BITS64)
size_t bpf_comp_jset64(struct sock_filter *filter, uint64_t mask,
- unsigned char jt, unsigned char jf)
+ unsigned char jt, unsigned char jf)
{
unsigned int mask_lo = (unsigned int)(mask & 0xFFFFFFFF);
unsigned int mask_hi = (unsigned int)(mask >> 32);
@@ -114,20 +114,32 @@
/* bpf_load_arg leaves |hi| in A */
curr_block += bpf_comp_jset32(curr_block, mask_hi, SKIPN(2) + jt, NEXT);
- set_bpf_stmt(curr_block++, BPF_LD+BPF_MEM, 0); /* swap in |lo| */
+ set_bpf_stmt(curr_block++, BPF_LD + BPF_MEM, 0); /* swap in |lo| */
curr_block += bpf_comp_jset32(curr_block, mask_lo, jt, jf);
return curr_block - filter;
}
#endif
-size_t bpf_arg_comp(struct sock_filter **pfilter,
- int op, int argidx, unsigned long c, unsigned int label_id)
+size_t bpf_comp_jin(struct sock_filter *filter, unsigned long mask,
+ unsigned char jt, unsigned char jf)
{
- struct sock_filter *filter = calloc(BPF_ARG_COMP_LEN + 1,
- sizeof(struct sock_filter));
+ unsigned long negative_mask = ~mask;
+ /*
+ * The mask is negated, so the comparison will be true when the argument
+ * includes a flag that wasn't listed in the original (non-negated)
+ * mask. This would be the failure case, so we switch |jt| and |jf|.
+ */
+ return bpf_comp_jset(filter, negative_mask, jf, jt);
+}
+
+size_t bpf_arg_comp(struct sock_filter **pfilter, int op, int argidx,
+ unsigned long c, unsigned int label_id)
+{
+ struct sock_filter *filter =
+ calloc(BPF_ARG_COMP_LEN + 1, sizeof(struct sock_filter));
struct sock_filter *curr_block = filter;
- size_t (*comp_function)(struct sock_filter *filter, unsigned long k,
+ size_t (*comp_function)(struct sock_filter * filter, unsigned long k,
unsigned char jt, unsigned char jf);
int flip = 0;
@@ -148,6 +160,10 @@
comp_function = bpf_comp_jset;
flip = 0;
break;
+ case IN:
+ comp_function = bpf_comp_jin;
+ flip = 0;
+ break;
default:
*pfilter = NULL;
return 0;
@@ -279,7 +295,7 @@
end = begin + labels->count;
for (; begin < end; ++begin) {
if (begin->label)
- free((void*)(begin->label));
+ free((void *)(begin->label));
}
labels->count = 0;
diff --git a/bpf.h b/bpf.h
index e4577fd..dd8c3f3 100644
--- a/bpf.h
+++ b/bpf.h
@@ -37,7 +37,8 @@
LE,
GT,
GE,
- SET
+ SET,
+ IN
};
/*
@@ -174,9 +175,11 @@
/* BPF helper functions. */
size_t bpf_load_arg(struct sock_filter *filter, int argidx);
size_t bpf_comp_jeq(struct sock_filter *filter, unsigned long c,
- unsigned char jt, unsigned char jf);
+ unsigned char jt, unsigned char jf);
size_t bpf_comp_jset(struct sock_filter *filter, unsigned long mask,
- unsigned char jt, unsigned char jf);
+ unsigned char jt, unsigned char jf);
+size_t bpf_comp_jin(struct sock_filter *filter, unsigned long mask,
+ unsigned char jt, unsigned char jf);
/* Functions called by syscall_filter.c */
#define ARCH_VALIDATION_LEN 3U
diff --git a/syscall_filter.c b/syscall_filter.c
index e42f017..34f6b3a 100644
--- a/syscall_filter.c
+++ b/syscall_filter.c
@@ -33,6 +33,8 @@
return NE;
} else if (!strcmp(op_str, "&")) {
return SET;
+ } else if (!strcmp(op_str, "in")) {
+ return IN;
} else {
return 0;
}
diff --git a/syscall_filter_unittest.c b/syscall_filter_unittest.c
index 541f98b..7862a5a 100644
--- a/syscall_filter_unittest.c
+++ b/syscall_filter_unittest.c
@@ -78,7 +78,8 @@
TEST_F(bpf, bpf_comp_jset) {
struct sock_filter comp_jset[BPF_COMP_LEN];
- unsigned long mask = O_WRONLY;
+ unsigned long mask =
+ (1UL << (sizeof(unsigned long) * 8 - 1)) | O_WRONLY;
unsigned char jt = 1;
unsigned char jf = 2;
@@ -91,10 +92,33 @@
BPF_JMP+BPF_JSET+BPF_K, mask, jt, jf);
#elif defined(BITS64)
EXPECT_EQ_BLOCK(&comp_jset[0],
- BPF_JMP+BPF_JSET+BPF_K, 0, jt + 2, 0);
+ BPF_JMP+BPF_JSET+BPF_K, 0x80000000, jt + 2, 0);
EXPECT_EQ_STMT(&comp_jset[1], BPF_LD+BPF_MEM, 0);
EXPECT_EQ_BLOCK(&comp_jset[2],
- BPF_JMP+BPF_JSET+BPF_K, mask, jt, jf);
+ BPF_JMP+BPF_JSET+BPF_K, O_WRONLY, jt, jf);
+#endif
+}
+
+TEST_F(bpf, bpf_comp_jin) {
+ struct sock_filter comp_jin[BPF_COMP_LEN];
+ unsigned long mask =
+ (1UL << (sizeof(unsigned long) * 8 - 1)) | O_WRONLY;
+ unsigned char jt = 10;
+ unsigned char jf = 20;
+
+ size_t len = bpf_comp_jin(comp_jin, mask, jt, jf);
+
+ EXPECT_EQ(len, BPF_COMP_LEN);
+
+#if defined(BITS32)
+ EXPECT_EQ_BLOCK(&comp_jin[0],
+ BPF_JMP+BPF_JSET+BPF_K, ~mask, jf, jt);
+#elif defined(BITS64)
+ EXPECT_EQ_BLOCK(&comp_jin[0],
+ BPF_JMP+BPF_JSET+BPF_K, 0x7FFFFFFF, jf + 2, 0);
+ EXPECT_EQ_STMT(&comp_jin[1], BPF_LD+BPF_MEM, 0);
+ EXPECT_EQ_BLOCK(&comp_jin[2],
+ BPF_JMP+BPF_JSET+BPF_K, ~O_WRONLY, jf, jt);
#endif
}
@@ -354,6 +378,48 @@
free_block_list(block);
}
+TEST_F(arg_filter, arg0_flag_set_inclusion) {
+ const char *fragment = "arg0 in O_RDONLY|O_CREAT";
+ int nr = 1;
+ unsigned int id = 0;
+ struct filter_block *block =
+ compile_section(nr, fragment, id, &self->labels, NO_LOGGING);
+
+ ASSERT_NE(block, NULL);
+ 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, NULL);
+ EXPECT_EQ(block->len, 1U);
+ EXPECT_LBL(curr_block->instrs);
+
+ /* Second block is a comparison. */
+ curr_block = block->next;
+ ASSERT_NE(curr_block, NULL);
+ 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, NULL);
+ EXPECT_GROUP_END(curr_block);
+
+ /* Fourth block is SECCOMP_RET_KILL. */
+ curr_block = curr_block->next;
+ ASSERT_NE(curr_block, NULL);
+ EXPECT_KILL(curr_block);
+
+ /* Fifth block is "SUCCESS" label and SECCOMP_RET_ALLOW. */
+ curr_block = curr_block->next;
+ ASSERT_NE(curr_block, NULL);
+ EXPECT_ALLOW(curr_block);
+
+ EXPECT_EQ(curr_block->next, NULL);
+
+ free_block_list(block);
+}
+
TEST_F(arg_filter, arg0_eq_mask) {
const char *fragment = "arg1 == O_WRONLY|O_CREAT";
int nr = 1;
diff --git a/syscall_filter_unittest.cpp b/syscall_filter_unittest.cpp
index ce2e019..403c10d 100644
--- a/syscall_filter_unittest.cpp
+++ b/syscall_filter_unittest.cpp
@@ -77,7 +77,7 @@
TEST(bpf, bpf_comp_jset) {
struct sock_filter comp_jset[BPF_COMP_LEN];
- unsigned long mask = O_WRONLY;
+ unsigned long mask = (1UL << (sizeof(unsigned long) * 8 - 1)) | O_WRONLY;
unsigned char jt = 1;
unsigned char jf = 2;
@@ -88,9 +88,30 @@
#if defined(BITS32)
EXPECT_EQ_BLOCK(&comp_jset[0], BPF_JMP + BPF_JSET + BPF_K, mask, jt, jf);
#elif defined(BITS64)
- EXPECT_EQ_BLOCK(&comp_jset[0], BPF_JMP + BPF_JSET + BPF_K, 0, jt + 2, 0);
+ EXPECT_EQ_BLOCK(
+ &comp_jset[0], BPF_JMP + BPF_JSET + BPF_K, 0x80000000, jt + 2, 0);
EXPECT_EQ_STMT(&comp_jset[1], BPF_LD + BPF_MEM, 0);
- EXPECT_EQ_BLOCK(&comp_jset[2], BPF_JMP + BPF_JSET + BPF_K, mask, jt, jf);
+ EXPECT_EQ_BLOCK(&comp_jset[2], BPF_JMP + BPF_JSET + BPF_K, O_WRONLY, jt, jf);
+#endif
+}
+
+TEST(bpf, bpf_comp_jin) {
+ struct sock_filter comp_jin[BPF_COMP_LEN];
+ unsigned long mask = (1UL << (sizeof(unsigned long) * 8 - 1)) | O_WRONLY;
+ unsigned char jt = 10;
+ unsigned char jf = 20;
+
+ size_t len = bpf_comp_jin(comp_jin, mask, jt, jf);
+
+ EXPECT_EQ(len, BPF_COMP_LEN);
+
+#if defined(BITS32)
+ EXPECT_EQ_BLOCK(&comp_jin[0], BPF_JMP + BPF_JSET + BPF_K, ~mask, jf, jt);
+#elif defined(BITS64)
+ EXPECT_EQ_BLOCK(
+ &comp_jin[0], BPF_JMP + BPF_JSET + BPF_K, 0x7FFFFFFF, jf + 2, 0);
+ EXPECT_EQ_STMT(&comp_jin[1], BPF_LD + BPF_MEM, 0);
+ EXPECT_EQ_BLOCK(&comp_jin[2], BPF_JMP + BPF_JSET + BPF_K, ~O_WRONLY, jf, jt);
#endif
}
@@ -336,6 +357,48 @@
free_block_list(block);
}
+TEST_F(ArgFilterTest, arg0_flag_set_inclusion) {
+ const char *fragment = "arg0 in O_RDONLY|O_CREAT";
+ int nr = 1;
+ unsigned int id = 0;
+ struct filter_block *block =
+ compile_section(nr, fragment, id, &labels_, NO_LOGGING);
+
+ 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(block->len, 1U);
+ EXPECT_LBL(curr_block->instrs);
+
+ /* Second block is a comparison. */
+ curr_block = block->next;
+ ASSERT_NE(curr_block, nullptr);
+ 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_KILL. */
+ curr_block = curr_block->next;
+ ASSERT_NE(curr_block, nullptr);
+ EXPECT_KILL(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_eq_mask) {
const char *fragment = "arg1 == O_WRONLY|O_CREAT";
int nr = 1;
diff --git a/util.c b/util.c
index b242c71..f0dc23d 100644
--- a/util.c
+++ b/util.c
@@ -108,7 +108,7 @@
* Try to parse constants separated by pipes. Note that since
* |constant_str| is an atom, there can be no spaces between the
* constant and the pipe. Constants can be either a named constant
- * defined in libconstants.gen.c or a number parsed with strtol.
+ * defined in libconstants.gen.c or a number parsed with strtol(3).
*
* If there is an error parsing any of the constants, the whole process
* fails.