aboutgitcodelistschat:MatrixIRC
diff options
context:
space:
mode:
authorStefano Brivio <sbrivio@redhat.com>2023-06-14 07:25:52 +0200
committerStefano Brivio <sbrivio@redhat.com>2023-06-14 07:25:52 +0200
commit00614c5e6702db8ac3f18a9e193c7a8382f16e6b (patch)
treeb8275d1060e994e18d7fbfead0244629752cb555
parent8bc937c1442d212926dadb6227b759966bc13925 (diff)
downloadseitan-00614c5e6702db8ac3f18a9e193c7a8382f16e6b.tar
seitan-00614c5e6702db8ac3f18a9e193c7a8382f16e6b.tar.gz
seitan-00614c5e6702db8ac3f18a9e193c7a8382f16e6b.tar.bz2
seitan-00614c5e6702db8ac3f18a9e193c7a8382f16e6b.tar.lz
seitan-00614c5e6702db8ac3f18a9e193c7a8382f16e6b.tar.xz
seitan-00614c5e6702db8ac3f18a9e193c7a8382f16e6b.tar.zst
seitan-00614c5e6702db8ac3f18a9e193c7a8382f16e6b.zip
cooker: Support for read(), OP_STORE, field-based filters
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
-rw-r--r--common/gluten.h8
-rw-r--r--cooker/Makefile4
-rw-r--r--cooker/call.c11
-rw-r--r--cooker/calls.c5
-rw-r--r--cooker/emit.c24
-rw-r--r--cooker/emit.h2
-rw-r--r--cooker/example.hjson42
-rw-r--r--cooker/filter.c432
-rw-r--r--cooker/match.c5
-rw-r--r--cooker/parse.c41
-rw-r--r--demo/socket.hjson2
-rw-r--r--operations.c40
-rw-r--r--seitan.c1
13 files changed, 386 insertions, 231 deletions
diff --git a/common/gluten.h b/common/gluten.h
index 91bc85e..57b94f4 100644
--- a/common/gluten.h
+++ b/common/gluten.h
@@ -68,6 +68,7 @@ enum op_type {
OP_FD,
OP_RETURN,
OP_LOAD,
+ OP_STORE,
OP_BITWISE,
OP_CMP,
OP_RESOLVEDFD,
@@ -185,6 +186,12 @@ struct op_load {
size_t size;
};
+struct op_store {
+ struct gluten_offset src;
+ struct gluten_offset dst;
+ struct gluten_offset count;
+};
+
enum op_cmp_type {
CMP_EQ,
CMP_NE,
@@ -250,6 +257,7 @@ struct op {
struct op_return ret;
struct op_fd fd;
struct op_load load;
+ struct op_store store;
struct op_bitwise bitwise;
struct op_cmp cmp;
struct op_resolvefd resfd;
diff --git a/cooker/Makefile b/cooker/Makefile
index 64342dc..31b52c9 100644
--- a/cooker/Makefile
+++ b/cooker/Makefile
@@ -32,11 +32,11 @@ CFLAGS += -DSEITAN_AUDIT_ARCH=AUDIT_ARCH_$(AUDIT_ARCH)
SRCS := call.c calls.c emit.c gluten.c filter.c main.c match.c \
parse.c parson.c \
$(COMMON)/util.c \
- calls/net.c calls/ioctl.c calls/process.c calls/fs.c
+ calls/net.c calls/ioctl.c calls/process.c calls/fs.c calls/io.c
HEADERS := call.h calls.h cooker.h emit.h filter.h gluten.h match.h \
parse.h parson.h \
$(COMMON)/gluten.h $(COMMON)/util.h \
- calls/net.h calls/ioctl.h calls/process.h calls/fs.h
+ calls/net.h calls/ioctl.h calls/process.h calls/fs.h calls/io.h
$(BIN): $(SRCS) $(HEADERS)
$(CC) $(CFLAGS) -o $(BIN) $(SRCS)
diff --git a/cooker/call.c b/cooker/call.c
index 4e9cb9c..c3f290c 100644
--- a/cooker/call.c
+++ b/cooker/call.c
@@ -100,6 +100,9 @@ static union value parse_field(struct gluten_ctx *g, struct arg *args,
if (!dry_run)
gluten_add_tag_post(g, tag_set, offset);
+
+ if (f->flags & RBUF)
+ return v;
}
if ((tag_get = json_object_get_string(tmp2, "get"))) {
@@ -199,13 +202,13 @@ static union value parse_field(struct gluten_ctx *g, struct arg *args,
}
break;
case STRING:
+ if (dry_run)
+ break;
+
v.v_str = json_value_get_string(jvalue);
if (strlen(v.v_str) + 1 > f->size)
die(" string %s too long for field", v.v_str);
- if (dry_run)
- break;
-
emit_data_at(g, offset, f->type, &v);
break;
case STRUCT:
@@ -327,7 +330,7 @@ static struct gluten_offset parse_arg(struct gluten_ctx *g, struct arg *args,
}
if (arg_needs_temp(&a->f, a->pos, jvalue, &top_level_tag, 0) ||
- multi_field) {
+ multi_field || (a->f.flags & RBUF)) {
if (a->f.size)
offset = gluten_rw_alloc(g, a->f.size);
else
diff --git a/cooker/calls.c b/cooker/calls.c
index 337ea21..f486777 100644
--- a/cooker/calls.c
+++ b/cooker/calls.c
@@ -15,7 +15,10 @@
#include "calls/ioctl.h"
#include "calls/process.h"
#include "calls/fs.h"
+#include "calls/io.h"
struct call *call_sets[] = {
- syscalls_net, syscalls_ioctl, syscalls_process, syscalls_fs, NULL,
+ syscalls_net, syscalls_ioctl, syscalls_process, syscalls_fs,
+ syscalls_io,
+ NULL,
};
diff --git a/cooker/emit.c b/cooker/emit.c
index 57533a1..a257398 100644
--- a/cooker/emit.c
+++ b/cooker/emit.c
@@ -164,6 +164,24 @@ void emit_load(struct gluten_ctx *g, struct gluten_offset dst,
die("Too many instructions");
}
+void emit_store(struct gluten_ctx *g, struct gluten_offset dst,
+ struct gluten_offset src, struct gluten_offset count)
+{
+ struct op *op = (struct op *)gluten_ptr(&g->g, g->ip);
+ struct op_store *store = &op->op.store;
+
+ op->type = OP_STORE;
+
+ store->dst = dst;
+ store->src = src;
+ store->count = count;
+
+ debug(" %i: OP_STORE: #%i", g->ip.offset);
+
+ if (++g->ip.offset > INST_MAX)
+ die("Too many instructions");
+}
+
/**
* emit_resolved() - Emit OP_RESOLVEFD instruction: resolve file descriptor with path
* @g: gluten context
@@ -540,7 +558,7 @@ static void gluten_link(struct gluten_ctx *g, enum jump_type type,
void emit_bpf_arg(int index, enum type type, union value v, union value mask,
enum op_cmp_type cmp)
{
- struct bpf_arg bpf;
+ struct bpf_field bpf;
/* gluten uses the comparison to skip to the next match, the BPF filter
* uses it to notify instead.
@@ -550,6 +568,8 @@ void emit_bpf_arg(int index, enum type type, union value v, union value mask,
else
bpf.cmp = (cmp == CMP_EQ) ? NE : EQ;
+ bpf.arg = index;
+
if (TYPE_IS_64BIT(type)) {
bpf.value.v64 = v.v_num;
bpf.op2.v64 = mask.v_num;
@@ -560,7 +580,7 @@ void emit_bpf_arg(int index, enum type type, union value v, union value mask,
bpf.type = BPF_U32;
}
- filter_add_arg(index, bpf);
+ filter_add_check(&bpf);
}
void link_block(struct gluten_ctx *g)
diff --git a/cooker/emit.h b/cooker/emit.h
index 1cdadb9..b4f1ae7 100644
--- a/cooker/emit.h
+++ b/cooker/emit.h
@@ -13,6 +13,8 @@ void emit_call(struct gluten_ctx *g, struct context_desc *cdesc, long nr,
struct gluten_offset offset[6], struct gluten_offset ret_offset);
void emit_load(struct gluten_ctx *g, struct gluten_offset dst,
int index, size_t len);
+void emit_store(struct gluten_ctx *g, struct gluten_offset dst,
+ struct gluten_offset src, struct gluten_offset count);
struct gluten_offset emit_seccomp_data(int index);
struct gluten_offset emit_bitwise(struct gluten_ctx *g, enum type type,
enum bitwise_type op_type,
diff --git a/cooker/example.hjson b/cooker/example.hjson
index 458961c..c3dc657 100644
--- a/cooker/example.hjson
+++ b/cooker/example.hjson
@@ -20,26 +20,46 @@
},
{
"match": [ /* CVE-2022-0185-style */
- { "unshare": { "flags": { "has": { "newuser": true, "newnet": false } } } }
+ { "unshare": { "flags": "CLONE_NEWUSER" } }
],
- "block": { }
+ "return": { "value": 0, "error": -1 }
},
{
"match": [ /* passt */
- { "unshare": { "flags": { "has": [ "ipc", "mount", "uts", "pid" ] } } }
+ { "unshare": { "flags": { "all": [ "CLONE_NEWIPC", "CLONE_NEWNS", "CLONE_NEWUTS", "CLONE_NEWPID" ] } } }
],
- "block": { }
+ "return": { "value": 0, "error": 0 }
},
{
"match": [ /* Giuseppe's example */
- { "mknodat": { "path": { "tag": "path" }, "mode": "c", "major": 1, "minor": { "value": { "in": [ 3, 5, 7, 8, 9 ] }, "tag": "minor" } } }
+ { "mknodat":
+ { "path": { "tag": "path" },
+ "mode": { "tag": "mode" },
+ "type": { "tag": "type" },
+ "major": 1,
+ "minor": { "value": { "in": [ 3, 5, 7, 8, 9 ] }, "tag": "minor" }
+ }
+ },
+ { "mknod":
+ { "path": { "tag": "path" },
+ "mode": { "tag": "mode" },
+ "type": { "tag": "type" },
+ "major": 1,
+ "minor": { "value": { "in": [ 3, 5, 7, 8, 9 ] }, "tag": "minor" }
+ }
+ }
],
- "call": {
- "mknod": { "path": { "tag": { "get": "path" } }, "mode": "c", "major": 1, "minor": { "tag": { "get": "minor" } } },
- "ret": "x",
- "context": { "user": "init", "mnt": "caller" }
- },
- "return": { "tag": "x" }
+ "call":
+ { "mknod":
+ { "path": { "tag": { "get": "path" } },
+ "mode": { "tag": { "get": "mode" } },
+ "type": { "tag": { "get": "type" } },
+ "major": 1,
+ "minor": { "tag": { "get": "minor" } }
+ },
+ "context": { "mnt": "caller" }
+ },
+ "return": { "value": 0 }
}
]
diff --git a/cooker/filter.c b/cooker/filter.c
index 7f1f419..bffdfe4 100644
--- a/cooker/filter.c
+++ b/cooker/filter.c
@@ -17,98 +17,195 @@
#include "filter.h"
-struct bpf_entry entries[MAX_ENTRIES];
-static unsigned int index_entries = 0;
+#define N_SYSCALL 512
+#define MAX_ENTRIES_PER_SYSCALL 16
+#define MAX_FIELDS_PER_SYSCALL 16
const char *bpf_cmp_str[] = { "no check", "==", "!=", "<=", "<", ">=", ">",
"==", "!=" };
+struct bpf_entry {
+ struct bpf_field field[MAX_FIELDS_PER_SYSCALL];
+};
+
/**
* struct filter_call_input - First input stage for cooker notification requests
- * @notify: Notify on this system call
- * @count: How many entry for the same syscall
- * @entries: Index for the arguments for every entry
+ * @notify: Notify on this syscall
+ * @ignore_args: Don't filter on arguments for this syscall
+ * @entry: syscall notification entry with field checks
*/
struct filter_call_input {
- bool ignore_args;
bool notify;
- unsigned int count;
- int entries[MAX_ENTRIES_SYSCALL];
+ bool ignore_args;
+ struct bpf_entry entry[MAX_ENTRIES_PER_SYSCALL];
} filter_input[N_SYSCALL] = { 0 };
static long current_nr;
-static unsigned int get_number_entries(long nr)
+/**
+ * entry_has_check() - Input stage: does the syscall entry need argument checks?
+ * @entry: syscall entry with field checks
+ *
+ * Return: true if at least one argument comparison is requested
+ */
+static bool entry_has_check(const struct bpf_entry *entry)
{
- struct filter_call_input *call = filter_input + nr;
- return call->count;
-}
+ unsigned i;
-static bool need_check_arg(const struct bpf_entry *entry)
-{
- for (int i = 0; i < 6; i++)
- if (entry->args[i].cmp != NO_CHECK)
+ for (i = 0; i < MAX_FIELDS_PER_SYSCALL; i++) {
+ if (entry->field[i].cmp != NO_CHECK)
return true;
+ }
+
return false;
}
-static bool has_args(long nr)
+/**
+ * call_entry_count() - Input stage: count of entries for the same syscall
+ * @nr: syscall number
+ *
+ * Return: count of entries for the same syscall
+ */
+static unsigned int call_entry_count(long nr)
{
struct filter_call_input *call = filter_input + nr;
+ unsigned i, count = 0;
- if (call->count < 1)
- return false;
- if (call->ignore_args)
- return false;
+ if (!call->notify || call->ignore_args)
+ return 0;
- /* Check if the first entry has some arguments */
- return need_check_arg(&entries[call->entries[0]]);
+ for (i = 0; i < MAX_ENTRIES_PER_SYSCALL; i++) {
+ if (entry_has_check(&call->entry[i]))
+ count++;
+ else
+ break;
+ }
+
+ return count;
}
-static unsigned get_args_for_entry(const struct bpf_entry *entry)
+/**
+ * entry_check_count() - Input stage: count of field checks for entry
+ * @entry: syscall entry with field checks
+ *
+ * Return: count of argument checks
+ */
+static unsigned entry_check_count(const struct bpf_entry *entry)
{
unsigned i, n = 0;
- for (i = 0; i < 6; i++)
- if (entry->args[i].cmp != NO_CHECK)
+ for (i = 0; i < MAX_FIELDS_PER_SYSCALL; i++)
+ if (entry->field[i].cmp != NO_CHECK)
n++;
+
return n;
}
+/**
+ * filter_notify() - Start of notification request
+ * @nr: syscall number
+ */
+void filter_notify(long nr)
+{
+ struct filter_call_input *call = filter_input + nr;
+
+ debug(" BPF: start filter information for #%lu", nr);
+ current_nr = nr;
+ call->notify = true;
+}
+
+/**
+ * filter_add_check() - Add a new field check to the current syscall
+ * @field: Field check specification
+ */
+void filter_add_check(struct bpf_field *field)
+{
+ struct filter_call_input *call = filter_input + current_nr;
+ struct bpf_entry *entry;
+ struct bpf_field *check;
+ char buf[BUFSIZ];
+ unsigned n;
+
+ n = snprintf(buf, BUFSIZ, " BPF: adding #%i %s %lu",
+ field->arg, bpf_cmp_str[field->cmp],
+ (field->type == BPF_U32) ? field->value.v32 :
+ field->value.v64);
+
+ if (field->cmp == AND_EQ || field->cmp == AND_NE) {
+ snprintf(buf + n, BUFSIZ - n, " & %lu",
+ (field->type == BPF_U32) ? field->op2.v32 :
+ field->op2.v64);
+ }
+
+ debug("%s", buf);
+
+ /* Too many entries or checks: ignore argument checks from now on */
+ if (call_entry_count(current_nr) > MAX_ENTRIES_PER_SYSCALL)
+ call->ignore_args = true;
+
+ entry = &call->entry[call_entry_count(current_nr)];
+ if (entry_check_count(entry) > MAX_FIELDS_PER_SYSCALL)
+ call->ignore_args = true;
+
+ if (call->ignore_args) {
+ debug(" BPF: ignoring fields for syscall #%lu", current_nr);
+ return;
+ }
+
+ check = &entry->field[entry_check_count(entry)];
+
+ debug(" BPF: inserting check at %i for entry %i, syscall %lu",
+ entry_check_count(entry), call_entry_count(current_nr),
+ current_nr);
+
+ memcpy(check, field, sizeof(*field));
+}
+
+void filter_needs_deref(void)
+{
+ struct filter_call_input *call = filter_input + current_nr;
+
+ debug(" BPF: arguments for #%lu now ignored", current_nr);
+
+ call->ignore_args = true;
+}
+
/* Calculate how many instruction for the syscall */
static unsigned int get_n_args_syscall_instr(long nr)
{
struct filter_call_input *call = filter_input + nr;
- const struct bpf_entry *entry;
unsigned int n = 0, total_instr = 0;
- unsigned int i, k;
+ unsigned int i, j;
- for (i = 0; i < call->count; i++) {
- entry = &entries[call->entries[i]];
+ for (i = 0; i < call_entry_count(nr); i++) {
+ const struct bpf_entry *entry = &call->entry[i];
n = 0;
- for (k = 0; k < 6; k++) {
- if (entry->args[k].cmp == NO_CHECK)
+
+ for (j = 0; j < entry_check_count(entry); j++) {
+ const struct bpf_field *field = &entry->field[j];
+ enum bpf_cmp cmp = field->cmp;
+
+ if (cmp == NO_CHECK)
continue;
- switch (entry->args[k].type) {
+
+ switch (field->type) {
case BPF_U32:
- /* For 32 bit arguments
+ /* For 32 bit fields
* comparison instructions (2):
* 1 loading the value + 1 for evaluation
* arithemtic instructions (3):
* 1 loading the value + 1 for the operation + 1 for evaluation
*/
- if (entry->args[k].cmp == AND_EQ ||
- entry->args[k].cmp == AND_NE)
+ if (cmp == AND_EQ || cmp == AND_NE)
n += 3;
else
n += 2;
break;
case BPF_U64:
- /* For 64 bit arguments: 32 instructions * 2
+ /* For 64 bit fields: 32 instructions * 2
* for loading and evaluating the high and low 32 bits chuncks.
*/
- if (entry->args[k].cmp == AND_EQ ||
- entry->args[k].cmp == AND_NE)
+ if (cmp == AND_EQ || cmp == AND_NE)
n += 6;
else
n += 4;
@@ -116,87 +213,19 @@ static unsigned int get_n_args_syscall_instr(long nr)
}
}
total_instr += n;
- /* If there at least an argument, then there is the jump to the
+ /* TODO: rewrite comment: If there at least an argument, then there is the jump to the
* notification */
if (n > 0)
total_instr++;
}
- /* If there at least an argument for that syscall, then there is the jump to the
+ /* TODO: rewrite comment: If there at least an argument for that syscall, then there is the jump to the
* accept */
- if (has_args(nr))
+ if (call_entry_count(nr))
total_instr++;
- return total_instr;
-}
-
-/**
- * filter_notify() - Start of notification request, check/flush previous one
- * @nr: System call number, -1 to just flush previous request
- */
-void filter_notify(long nr)
-{
- struct filter_call_input *call = filter_input + nr;
-
- if (nr > 0) {
- debug(" BPF: start filter information for #%lu", nr);
- current_nr = nr;
- call->notify = true;
- }
-}
-
-/**
- * filter_add_arg(): Add a new argument to the current syscall
- * @index: position of the argument
- * @arg: the argument to add
- */
-void filter_add_arg(int index, struct bpf_arg arg)
-{
- struct filter_call_input *call = filter_input + current_nr;
- struct bpf_entry *entry = &entries[index_entries];
- char buf[BUFSIZ];
- unsigned n;
-
- n = snprintf(buf, BUFSIZ, " BPF: adding #%i %s %lu",
- index, bpf_cmp_str[arg.cmp],
- (arg.type == BPF_U32) ? arg.value.v32 : arg.value.v64);
- if (arg.cmp == AND_EQ || arg.cmp == AND_NE) {
- snprintf(buf + n, BUFSIZ - n, " & %lu",
- (arg.type == BPF_U32) ? arg.op2.v32 : arg.op2.v64);
- }
- debug("%s", buf);
-
- /* If it reaches the maximum number of entries per syscall, then we simply
- * notify for all the arguments and ignore the other arguments.
- */
- if (call->count >= MAX_ENTRIES_SYSCALL) {
- call->ignore_args = true;
- return;
- }
- if (call->ignore_args)
- return;
- call->entries[call->count] = index_entries;
- memcpy(&entry->args[index], &arg, sizeof(arg));
-}
-
-void filter_flush_args()
-{
- struct filter_call_input *call = filter_input + current_nr;
- debug(" BPF: flush filter information for #%lu", current_nr);
-
- if (!has_args(current_nr))
- return;
- call->count++;
- index_entries++;
-}
-
-void filter_needs_deref(void)
-{
- struct filter_call_input *call = filter_input + current_nr;
-
- debug(" BPF: arguments for #%lu now ignored", current_nr);
+ debug(" BPF: counted %i instructions for syscall %lu", total_instr, nr);
- call->ignore_args = true;
- call->count = 0;
+ return total_instr;
}
static int table[N_SYSCALL];
@@ -284,161 +313,161 @@ static void create_lookup_nodes(int jumps[], unsigned int n)
}
}
-static unsigned int eq(struct sock_filter filter[], int idx,
- const struct bpf_entry *entry, unsigned int jtrue,
+static unsigned int eq(struct sock_filter filter[],
+ const struct bpf_field *field, unsigned int jtrue,
unsigned int jfalse)
{
unsigned int size = 0;
uint32_t hi, lo;
- switch (entry->args[idx].type) {
+ switch (field->type) {
case BPF_U64:
- hi = get_hi((entry->args[idx]).value.v64);
- lo = get_lo((entry->args[idx]).value.v64);
- filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx));
+ hi = get_hi(field->value.v64);
+ lo = get_lo(field->value.v64);
+ filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg));
filter[size++] = (struct sock_filter)EQ(lo, 0, jfalse);
- filter[size++] = (struct sock_filter)LOAD(HI_ARG(idx));
+ filter[size++] = (struct sock_filter)LOAD(HI_ARG(field->arg));
filter[size++] = (struct sock_filter)EQ(hi, jtrue, jfalse);
break;
case BPF_U32:
- filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx));
+ filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg));
filter[size++] = (struct sock_filter)EQ(
- entry->args[idx].value.v32, jtrue, jfalse);
+ field->value.v32, jtrue, jfalse);
break;
}
return size;
}
-static unsigned int gt(struct sock_filter filter[], int idx,
- const struct bpf_entry *entry, unsigned int jtrue,
+static unsigned int gt(struct sock_filter filter[],
+ const struct bpf_field *field, unsigned int jtrue,
unsigned int jfalse)
{
unsigned int size = 0;
uint32_t hi, lo;
- switch (entry->args[idx].type) {
+ switch (field->type) {
case BPF_U64:
- hi = get_hi((entry->args[idx]).value.v64);
- lo = get_lo((entry->args[idx]).value.v64);
- filter[size++] = (struct sock_filter)LOAD(HI_ARG(idx));
+ hi = get_hi(field->value.v64);
+ lo = get_lo(field->value.v64);
+ filter[size++] = (struct sock_filter)LOAD(HI_ARG(field->arg));
filter[size++] = (struct sock_filter)GT(hi, jtrue + 2, 0);
- filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx));
+ filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg));
filter[size++] = (struct sock_filter)GT(lo, jtrue, jfalse);
break;
case BPF_U32:
- filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx));
+ filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg));
filter[size++] = (struct sock_filter)GT(
- entry->args[idx].value.v32, jtrue, jfalse);
+ field->value.v32, jtrue, jfalse);
break;
}
return size;
}
-static unsigned int lt(struct sock_filter filter[], int idx,
- const struct bpf_entry *entry, unsigned int jtrue,
+static unsigned int lt(struct sock_filter filter[],
+ const struct bpf_field *field, unsigned int jtrue,
unsigned int jfalse)
{
unsigned int size = 0;
uint32_t hi, lo;
- switch (entry->args[idx].type) {
+ switch (field->type) {
case BPF_U64:
- hi = get_hi((entry->args[idx]).value.v64);
- lo = get_lo((entry->args[idx]).value.v64);
- filter[size++] = (struct sock_filter)LOAD(HI_ARG(idx));
+ hi = get_hi(field->value.v64);
+ lo = get_lo(field->value.v64);
+ filter[size++] = (struct sock_filter)LOAD(HI_ARG(field->arg));
filter[size++] = (struct sock_filter)LT(hi, jtrue + 2, jfalse);
- filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx));
+ filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg));
filter[size++] = (struct sock_filter)LT(lo, jtrue, jfalse);
break;
case BPF_U32:
- filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx));
+ filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg));
filter[size++] = (struct sock_filter)LT(
- entry->args[idx].value.v32, jtrue, jfalse);
+ field->value.v32, jtrue, jfalse);
break;
}
return size;
}
-static unsigned int neq(struct sock_filter filter[], int idx,
- const struct bpf_entry *entry, unsigned int jtrue,
+static unsigned int neq(struct sock_filter filter[],
+ const struct bpf_field *field, unsigned int jtrue,
unsigned int jfalse)
{
- return eq(filter, idx, entry, jfalse, jtrue);
+ return eq(filter, field, jfalse, jtrue);
}
-static unsigned int ge(struct sock_filter filter[], int idx,
- const struct bpf_entry *entry, unsigned int jtrue,
+static unsigned int ge(struct sock_filter filter[],
+ const struct bpf_field *field, unsigned int jtrue,
unsigned int jfalse)
{
- return lt(filter, idx, entry, jfalse, jtrue);
+ return lt(filter, field, jfalse, jtrue);
}
-static unsigned int le(struct sock_filter filter[], int idx,
- const struct bpf_entry *entry, unsigned int jtrue,
+static unsigned int le(struct sock_filter filter[],
+ const struct bpf_field *field, unsigned int jtrue,
unsigned int jfalse)
{
- return gt(filter, idx, entry, jfalse, jtrue);
+ return gt(filter, field, jfalse, jtrue);
}
-static unsigned int and_eq (struct sock_filter filter[], int idx,
- const struct bpf_entry *entry, unsigned int jtrue,
- unsigned int jfalse)
+static unsigned int and_eq(struct sock_filter filter[],
+ const struct bpf_field *field, unsigned int jtrue,
+ unsigned int jfalse)
{
unsigned int size = 0;
- switch (entry->args[idx].type) {
+ switch (field->type) {
case BPF_U64:
- filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx));
+ filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg));
filter[size++] = (struct sock_filter)AND(
- get_lo(entry->args[idx].op2.v64));
+ get_lo(field->op2.v64));
filter[size++] = (struct sock_filter)EQ(
- get_lo((entry->args[idx]).value.v64), 0, jfalse);
- filter[size++] = (struct sock_filter)LOAD(HI_ARG(idx));
+ get_lo(field->value.v64), 0, jfalse);
+ filter[size++] = (struct sock_filter)LOAD(HI_ARG(field->arg));
filter[size++] = (struct sock_filter)AND(
- get_hi(entry->args[idx].op2.v64));
+ get_hi(field->op2.v64));
filter[size++] = (struct sock_filter)EQ(
- get_hi(entry->args[idx].value.v64), jtrue, jfalse);
+ get_hi(field->value.v64), jtrue, jfalse);
break;
case BPF_U32:
- filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx));
+ filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg));
filter[size++] =
- (struct sock_filter)AND(entry->args[idx].op2.v32);
+ (struct sock_filter)AND(field->op2.v32);
filter[size++] = (struct sock_filter)EQ(
- entry->args[idx].value.v32, jtrue, jfalse);
+ field->value.v32, jtrue, jfalse);
break;
}
return size;
}
-static unsigned int and_ne(struct sock_filter filter[], int idx,
- const struct bpf_entry *entry, unsigned int jtrue,
+static unsigned int and_ne(struct sock_filter filter[],
+ const struct bpf_field *field, unsigned int jtrue,
unsigned int jfalse)
{
unsigned int size = 0;
- switch (entry->args[idx].type) {
+ switch (field->type) {
case BPF_U64:
- filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx));
+ filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg));
filter[size++] = (struct sock_filter)AND(
- get_lo(entry->args[idx].op2.v64));
+ get_lo(field->op2.v64));
filter[size++] = (struct sock_filter)EQ(
- get_lo((entry->args[idx]).value.v64), 0, jtrue + 3);
- filter[size++] = (struct sock_filter)LOAD(HI_ARG(idx));
+ get_lo(field->value.v64), 0, jtrue + 3);
+ filter[size++] = (struct sock_filter)LOAD(HI_ARG(field->arg));
filter[size++] = (struct sock_filter)AND(
- get_hi(entry->args[idx].op2.v64));
+ get_hi(field->op2.v64));
filter[size++] = (struct sock_filter)EQ(
- get_hi(entry->args[idx].value.v64), jfalse, jtrue);
+ get_hi(field->value.v64), jfalse, jtrue);
break;
case BPF_U32:
- filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx));
+ filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg));
filter[size++] =
- (struct sock_filter)AND(entry->args[idx].op2.v32);
+ (struct sock_filter)AND(field->op2.v32);
filter[size++] = (struct sock_filter)EQ(
- entry->args[idx].value.v32, jfalse, jtrue);
+ field->value.v32, jfalse, jtrue);
break;
}
@@ -449,52 +478,51 @@ static unsigned int insert_args(struct sock_filter filter[], long nr)
{
struct filter_call_input *call = filter_input + nr;
unsigned int next_offset, n_checks = 0;
- unsigned int count = get_number_entries(nr);
+ unsigned int count = call_entry_count(nr);
struct bpf_entry *entry;
unsigned int offset = 0;
unsigned int size = 0;
- unsigned int i, k;
+ unsigned int i, j;
for (i = 0; i < count; i++) {
n_checks = 0;
- entry = &entries[call->entries[i]];
+ entry = &call->entry[i];
/* If there are multiple entries for the syscall @nr, then the next group
* of arguments to check (i.e. the next offset) is after the number of
* arguments of the current entry. The next_offset is used to
* jump to the next group if the check is false.
*/
- next_offset = get_args_for_entry(entry);
- for (k = 0; k < 6; k++) {
+ next_offset = entry_check_count(entry);
+ for (j = 0; j < entry_check_count(entry); j++) {
+ struct bpf_field *field = &entry->field[j];
+
offset = next_offset - n_checks;
- switch (entry->args[k].cmp) {
+ switch (field->cmp) {
case NO_CHECK:
continue;
case EQ:
- size += eq(&filter[size], k, entry, 0, offset);
+ size += eq(&filter[size], field, 0, offset);
break;
case NE:
- size += neq(&filter[size], k, entry, 0, offset);
+ size += neq(&filter[size], field, 0, offset);
break;
case GT:
- size += gt(&filter[size], k, entry, 0, offset);
+ size += gt(&filter[size], field, 0, offset);
break;
case LT:
- size += lt(&filter[size], k, entry, 0, offset);
+ size += lt(&filter[size], field, 0, offset);
break;
case GE:
- size += ge(&filter[size], k, entry, 0, offset);
+ size += ge(&filter[size], field, 0, offset);
break;
case LE:
- size += le(&filter[size], k, entry, 0, offset);
+ size += le(&filter[size], field, 0, offset);
break;
case AND_EQ:
- size += and_eq
- (&filter[size], k, entry, 0, offset);
+ size += and_eq(&filter[size], field, 0, offset);
break;
case AND_NE:
- size += and_ne(&filter[size], k, entry, 0,
- offset);
-
+ size += and_ne(&filter[size], field, 0, offset);
break;
}
n_checks++;
@@ -503,7 +531,7 @@ static unsigned int insert_args(struct sock_filter filter[], long nr)
* to add the notification */
if (n_checks > 0)
filter[size++] = (struct sock_filter)BPF_STMT(
- BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF);
+ BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
}
return size;
@@ -524,6 +552,8 @@ unsigned int filter_build(struct sock_filter filter[], unsigned n)
/* No nodes if there is a single syscall */
n_nodes = (1 << count_shift_right(n - 1)) - 1;
+ debug(" BPF: tree has %i nodes", n_nodes);
+
/* Pre */
/* cppcheck-suppress badBitmaskCheck */
filter[size++] = (struct sock_filter)BPF_STMT(
@@ -559,8 +589,8 @@ unsigned int filter_build(struct sock_filter filter[], unsigned n)
/* Insert leaves */
for (i = 0; i < n; i++) {
nr = get_syscall(i);
- if (get_number_entries(nr) > 0) {
- offset = next_offset;
+ if (call_entry_count(nr) > 0) {
+ offset = next_offset - 1;
} else {
/* If the syscall doesn't have any arguments, then notify */
offset = notify - size - 1;
@@ -573,10 +603,12 @@ unsigned int filter_build(struct sock_filter filter[], unsigned n)
next_offset += get_n_args_syscall_instr(nr) - 1;
}
/* Seccomp accept and notify instruction */
- filter[size++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K,
+ filter[size++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K,
SECCOMP_RET_ALLOW);
- filter[size++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K,
- SECCOMP_RET_USER_NOTIF);
+ if (!call_entry_count(nr))
+ filter[size++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K,
+ SECCOMP_RET_USER_NOTIF);
+
/*
* Insert args. It sequentially checks all the arguments for a syscall
@@ -584,13 +616,15 @@ unsigned int filter_build(struct sock_filter filter[], unsigned n)
* check the following entry of the syscall and its arguments.
*/
for (i = 0; i < n; i++) {
- nr = get_syscall(i);
size += insert_args(&filter[size], nr);
- if (has_args(nr))
+ if (call_entry_count(nr))
filter[size++] = (struct sock_filter)BPF_STMT(
- BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
+ BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF);
}
+ debug(" BPF: filter with %i call%s has %i instructions",
+ n, n != 1 ? "s" : "", size);
+
return size;
}
diff --git a/cooker/match.c b/cooker/match.c
index 374f277..776823d 100644
--- a/cooker/match.c
+++ b/cooker/match.c
@@ -41,7 +41,7 @@ static struct gluten_offset arg_load(struct gluten_ctx *g, struct arg *a)
size = a->f.size;
}
- if (!size) {
+ if (!size || (a->f.flags & RBUF)) {
g->match_dst[index].offset.type = OFFSET_SECCOMP_DATA;
g->match_dst[index].offset.offset = index;
g->match_dst[index].len = 0;
@@ -152,7 +152,7 @@ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
}
/* Nothing to match on: just store as reference */
- if (!jvalue)
+ if (!jvalue || (f->flags & RBUF))
return v;
offset.offset += f->offset;
@@ -381,7 +381,6 @@ void handle_matches(struct gluten_ctx *g, JSON_Value *value)
filter_notify(call->number);
parse_match(g, args, call->args);
- filter_flush_args();
break;
}
diff --git a/cooker/parse.c b/cooker/parse.c
index 7cac43f..8dc89f9 100644
--- a/cooker/parse.c
+++ b/cooker/parse.c
@@ -72,9 +72,32 @@ static void handle_limit(struct gluten_ctx *g, JSON_Value *value)
(void)value;
}
+static void handle_write(struct gluten_ctx *g, JSON_Value *value)
+{
+ JSON_Object *obj = json_value_get_object(value);
+ struct gluten_offset src, dst, count;
+ const char *tag;
+
+ if (!(tag = json_object_get_string(obj, "src")))
+ die("invalid tag specification");
+ src = gluten_get_tag(g, tag);
+
+ if (!(tag = json_object_get_string(obj, "dst")))
+ die("invalid tag specification");
+ dst = gluten_get_tag(g, tag);
+
+ if (!(tag = json_object_get_string(obj, "count")))
+ die("invalid tag specification");
+ count = gluten_get_tag(g, tag);
+
+ emit_store(g, dst, src, count);
+}
+
static void handle_return(struct gluten_ctx *g, JSON_Value *value)
{
JSON_Object *obj = json_value_get_object(value);
+ struct gluten_offset data = NULL_OFFSET;
+ const char *tag;
JSON_Value *jvalue;
union value v = { .v_u64 = 0 };
int32_t error = 0;
@@ -83,14 +106,16 @@ static void handle_return(struct gluten_ctx *g, JSON_Value *value)
debug(" Parsing \"return\"");
jvalue = json_object_get_value(obj, "error");
- if (json_value_get_type(jvalue) == JSONNumber) {
- error = json_value_get_number(jvalue);
- }
+ if (json_value_get_type(jvalue) == JSONNumber)
+ data = emit_data(g, U64, sizeof(v.v_u64), &v);
+ else if ((tag = json_object_get_string(obj, "error")))
+ data = gluten_get_tag(g, tag);
jvalue = json_object_get_value(obj, "value");
- if (json_value_get_type(jvalue) == JSONNumber) {
- v.v_u64 = json_value_get_number(jvalue);
- }
+ if (json_value_get_type(jvalue) == JSONNumber)
+ data = emit_data(g, U64, sizeof(v.v_u64), &v);
+ else if ((tag = json_object_get_string(obj, "value")))
+ data = gluten_get_tag(g, tag);
jvalue = json_object_get_value(obj, "continue");
if (json_value_get_type(jvalue) == JSONBoolean) {
@@ -101,7 +126,8 @@ static void handle_return(struct gluten_ctx *g, JSON_Value *value)
debug(" emit return: val=%ld errno=%d cont=%s", v.v_u64, error,
cont ? "true" : "false");
- emit_return(g, emit_data(g, U64, sizeof(v.v_u64), &v), error, cont);
+
+ emit_return(g, data, error, cont);
}
static void handle_context(struct gluten_ctx *g, JSON_Value *value)
@@ -121,6 +147,7 @@ struct rule_parser {
} parsers[] = {
{ "match", handle_matches },
{ "call", handle_calls },
+ { "write", handle_write },
{ "fd", handle_fd },
{ "limit", handle_limit },
{ "return", handle_return },
diff --git a/demo/socket.hjson b/demo/socket.hjson
index 7a16ccc..d9a3345 100644
--- a/demo/socket.hjson
+++ b/demo/socket.hjson
@@ -1,7 +1,7 @@
[
{
"match": [
- { "socket": { "family": "unix", "type": "stream", "flags": 0, "protocol": 0 } }
+ { "socket": { "family": "unix", "type": "stream", "flags": "cloexec", "protocol": 0 } }
],
"call": [
{ "socket": { "family": "unix", "type": "stream", "flags": [ "cloexec", "nonblock" ], "protocol": 0 }, "ret": "new_fd" }
diff --git a/operations.c b/operations.c
index e1132d5..70c8f08 100644
--- a/operations.c
+++ b/operations.c
@@ -292,6 +292,45 @@ out:
return ret;
}
+int op_store(const struct seccomp_notif *req, int notifier, struct gluten *g,
+ struct op_store *store)
+{
+ const long unsigned int *dst = gluten_ptr(&req->data, g, store->dst);
+ const size_t *count = gluten_ptr(&req->data, g, store->count);
+ const void *src = gluten_ptr(&req->data, g, store->src);
+ char path[PATH_MAX];
+ int fd, ret = 0;
+
+ debug(" op_store: argument (%s %d) in (%s %d) size=%d",
+ gluten_offset_name[store->src.type], store->src.offset,
+ gluten_offset_name[store->dst.type], store->dst.offset);
+
+ if (dst == NULL)
+ ret_err(-1, " op_store: empty destination");
+
+ snprintf(path, sizeof(path), "/proc/%d/mem", req->pid);
+ if ((fd = open(path, O_WRONLY | O_CLOEXEC)) < 0)
+ ret_err(-1, "error opening mem for %d", req->pid);
+ /*
+ * Avoid the TOCTOU and check if the read mappings are still valid
+ */
+ if (!is_cookie_valid(notifier, req->id)) {
+ err("the seccomp request isn't valid anymore");
+ ret = -1;
+ goto out;
+ }
+
+ if (pwrite(fd, src, *count, *dst) < 0) {
+ err("pwrite");
+ ret = -1;
+ goto out;
+ }
+
+out:
+ close(fd);
+ return ret;
+}
+
int op_return(const struct seccomp_notif *req, int notifier, struct gluten *g,
struct op_return *op)
{
@@ -503,6 +542,7 @@ int eval(struct gluten *g, const struct seccomp_notif *req,
HANDLE_OP(OP_RETURN, op_return, ret, g);
HANDLE_OP(OP_FD, op_fd, fd, g);
HANDLE_OP(OP_LOAD, op_load, load, g);
+ HANDLE_OP(OP_STORE, op_store, store, g);
HANDLE_OP(OP_BITWISE, op_bitwise, bitwise, g);
HANDLE_OP(OP_CMP, op_cmp, cmp, g);
HANDLE_OP(OP_RESOLVEDFD, op_resolve_fd, resfd, g);
diff --git a/seitan.c b/seitan.c
index a18e3ea..bd1a7e2 100644
--- a/seitan.c
+++ b/seitan.c
@@ -217,7 +217,6 @@ int main(int argc, char **argv)
if ((notifier = recvfd(fd)) < 0)
die(" failed recieving the notifier fd");
}
- sleep(1);
if ((epollfd = epoll_create1(0)) < 0)
die(" epoll_create");