Add "unconditional errno" support to syscall filter policies.

BUG=chromium:224082
TEST=syscall_filter_unittest

Change-Id: Ic83ecb72af8b62f297f1b6a3dc49eb3219029715
Reviewed-on: https://gerrit.chromium.org/gerrit/46568
Tested-by: Jorge Lucangeli Obes <jorgelo@chromium.org>
Reviewed-by: Kees Cook <keescook@chromium.org>
Commit-Queue: Jorge Lucangeli Obes <jorgelo@chromium.org>
diff --git a/syscall_filter.c b/syscall_filter.c
index e10c7bc..9d4ad58 100644
--- a/syscall_filter.c
+++ b/syscall_filter.c
@@ -39,6 +39,18 @@
 	return buf;
 }
 
+struct filter_block *new_filter_block()
+{
+	struct filter_block *block = calloc(1, sizeof(struct filter_block));
+	if (!block)
+		die("could not allocate BPF filter block");
+
+	block->instrs = NULL;
+	block->last = block->next = NULL;
+
+	return block;
+}
+
 void append_filter_block(struct filter_block *head,
 		struct sock_filter *instrs, size_t len)
 {
@@ -51,10 +63,7 @@
 	if (head->instrs == NULL) {
 		new_last = head;
 	} else {
-		new_last = calloc(1, sizeof(struct filter_block));
-		if (!new_last)
-			die("could not allocate BPF filter block");
-
+		new_last = new_filter_block();
 		if (head->next != NULL) {
 			head->last->next = new_last;
 			head->last = new_last;
@@ -231,7 +240,7 @@
 	 * a disjunction ('||') of one or more conjunctions ('&&')
 	 * of one or more atoms.
 	 *
-	 * Atoms are of the form "arg{DNUM} OP NUM"
+	 * Atoms are of the form "arg{DNUM} {OP} {NUM}"
 	 * where:
 	 *   - DNUM is a decimal number.
 	 *   - OP is an operator: ==, !=, or & (flags set).
@@ -240,13 +249,19 @@
 	 * When the syscall arguments make the expression true,
 	 * the syscall is allowed. If not, the process is killed.
 	 *
-	 * To avoid killing the process, a policy line can include an
-	 * optional "return <errno>" clause:
+	 * To block a syscall without killing the process,
+	 * |policy_line| can be of the form:
+	 * "return <errno>"
 	 *
+	 * This "return {NUM}" policy line will block the syscall,
+	 * make it return -1 and set |errno| to NUM.
+	 *
+	 * A regular policy line can also include a "return <errno>" clause,
+	 * separated by a semicolon (';'):
 	 * "arg0 == 3 && arg1 == 5 || arg0 == 0x8; return {NUM}"
 	 *
-	 * In this case, the syscall will return -1 and |errno| will
-	 * be set to NUM.
+	 * If the syscall arguments don't make the expression true,
+	 * the syscall will be blocked as above instead of killing the process.
 	 */
 
 	size_t len = 0;
@@ -256,35 +271,38 @@
 	if (strlen(policy_line) >= MAX_POLICY_LINE_LENGTH)
 		return NULL;
 
-	/* strtok() modifies its first argument, so let's make a copy. */
+	/* We will modify |policy_line|, so let's make a copy. */
 	char *line = strndup(policy_line, MAX_POLICY_LINE_LENGTH);
 	if (!line)
 		return NULL;
 
-	/* Splits the optional "return <errno>" part. */
-	char *line_ptr;
-	char *arg_filter = strtok_r(line, ";", &line_ptr);
-	char *ret_errno = strtok_r(NULL, ";", &line_ptr);
-
 	/*
-	 * We build the argument filter as a collection of smaller
+	 * We build the filter section as a collection of smaller
 	 * "filter blocks" linked together in a singly-linked list.
 	 */
-	struct filter_block *head = calloc(1, sizeof(struct filter_block));
-	if (!head)
-		return NULL;
-
-	head->instrs = NULL;
-	head->last = head->next = NULL;
+	struct filter_block *head = new_filter_block();
 
 	/*
-	 * Argument filters begin with a label where the main filter
+	 * Filter sections begin with a label where the main filter
 	 * will jump after checking the syscall number.
 	 */
 	struct sock_filter *entry_label = new_instr_buf(ONE_INSTR);
 	set_bpf_lbl(entry_label, entry_lbl_id);
 	append_filter_block(head, entry_label, ONE_INSTR);
 
+	/* Checks whether we're unconditionally blocking this syscall. */
+	if (strncmp(line, "return", strlen("return")) == 0) {
+		if (compile_errno(head, line) < 0)
+			return NULL;
+		free(line);
+		return head;
+	}
+
+	/* Splits the optional "return <errno>" part. */
+	char *line_ptr;
+	char *arg_filter = strtok_r(line, ";", &line_ptr);
+	char *ret_errno = strtok_r(NULL, ";", &line_ptr);
+
 	/*
 	 * Splits the policy line by '||' into conjunctions and each conjunction
 	 * by '&&' into atoms.
@@ -354,11 +372,7 @@
 	if (!policy_file)
 		return -1;
 
-	struct filter_block *head = calloc(1, sizeof(struct filter_block));
-	if (!head)
-		return -1;
-	head->instrs = NULL;
-	head->last = head->next = NULL;
+	struct filter_block *head = new_filter_block();
 	struct filter_block *arg_blocks = NULL;
 
 	/* Start filter by validating arch. */
diff --git a/syscall_filter_unittest.c b/syscall_filter_unittest.c
index a4911c8..68551f3 100644
--- a/syscall_filter_unittest.c
+++ b/syscall_filter_unittest.c
@@ -445,6 +445,37 @@
 	free_label_strings(&self->labels);
 }
 
+TEST_F(arg_filter, unconditional_errno) {
+	const char *fragment = "return 1";
+	int nr = 1;
+	unsigned int id = 0;
+
+	struct filter_block *block =
+		compile_section(nr, fragment, id, &self->labels);
+	ASSERT_NE(block, NULL);
+	size_t exp_total_len = 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 SECCOMP_RET_ERRNO */
+	curr_block = curr_block->next;
+	EXPECT_NE(curr_block, NULL);
+	EXPECT_EQ(curr_block->len, 1U);
+	EXPECT_EQ_STMT(curr_block->instrs,
+			BPF_RET+BPF_K,
+			SECCOMP_RET_ERRNO | (1 & SECCOMP_RET_DATA));
+
+	EXPECT_EQ(curr_block->next, NULL);
+
+	free_block_list(block);
+	free_label_strings(&self->labels);
+}
+
 TEST_F(arg_filter, invalid) {
 	const char *fragment = "argnn == 0";
 	int nr = 1;