diff options
author | Stefano Brivio <sbrivio@redhat.com> | 2023-05-16 03:19:01 +0200 |
---|---|---|
committer | Stefano Brivio <sbrivio@redhat.com> | 2023-05-16 07:20:25 +0200 |
commit | 7ab2bda2e69d4a862572be4b5e389a6aa864470d (patch) | |
tree | fa9653204a4ab9581b76499c95d76d16d467301d | |
parent | 049bd1ca828da835f2903b88adcf9ce0bdacd6e4 (diff) | |
download | seitan-7ab2bda2e69d4a862572be4b5e389a6aa864470d.tar seitan-7ab2bda2e69d4a862572be4b5e389a6aa864470d.tar.gz seitan-7ab2bda2e69d4a862572be4b5e389a6aa864470d.tar.bz2 seitan-7ab2bda2e69d4a862572be4b5e389a6aa864470d.tar.lz seitan-7ab2bda2e69d4a862572be4b5e389a6aa864470d.tar.xz seitan-7ab2bda2e69d4a862572be4b5e389a6aa864470d.tar.zst seitan-7ab2bda2e69d4a862572be4b5e389a6aa864470d.zip |
cooker, seitan: Now with 100% more gluten
Pseudorandom changes and progress around cooker and seitan:
- cooker:
- rename matching functions, split match.c
- fix up SELECT semantics
- add some form of handling for all syscalls in the example
(some stubs)
- OP_CMP for all basic and compound types except for flags
- link jumps to next block and next match
- completed implementation of tags
- gluten write
- filter clean-ups, write filters (probably not working)
- seitan:
- load gluten and source instructions and data from there
$ ./seitan-cooker cooker/example.hjson example.gluten example.bpf
Parsing block 0
Parsing match 0: connect
Found description for connect
0: OP_NR: if syscall number is not 0, jump to next block
Parsing match argument fd
setting tag reference 'fd'
tag 'fd' now refers to seccomp data at 0
Parsing match argument addr
allocating 128 at offset 0
1: OP_LOAD: #0 < args[1] (size: 128)
C#0: (INT) 1
2: OP_CMP: if temporary data: #0 NE (size: 4) read-only data: #0, jump to next block
C#4: (STRING:24) /var/run/pr-helper.sock
3: OP_CMP: if temporary data: #0 NE (size: 24) read-only data: #4, jump to next block
Linking match...
Linking block...
linked jump of instruction #0 to #4
linked jump of instruction #2 to #4
linked jump of instruction #3 to #4
Parsing block 1
Parsing match 0: ioctl
Found description for ioctl
4: OP_NR: if syscall number is not 112, jump to next block
Parsing match argument path
Parsing match argument request
C#28: (INT) 1074025674
5: OP_CMP: if seccomp data: #1 NE (size: 4) read-only data: #28, jump to next block
Parsing match argument ifr
allocating 40 at offset 128
6: OP_LOAD: #128 < args[2] (size: 40)
C#32: (STRING:5) tap0
7: OP_CMP: if temporary data: #128 NE (size: 5) read-only data: #32, jump to next block
C#37: (INT) 1
8: OP_CMP: if temporary data: #128 NE (size: 4) read-only data: #37, jump to next block
Linking match...
Linking block...
linked jump of instruction #4 to #9
linked jump of instruction #5 to #9
linked jump of instruction #7 to #9
linked jump of instruction #8 to #9
Parsing block 2
Parsing match 0: unshare
Found description for unshare
9: OP_NR: if syscall number is not 164, jump to next block
Parsing match argument flags
Linking match...
Linking block...
linked jump of instruction #9 to #10
Parsing block 3
Parsing match 0: unshare
Found description for unshare
10: OP_NR: if syscall number is not 164, jump to next block
Parsing match argument flags
Linking match...
Linking block...
linked jump of instruction #10 to #11
Parsing block 4
Parsing match 0: mknod
Found description for mknod
11: OP_NR: if syscall number is not 164, jump to next block
Parsing match argument path
allocating 1 at offset 168
12: OP_LOAD: #168 < args[0] (size: 1)
setting tag reference 'path'
tag 'path' now refers to temporary data at 168
Parsing match argument mode
Parsing match argument major
Parsing match argument minor
setting tag reference 'minor'
tag 'minor' now refers to seccomp data at 2
Linking match...
Linking block...
linked jump of instruction #11 to #13
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
-rw-r--r-- | common/gluten.h | 10 | ||||
-rw-r--r-- | common/util.c | 5 | ||||
-rw-r--r-- | cooker/Makefile | 28 | ||||
-rw-r--r-- | cooker/calls.c | 5 | ||||
-rw-r--r-- | cooker/calls/fs.c | 91 | ||||
-rw-r--r-- | cooker/calls/fs.h | 11 | ||||
-rw-r--r-- | cooker/calls/ioctl.c | 135 | ||||
-rw-r--r-- | cooker/calls/ioctl.h | 11 | ||||
-rw-r--r-- | cooker/calls/net.c | 22 | ||||
-rw-r--r-- | cooker/calls/process.c | 48 | ||||
-rw-r--r-- | cooker/calls/process.h | 11 | ||||
-rw-r--r-- | cooker/cooker.h | 101 | ||||
-rw-r--r-- | cooker/emit.c | 150 | ||||
-rw-r--r-- | cooker/emit.h | 6 | ||||
-rw-r--r-- | cooker/example.hjson | 14 | ||||
-rw-r--r-- | cooker/filter.c | 388 | ||||
-rw-r--r-- | cooker/filter.h | 36 | ||||
-rw-r--r-- | cooker/gluten.c | 58 | ||||
-rw-r--r-- | cooker/gluten.h | 23 | ||||
-rw-r--r-- | cooker/main.c | 15 | ||||
-rw-r--r-- | cooker/match.c | 361 | ||||
-rw-r--r-- | cooker/match.h | 11 | ||||
-rw-r--r-- | cooker/parse.c | 273 | ||||
-rw-r--r-- | cooker/parse.h | 2 | ||||
-rw-r--r-- | operations.c | 6 | ||||
-rw-r--r-- | operations.h | 3 | ||||
-rw-r--r-- | seitan.c | 7 |
27 files changed, 1311 insertions, 520 deletions
diff --git a/common/gluten.h b/common/gluten.h index 3df638e..ff29caa 100644 --- a/common/gluten.h +++ b/common/gluten.h @@ -10,6 +10,7 @@ #include <stddef.h> #include <stdint.h> #include <stdbool.h> +#include <sys/types.h> #include <linux/seccomp.h> #include <stdio.h> @@ -39,6 +40,8 @@ enum gluten_offset_type { OFFSET_TYPE_MAX = OFFSET_INSTRUCTION, }; +extern const char *gluten_offset_name[OFFSET_TYPE_MAX + 1]; + struct gluten_offset { #ifdef __GNUC__ enum gluten_offset_type type : BITS_PER_NUM(OFFSET_TYPE_MAX); @@ -89,6 +92,8 @@ struct op_context { }; enum op_type { + OP_END = 0, + OP_NR, OP_CALL, OP_BLOCK, OP_CONT, @@ -98,7 +103,6 @@ enum op_type { OP_LOAD, OP_CMP, OP_RESOLVEDFD, - OP_END, }; struct op_nr { @@ -147,7 +151,7 @@ struct op_cmp { struct gluten_offset y; size_t size; enum op_cmp_type cmp; - unsigned int jmp; + struct gluten_offset jmp; }; struct op_resolvedfd { @@ -237,7 +241,7 @@ static inline const void *gluten_ptr(const struct seccomp_data *s, if (!is_offset_valid(x)) return NULL; - if(x.type == OFFSET_SECCOMP_DATA && s == NULL) + if (x.type == OFFSET_SECCOMP_DATA && s == NULL) return NULL; switch (x.type) { diff --git a/common/util.c b/common/util.c index a2ecce0..3a4b2e5 100644 --- a/common/util.c +++ b/common/util.c @@ -12,6 +12,8 @@ #include <stdarg.h> #include <string.h> +#include "gluten.h" + #define logfn(name) \ void name(const char *format, ...) { \ va_list args; \ @@ -27,3 +29,6 @@ logfn(err) logfn(info) logfn(debug) +const char *gluten_offset_name[OFFSET_TYPE_MAX + 1] = { + "read-only data", "temporary data", "seccomp data", "instruction area" +}; diff --git a/cooker/Makefile b/cooker/Makefile index be8f703..5a0ea8e 100644 --- a/cooker/Makefile +++ b/cooker/Makefile @@ -13,12 +13,28 @@ COMMON := ../common BIN := $(OUTDIR)/seitan-cooker CFLAGS := -O0 -g -Wall -Wextra -pedantic -std=c99 -I$(COMMON) -SRCS := calls.c emit.c gluten.c main.c parse.c parson.c \ - $(COMMON)/util.c \ - calls/net.c -HEADERS := calls.h cooker.h emit.h gluten.h parse.h parson.h \ - $(COMMON)/gluten.h $(COMMON)/util.h \ - calls/net.h +TARGET ?= $(shell $(CC) -dumpmachine) +# Get 'uname -m'-like architecture description for target +TARGET_ARCH := $(shell echo $(TARGET) | cut -f1 -d- | tr [A-Z] [a-z]) +TARGET_ARCH := $(shell echo $(TARGET_ARCH) | sed 's/powerpc/ppc/') + +AUDIT_ARCH := $(shell echo $(TARGET_ARCH) | tr [a-z] [A-Z] | sed 's/^ARM.*/ARM/') +AUDIT_ARCH := $(shell echo $(AUDIT_ARCH) | sed 's/I[456]86/I386/') +AUDIT_ARCH := $(shell echo $(AUDIT_ARCH) | sed 's/PPC64/PPC/') +AUDIT_ARCH := $(shell echo $(AUDIT_ARCH) | sed 's/PPCLE/PPC64LE/') +AUDIT_ARCH := $(shell echo $(AUDIT_ARCH) | sed 's/MIPS64EL/MIPSEL64/') +AUDIT_ARCH := $(shell echo $(AUDIT_ARCH) | sed 's/HPPA/PARISC/') +AUDIT_ARCH := $(shell echo $(AUDIT_ARCH) | sed 's/SH4/SH/') + +CFLAGS += -DSEITAN_AUDIT_ARCH=AUDIT_ARCH_$(AUDIT_ARCH) + + +SRCS := 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 +HEADERS := 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 $(BIN): $(SRCS) $(HEADERS) $(CC) $(CFLAGS) -o $(BIN) $(SRCS) diff --git a/cooker/calls.c b/cooker/calls.c index 74b5a06..783c3f6 100644 --- a/cooker/calls.c +++ b/cooker/calls.c @@ -12,7 +12,10 @@ #include "calls.h" #include "calls/net.h" +#include "calls/ioctl.h" +#include "calls/process.h" +#include "calls/fs.h" struct call *call_sets[] = { - syscalls_net, NULL, + syscalls_net, syscalls_ioctl, syscalls_process, syscalls_fs, NULL, }; diff --git a/cooker/calls/fs.c b/cooker/calls/fs.c new file mode 100644 index 0000000..d800f38 --- /dev/null +++ b/cooker/calls/fs.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/* seitan - Syscall Expressive Interpreter, Transformer and Notifier + * + * cooker/calls/fs.c - Description of known filesystem-related system calls + * + * Copyright 2023 Red Hat GmbH + * Author: Stefano Brivio <sbrivio@redhat.com> + */ + +/* +stat ? +fstat ? +lstat ? + +lseek ? + +fcntl ? +flock ~ +fsync +fdatasync +truncate +ftruncate + +getdents +getcwd +chdir +fchdir +mkdir +rmdir + +rename + +creat + +link +unlink +symlink +readlink + +chmod +fchmod +chown +fchown +fchownat +lchown +umask + +mknod +mknodat + +mount +umount2 +swapon +swapoff +*/ + +#include <asm-generic/unistd.h> +#include <sys/syscall.h> + +#include <fcntl.h> +#include <sys/stat.h> +#include <linux/limits.h> + +#include "../cooker.h" +#include "../calls.h" + +static struct arg mknod_args[] = { + { + 0, "path", STRING, 1 /* TODO: PATH_MAX */, + { 0 } + }, + { + 1, "mode", INTFLAGS, 0, + { 0 /* TODO */ }, + }, + { + 2, "major", UNDEF /* TODO */, 0, + { 0 }, + }, + { + 2, "minor", UNDEF /* TODO */, 0, + { 0 }, + }, + { 0 }, +}; + +struct call syscalls_fs[] = { + { __NR_mknod, "mknod", mknod_args }, + { 0 }, +}; diff --git a/cooker/calls/fs.h b/cooker/calls/fs.h new file mode 100644 index 0000000..2e3c06b --- /dev/null +++ b/cooker/calls/fs.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2023 Red Hat GmbH + * Author: Stefano Brivio <sbrivio@redhat.com> + */ + +#ifndef CALLS_FS_H +#define CALLS_FS_H + +extern struct call syscalls_fs[]; + +#endif /* CALLS_FS_H */ diff --git a/cooker/calls/ioctl.c b/cooker/calls/ioctl.c new file mode 100644 index 0000000..576e02e --- /dev/null +++ b/cooker/calls/ioctl.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* seitan - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * cooker/calls/ioctl.c - Description of known ioctl(2) requests
+ *
+ * Copyright 2023 Red Hat GmbH
+ * Authors: Alice Frosi <afrosi@redhat.com>
+ * Stefano Brivio <sbrivio@redhat.com>
+ */
+
+/*
+fd = ioctl_ns(fd, request)
+n = ioctl_tty(fd, cmd, argp)
+e = ioctl_iflags(fd, cmd, attr)
+*/
+
+#include <asm-generic/unistd.h>
+#include <sys/syscall.h>
+
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <linux/fs.h>
+#include <linux/nsfs.h>
+
+#include <net/if.h>
+#include <linux/if.h>
+#include <linux/if_tun.h>
+
+#include "../cooker.h"
+#include "../calls.h"
+
+static struct num request[] = {
+ { "FS_IOC_GETFLAGS", FS_IOC_GETFLAGS }, /* ioctl_iflags */
+ { "FS_IOC_SETFLAGS", FS_IOC_SETFLAGS },
+
+ { "NS_GET_USERNS", NS_GET_USERNS }, /* ioctl_ns*/
+ { "NS_GET_PARENT", NS_GET_PARENT },
+
+ { "TCGETS", TCGETS }, /* ioctl_tty */
+ { "TCSETS", TCSETS },
+ { "TCSETSW", TCSETSW },
+ { "TCSETSF", TCSETSF },
+
+ { "TUNSETIFF", TUNSETIFF }, /* no man page? */
+
+ { 0 },
+};
+
+static struct num attr[] = {
+ { "FS_APPEND_FL", FS_APPEND_FL },
+ { "FS_COMPR_FL", FS_COMPR_FL },
+ { "FS_DIRSYNC_FL", FS_DIRSYNC_FL },
+ { "FS_IMMUTABLE_FL", FS_IMMUTABLE_FL },
+ { "FS_JOURNAL_DATA_FL", FS_JOURNAL_DATA_FL },
+ { "FS_NOATIME_FL", FS_NOATIME_FL },
+ { "FS_NOCOW_FL", FS_NOCOW_FL },
+ { "FS_NODUMP_FL", FS_NODUMP_FL },
+ { "FS_NOTAIL_FL", FS_NOTAIL_FL },
+ { "FS_PROJINHERIT_FL", FS_PROJINHERIT_FL },
+ { "FS_SECRM_FL", FS_SECRM_FL },
+ { "FS_SYNC_FL", FS_SYNC_FL },
+ { "FS_TOPDIR_FL", FS_TOPDIR_FL },
+ { "FS_UNRM_FL", FS_UNRM_FL },
+};
+
+static struct num tun_ifr_flags[] = {
+ { "IFF_TUN", IFF_TUN },
+ { 0 },
+};
+
+static struct field tun_ifr[] = { /* netdevice(7) */
+ {
+ "name", STRING,
+ offsetof(struct ifreq, ifr_name),
+ IFNAMSIZ, { 0 },
+ },
+ {
+ "flags", INT, /* One allowed at a time? */
+ offsetof(struct ifreq, ifr_flags),
+ 0, { .d_num = tun_ifr_flags },
+ },
+};
+
+static struct select_num ioctl_request_arg[] = {
+ {
+ FS_IOC_GETFLAGS,
+ { 2, "argp", INTFLAGS, sizeof(int), { .d_num = attr } }
+ },
+ {
+ FS_IOC_SETFLAGS,
+ { 2, "argp", INTFLAGS, sizeof(int), { .d_num = attr } }
+ },
+ {
+ TUNSETIFF,
+ {
+ 2, "ifr", STRUCT, sizeof(struct ifreq),
+ { .d_struct = tun_ifr }
+ }
+ },
+ { 0 },
+};
+
+static struct field ioctl_request = {
+ "request", INT, 0, 0, { .d_num = request },
+};
+
+static struct select ioctl_request_select = {
+ &ioctl_request, { .d_num = ioctl_request_arg }
+};
+
+static struct arg ioctl_args[] = {
+ {
+ 0, "path", FDPATH, 0,
+ { 0 }
+ },
+ {
+ 0, "fd", INT, 0,
+ { 0 }
+ },
+ {
+ 1, "request", SELECT, 0,
+ { .d_select = &ioctl_request_select }
+ },
+ {
+ 2, "arg", SELECTED, -1,
+ { 0 }
+ },
+ { 0 },
+};
+
+struct call syscalls_ioctl[] = {
+ { __NR_ioctl, "ioctl", ioctl_args },
+ { 0 },
+};
diff --git a/cooker/calls/ioctl.h b/cooker/calls/ioctl.h new file mode 100644 index 0000000..a06a9bc --- /dev/null +++ b/cooker/calls/ioctl.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2023 Red Hat GmbH + * Author: Stefano Brivio <sbrivio@redhat.com> + */ + +#ifndef CALLS_IOCTL_H +#define CALLS_IOCTL_H + +extern struct call syscalls_ioctl[]; + +#endif /* CALLS_IOCTL_H */ diff --git a/cooker/calls/net.c b/cooker/calls/net.c index 370a3a1..df97aab 100644 --- a/cooker/calls/net.c +++ b/cooker/calls/net.c @@ -135,7 +135,7 @@ static struct field connect_addr_nl[] = { }, { "groups", U32, - offsetof(struct sockaddr_in6, sin6_addr), + offsetof(struct sockaddr_nl, nl_groups), 0, { 0 } }, { 0 }, @@ -148,10 +148,22 @@ static struct field connect_family = { }; static struct select_num connect_addr_select_family[] = { - { AF_UNIX, STRUCT, { .d_struct = connect_addr_unix } }, - { AF_INET, STRUCT, { .d_struct = connect_addr_ipv4 } }, - { AF_INET6, STRUCT, { .d_struct = connect_addr_ipv6 } }, - { AF_NETLINK, STRUCT, { .d_struct = connect_addr_nl } }, + { + AF_UNIX, + { 1, NULL, STRUCT, 0, { .d_struct = connect_addr_unix } } + }, + { + AF_INET, + { 1, NULL, STRUCT, 0, { .d_struct = connect_addr_ipv4 } } + }, + { + AF_INET6, + { 1, NULL, STRUCT, 0, { .d_struct = connect_addr_ipv6 } } + }, + { + AF_NETLINK, + { 1, NULL, STRUCT, 0, { .d_struct = connect_addr_nl } } + }, { 0 }, }; diff --git a/cooker/calls/process.c b/cooker/calls/process.c new file mode 100644 index 0000000..7c0f36e --- /dev/null +++ b/cooker/calls/process.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/* seitan - Syscall Expressive Interpreter, Transformer and Notifier + * + * cooker/calls/process.c - Description of known process-related system calls + * + * Copyright 2023 Red Hat GmbH + * Author: Stefano Brivio <sbrivio@redhat.com> + */ + +/* +clone +fork +vfork +execve +exit +wait3 +wait4 +waitid +kill +exit_group +unshare +kcmp +clone3 +*/ + +#include <asm-generic/unistd.h> +#include <sys/syscall.h> + +#include <unistd.h> +#include <sched.h> +#include <linux/kcmp.h> +#include <sys/wait.h> + +#include "../cooker.h" +#include "../calls.h" + +static struct arg unshare_args[] = { + { + 0, "flags", INTFLAGS, 0, + { 0 /* TODO */ } + }, +}; + +struct call syscalls_process[] = { + { __NR_unshare, "unshare", unshare_args }, + { 0 }, +}; diff --git a/cooker/calls/process.h b/cooker/calls/process.h new file mode 100644 index 0000000..5e214ef --- /dev/null +++ b/cooker/calls/process.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2023 Red Hat GmbH + * Author: Stefano Brivio <sbrivio@redhat.com> + */ + +#ifndef CALLS_PROCESS_H +#define CALLS_PROCESS_H + +extern struct call syscalls_process[]; + +#endif /* CALLS_PROCESS_H */ diff --git a/cooker/cooker.h b/cooker/cooker.h index a1cc360..82b24f7 100644 --- a/cooker/cooker.h +++ b/cooker/cooker.h @@ -6,6 +6,9 @@ #ifndef COOKER_H #define COOKER_H +#define _GNU_SOURCE +#include <fcntl.h> +#include <unistd.h> #include <stddef.h> #include <stdint.h> #include <stdio.h> @@ -14,27 +17,46 @@ #include <sys/types.h> #include <arpa/inet.h> -#define REFS_MAX 256 -#define REF_NAMEMAX 256 +#define TAGS_MAX 256 #define CALL_ARGS 6 struct num; struct field; struct select; +/** + * union desc - Description of lists of numbers, structs or selector fields + * @d_num: Pointer to a list of numbers and their labels + * @d_struct: Pointer to a struct description + * @d_select: Pointer to description of a selector + */ union desc { struct num *d_num; struct field *d_struct; struct select *d_select; }; +/** + * union value - Represent a generic value used internally by cooker + * @v_int: Value of type int + * @v_u32: Value of type u32 + * @v_num: Value of type long long, or any other numeric type + * @v_str: String, directly from JSON + */ union value { int v_int; uint32_t v_u32; long long v_num; + const char *v_str; }; +/** + * enum type - Types of values for arguments and fields within arguments + */ enum type { + UNDEF = 0, + NONE, + INT, INTMASK, INTFLAGS, @@ -43,6 +65,10 @@ enum type { U32MASK, U32FLAGS, + U64, + U64MASK, + U64FLAGS, + LONG, LONGMASK, LONGFLAGS, @@ -51,6 +77,7 @@ enum type { STRUCT, SELECT, + SELECTED, PID, @@ -68,16 +95,24 @@ enum type { #define TYPE_IS_COMPOUND(t) ((t) == STRUCT || (t) == SELECT) #define TYPE_IS_NUM(t) ((t) == INT || (t) == U32 || (t) == LONG) -enum jump_type { - NEXT_BLOCK, - END, -}; - +/** + * struct num - A numeric value and its label + * @name: Label for numeric value + * @value: Numeric value + */ struct num { char *name; long long value; }; +/** + * struct field - Field inside a struct + * @name: Name of field + * @type: Type of field + * @offset: Offset of field within struct, in bytes + * @strlen: Length of string for string types, 0 otherwise + * @desc: Description of possible values for field, or linked struct + */ struct field { char *name; enum type type; @@ -88,21 +123,27 @@ struct field { union desc desc; }; -struct select_num { - long long value; +/** + * struct select_target - Description of value selected by selector field + * @type: Type of value + * @size: Size to dereference for pointers, 0 otherwise + * @desc: Description for selected value + */ +struct select_target { + enum type type; /* TODO: Almost a struct arg? */ + size_t size; - enum type type; union desc desc; }; -struct select { - struct field *field; - - union { - struct select_num *d_num; - } desc; -}; - +/** + * struct arg - Description of part of, or complete system call argument + * @pos: Index of argument in system call + * @name: JSON name used for matches and calls + * @type: Argument type + * @size: Size of pointed area if any, 0 otherwise + * @desc: Description of list of numbers, struct or selector field + */ struct arg { int pos; char *name; @@ -113,4 +154,28 @@ struct arg { union desc desc; }; +/** + * struct select_num - List of possible selections based on numeric selector + * @value: Numeric value of the selector + * @target: Argument description defined by this selector + */ +struct select_num { + long long value; + + struct arg target; +}; + +/** + * struct select - Association between argument description and selected values + * @field: Description of argument operating the selection + * @d_num: List of possible selections + */ +struct select { + struct field *field; + + union { + struct select_num *d_num; + } desc; +}; + #endif /* COOKER_H */ diff --git a/cooker/emit.c b/cooker/emit.c index 8c35f1d..c233b0a 100644 --- a/cooker/emit.c +++ b/cooker/emit.c @@ -14,8 +14,10 @@ #include "emit.h" static const char *type_str[] = { + "UNDEF", "NONE", "INT", "INTMASK", "INTFLAGS", "U32", "U32MASK", "U32FLAGS", + "U64", "U64MASK", "U64FLAGS", "LONG", "LONGMASK", "LONGFLAGS", "STRING", "STRUCT", "SELECT", @@ -25,83 +27,183 @@ static const char *type_str[] = { NULL }; -static const char *cmp_type_str[] = { "EQ", "GT", "GE", "LT", "LE", NULL }; +static const char *cmp_type_str[] = { + "EQ", "NE", "GT", "GE", "LT", "LE", NULL +}; -void emit_nr(struct gluten_ctx *g, long number) +/** + * emit_nr() - Emit OP_NR instruction: jump on syscall mismatch + * @g: gluten context + * @number: Pointer to system call number + */ +void emit_nr(struct gluten_ctx *g, struct gluten_offset number) { - struct op_nr *nr = (struct op_nr *)gluten_ptr(&g->g, g->ip); + struct op *op = (struct op *)gluten_ptr(&g->g, g->ip); + struct op_nr *nr = &op->op.nr; + + op->type = OP_NR; nr->nr = number; nr->no_match.type = OFFSET_INSTRUCTION; - nr->no_match.offset = NEXT_BLOCK; + nr->no_match.offset = JUMP_NEXT_BLOCK; - debug(" %i: OP_NR %li, < >", g->ip.offset, number); + debug(" %i: OP_NR: if syscall number is not %li, jump to %s", + g->ip.offset, number, jump_name[nr->no_match.offset]); if (++g->ip.offset > INST_MAX) die("Too many instructions"); } +/** + * emit_load() - Emit OP_LOAD instruction: dereference and copy syscall argument + * @g: gluten context + * @dst: gluten destination to copy dereferenced data + * @index: Index of system call argument + * @len: Length of data item pointed by reference + */ void emit_load(struct gluten_ctx *g, struct gluten_offset dst, int index, size_t len) { - struct op_load *load = (struct op_load *)gluten_ptr(&g->g, g->ip); + struct op *op = (struct op *)gluten_ptr(&g->g, g->ip); + struct op_load *load = &op->op.load; + + op->type = OP_LOAD; load->src.type = OFFSET_SECCOMP_DATA; load->src.offset = index; load->dst = dst; - debug(" %i: OP_LOAD #%i < %i (%lu)", g->ip.offset, dst.offset, - index, len); + debug(" %i: OP_LOAD: #%i < args[%i] (size: %lu)", + g->ip.offset, dst.offset, index, len); if (++g->ip.offset > INST_MAX) die("Too many instructions"); } -void emit_cmp(struct gluten_ctx *g, enum op_cmp_type cmp, +/** + * emit_cmp(): Emit OP_CMP instruction: compare data from two offsets + * @g: gluten context + * @cmp_type: Type of comparison + * @x: gluten pointer to first operand of comparison + * @y: gluten pointer to second operand of comparison + * @size: Size of comparison + * @jmp: Jump direction if comparison is true + */ +void emit_cmp(struct gluten_ctx *g, enum op_cmp_type cmp_type, struct gluten_offset x, struct gluten_offset y, size_t size, enum jump_type jmp) { - struct op_cmp *op = (struct op_cmp *)gluten_ptr(&g->g, g->ip); + struct op *op = (struct op *)gluten_ptr(&g->g, g->ip); + struct op_cmp *cmp = &op->op.cmp; - op->x = x; - op->y = y; - op->size = size; - op->cmp = cmp; - op->jmp = jmp; + op->type = OP_CMP; - debug(" %i: OP_CMP (#%lu) %%%lu %s %%%lu", g->ip.offset, size, - x.offset, cmp_type_str[cmp], y.offset); + cmp->x = x; + cmp->y = y; + cmp->size = size; + cmp->cmp = cmp_type; + cmp->jmp.type = OFFSET_INSTRUCTION; + cmp->jmp.offset = jmp; + + debug(" %i: OP_CMP: if %s: #%lu %s (size: %lu) %s: #%lu, jump to %s", + g->ip.offset, + gluten_offset_name[x.type], x.offset, + cmp_type_str[cmp_type], size, + gluten_offset_name[y.type], y.offset, + jump_name[jmp]); if (++g->ip.offset > INST_MAX) die("Too many instructions"); } +/** + * emit_cmp_field() - Emit OP_CMP for a given field type + * @g: gluten context + * @cmp: Type of comparison + * @field: Description of field from system call model + * @x: gluten pointer to first operand of comparison + * @y: gluten pointer to second operand of comparison + * @jmp: Jump direction if comparison is true + */ void emit_cmp_field(struct gluten_ctx *g, enum op_cmp_type cmp, struct field *field, - struct gluten_offset base, struct gluten_offset match, + struct gluten_offset x, struct gluten_offset y, enum jump_type jmp) { - base.offset += field->offset; - - emit_cmp(g, cmp, base, match, + emit_cmp(g, cmp, x, y, field->strlen ? field->strlen : gluten_size[field->type], jmp); } struct gluten_offset emit_data(struct gluten_ctx *g, enum type type, - union value *value) + size_t str_len, union value *value) { void *p = gluten_ptr(&g->g, g->cp); struct gluten_offset ret = g->cp; - if (type == INT) { + switch (type) { + case INT: + if (g->cp.offset + sizeof(int) > RO_DATA_SIZE) + die(" Read-only data section exceeded"); + *(int *)p = value->v_int; debug(" C#%i: (%s) %i", g->cp.offset, type_str[type], value->v_int); - if ((g->cp.offset += sizeof(int)) > RO_DATA_SIZE) + + g->cp.offset += sizeof(int); + break; + case STRING: + if (g->cp.offset + str_len > RO_DATA_SIZE) die(" Read-only data section exceeded"); + + strncpy(p, value->v_str, str_len); + debug(" C#%i: (%s:%i) %s", g->cp.offset, type_str[type], + str_len, value->v_str); + + g->cp.offset += str_len; + break; + default: + ; } return ret; } + +static void gluten_link(struct gluten_ctx *g, enum jump_type type, + struct op *start) +{ + struct gluten_offset *jmp; + struct op *op; + + for (op = (struct op *)start; op->type; op++) { + switch (op->type) { + case OP_NR: + jmp = &op->op.nr.no_match; + break; + case OP_CMP: + jmp = &op->op.cmp.jmp; + break; + default: + continue; + } + + if (jmp->offset == type) { + jmp->offset = g->ip.offset; + debug(" linked jump of instruction #%i to #%i", + op - (struct op *)g->g.inst, g->ip.offset); + } + } +} + +void link_block(struct gluten_ctx *g) +{ + debug(" Linking block..."); + gluten_link(g, JUMP_NEXT_BLOCK, (struct op *)gluten_ptr(&g->g, g->lr)); +} + +void link_match(struct gluten_ctx *g) +{ + debug(" Linking match..."); + gluten_link(g, JUMP_NEXT_MATCH, (struct op *)gluten_ptr(&g->g, g->mr)); +} diff --git a/cooker/emit.h b/cooker/emit.h index 94b2600..2135052 100644 --- a/cooker/emit.h +++ b/cooker/emit.h @@ -6,7 +6,7 @@ #ifndef EMIT_H #define EMIT_H -void emit_nr(struct gluten_ctx *g, long number); +void emit_nr(struct gluten_ctx *g, struct gluten_offset number); void emit_load(struct gluten_ctx *g, struct gluten_offset dst, int index, size_t len); void emit_cmp(struct gluten_ctx *g, enum op_cmp_type cmp, @@ -17,6 +17,8 @@ void emit_cmp_field(struct gluten_ctx *g, enum op_cmp_type cmp, struct gluten_offset base, struct gluten_offset match, enum jump_type jmp); struct gluten_offset emit_data(struct gluten_ctx *g, enum type type, - union value *value); + size_t str_len, union value *value); +void link_block(struct gluten_ctx *g); +void link_match(struct gluten_ctx *g); #endif /* EMIT_H */ diff --git a/cooker/example.hjson b/cooker/example.hjson index 45ed339..8c862da 100644 --- a/cooker/example.hjson +++ b/cooker/example.hjson @@ -1,10 +1,10 @@ [ { "match": [ /* qemu-pr-helper and similar */ - { "connect": { "addr": { "family": "unix", "path": "/var/run/pr-helper.sock" }, "fd": { "ref": "fd" } } } + { "connect": { "addr": { "family": "unix", "path": "/var/run/pr-helper.sock" }, "fd": { "tag": "fd" } } } ], "call": { "connect": { "addr": { "family": "unix", "path": "/var/run/pr-helper.sock" }, "ret": "y" } }, - "inject": { "what": "fd", "new": { "ref": "y" }, "old": { "ref": "fd" }, "return": 0 } + "inject": { "what": "fd", "new": { "tag": "y" }, "old": { "tag": "fd" }, "return": 0 } }, { "match": [ /* qemu creates a tap interface */ @@ -12,7 +12,7 @@ ], "limit": { "scope": "process", "count": 1 }, "call": { "ioctl": { "request": "TUNSETIFF", "path": "/dev/net/tun", "ifr": { "name": "tap0", "flags": "IFF_TUN", "ret": "x" } } }, - "return": { "ref": "x" } + "return": { "tag": "x" } }, { "match": [ /* CVE-2022-0185-style */ @@ -28,12 +28,12 @@ }, { "match": [ /* Giuseppe's example */ - { "mknod": { "path": { "ref": "path" }, "mode": "c", "major": 1, "minor": { "in": [ 3, 5, 7, 8, 9 ], "ref": "minor" } } } + { "mknod": { "path": { "tag": "path" }, "mode": "c", "major": 1, "minor": { "in": [ 3, 5, 7, 8, 9 ], "tag": "minor" } } } ], "context": { "userns": "init", "mountns": "caller" }, - "call": { "mknod": { "path": { "ref": "path" }, "mode": "c", "major": 1, "minor": { "ref": "minor" }, "ret": "x" } }, - "inject": { "what": "fd", "new": { "ref": "x" } }, - "return": { "ref": "x" } + "call": { "mknod": { "path": { "tag": "path" }, "mode": "c", "major": 1, "minor": { "tag": "minor" }, "ret": "x" } }, + "inject": { "what": "fd", "new": { "tag": "x" } }, + "return": { "tag": "x" } } ] diff --git a/cooker/filter.c b/cooker/filter.c index 0539e42..9476089 100644 --- a/cooker/filter.c +++ b/cooker/filter.c @@ -12,29 +12,13 @@ #include <string.h> #include <unistd.h> -#include "numbers.h" #include "filter.h" #include "util.h" -#define N_SYSCALL sizeof(numbers) / sizeof(numbers[0]) - -static int compare_key(const void *key, const void *base) -{ - return strcmp((const char *)key, - ((struct syscall_numbers *)base)->name); -} - -int compare_bpf_call_names(const void *a, const void *b) -{ - return strcmp(((struct bpf_call *)a)->name, - ((struct bpf_call *)b)->name); -} - -static int compare_table_nr(const void *a, const void *b) -{ - return (((struct syscall_entry *)a)->nr - - ((struct syscall_entry *)b)->nr); -} +struct notify { + long nr; + struct bpf_arg arg[6]; +} notify_call[512]; static unsigned int count_shift_right(unsigned int n) { @@ -104,102 +88,50 @@ void create_lookup_nodes(int jumps[], unsigned int n) } } -long resolve_syscall_nr(const char *name) -{ - struct syscall_numbers *p; - p = (struct syscall_numbers *)bsearch( - name, numbers, sizeof(numbers) / sizeof(numbers[0]), - sizeof(numbers[0]), compare_key); - if (p == NULL) - return -1; - return p->number; -} - -/* - * Construct a syscall tables ordered by increasing syscall number - * @returns number of syscall entries in the table - */ -int construct_table(const struct bpf_call *entries, int n, - struct syscall_entry *table) -{ - long nr; - unsigned int tn; - int i; - - tn = 0; - for (i = 0; i < n; i++) { - table[i].count = 0; - table[i].entry = NULL; - } - - for (i = 0; i < n; i++) { - if (tn > N_SYSCALL - 1) - return -1; - if (i > 0) { - if (strcmp((entries[i]).name, (entries[i - 1]).name) == - 0) { - table[tn - 1].count++; - continue; - } - } - nr = resolve_syscall_nr((entries[i]).name); - if (nr < 0) { - fprintf(stderr, "wrong syscall number for %s\n", - (entries[i]).name); - continue; - } - table[tn].entry = &entries[i]; - table[tn].count++; - table[tn].nr = nr; - tn++; - } - qsort(table, tn, sizeof(struct syscall_entry), compare_table_nr); - - return tn; -} - -static unsigned get_n_args_syscall_entry(const struct bpf_call *entry) +static unsigned get_n_args_syscall_entry(const struct notify *entry) { unsigned i, n = 0; for (i = 0; i < 6; i++) - if (entry->args[i].cmp != NO_CHECK) + if (entry->arg[i].cmp != NO_CHECK) n++; return n; } -static unsigned int get_n_args_syscall_instr(const struct syscall_entry *table) +static unsigned int get_n_args_syscall_instr(const struct notify *table, + int len) { - const struct bpf_call *entry; + const struct notify *entry; bool has_arg = false; unsigned n = 0, total_instr = 0; + int i; - for (unsigned int i = 0; i < table->count; i++) { - entry = table->entry + i; + for (i = 0; i < len; i++) { + entry = table + i; n = 0; for (unsigned int k = 0; k < 6; k++) { - if (entry->args[k].cmp == NO_CHECK) + if (entry->arg[k].cmp == NO_CHECK) continue; - switch (entry->args[k].type) { - case U32: + switch (entry->arg[k].type) { + case BPF_U32: /* For 32 bit arguments * 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 (entry->arg[k].cmp == AND_EQ || + entry->arg[k].cmp == AND_NE) n += 3; else n += 2; break; - case U64: + case BPF_U64: /* For 64 bit arguments: 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 (entry->arg[k].cmp == AND_EQ || + entry->arg[k].cmp == AND_NE) n += 6; else n += 4; @@ -222,44 +154,34 @@ static unsigned int get_n_args_syscall_instr(const struct syscall_entry *table) return total_instr; } -static bool check_args_syscall_entry(const struct bpf_call *entry){ - return entry->args[0].cmp != NO_CHECK || - entry->args[1].cmp != NO_CHECK || - entry->args[2].cmp != NO_CHECK || - entry->args[3].cmp != NO_CHECK || - entry->args[4].cmp != NO_CHECK || entry->args[5].cmp != NO_CHECK; -} - -static bool check_args_syscall(const struct syscall_entry *table) -{ - for (unsigned int i = 0; i < table->count; i++) { - if (check_args_syscall_entry(table->entry + i)) - return true; - } - return false; +static bool check_args_syscall_entry(const struct notify *entry){ + return entry->arg[0].cmp != NO_CHECK || + entry->arg[1].cmp != NO_CHECK || + entry->arg[2].cmp != NO_CHECK || + entry->arg[3].cmp != NO_CHECK || + entry->arg[4].cmp != NO_CHECK || entry->arg[5].cmp != NO_CHECK; } static unsigned int eq(struct sock_filter filter[], int idx, - const struct bpf_call *entry, unsigned int jtrue, + const struct notify *entry, unsigned int jtrue, unsigned int jfalse) { unsigned int size = 0; uint32_t hi, lo; - switch (entry->args[idx].type) { - case U64: - hi = get_hi((entry->args[idx]).value.v64); - lo = get_lo((entry->args[idx]).value.v64); + switch (entry->arg[idx].type) { + case BPF_U64: + hi = get_hi((entry->arg[idx]).value.v64); + lo = get_lo((entry->arg[idx]).value.v64); filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); filter[size++] = (struct sock_filter)EQ(lo, 0, jfalse); filter[size++] = (struct sock_filter)LOAD(HI_ARG(idx)); filter[size++] = (struct sock_filter)EQ(hi, jtrue, jfalse); break; - case U32: - + case BPF_U32: filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); filter[size++] = (struct sock_filter)EQ( - entry->args[idx].value.v32, jtrue, jfalse); + entry->arg[idx].value.v32, jtrue, jfalse); break; } @@ -267,26 +189,25 @@ static unsigned int eq(struct sock_filter filter[], int idx, } static unsigned int gt(struct sock_filter filter[], int idx, - const struct bpf_call *entry, unsigned int jtrue, + const struct notify *entry, unsigned int jtrue, unsigned int jfalse) { unsigned int size = 0; uint32_t hi, lo; - switch (entry->args[idx].type) { - case U64: - hi = get_hi((entry->args[idx]).value.v64); - lo = get_lo((entry->args[idx]).value.v64); + switch (entry->arg[idx].type) { + case BPF_U64: + hi = get_hi((entry->arg[idx]).value.v64); + lo = get_lo((entry->arg[idx]).value.v64); filter[size++] = (struct sock_filter)LOAD(HI_ARG(idx)); filter[size++] = (struct sock_filter)GT(hi, jtrue + 2, 0); filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); filter[size++] = (struct sock_filter)GT(lo, jtrue, jfalse); break; - case U32: - + case BPF_U32: filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); filter[size++] = (struct sock_filter)GT( - entry->args[idx].value.v32, jtrue, jfalse); + entry->arg[idx].value.v32, jtrue, jfalse); break; } @@ -294,26 +215,25 @@ static unsigned int gt(struct sock_filter filter[], int idx, } static unsigned int lt(struct sock_filter filter[], int idx, - const struct bpf_call *entry, unsigned int jtrue, + const struct notify *entry, unsigned int jtrue, unsigned int jfalse) { unsigned int size = 0; uint32_t hi, lo; - switch (entry->args[idx].type) { - case U64: - hi = get_hi((entry->args[idx]).value.v64); - lo = get_lo((entry->args[idx]).value.v64); + switch (entry->arg[idx].type) { + case BPF_U64: + hi = get_hi((entry->arg[idx]).value.v64); + lo = get_lo((entry->arg[idx]).value.v64); filter[size++] = (struct sock_filter)LOAD(HI_ARG(idx)); filter[size++] = (struct sock_filter)LT(hi, jtrue + 2, jfalse); filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); filter[size++] = (struct sock_filter)LT(lo, jtrue, jfalse); break; - case U32: - + case BPF_U32: filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); filter[size++] = (struct sock_filter)LT( - entry->args[idx].value.v32, jtrue, jfalse); + entry->arg[idx].value.v32, jtrue, jfalse); break; } @@ -321,52 +241,51 @@ static unsigned int lt(struct sock_filter filter[], int idx, } static unsigned int neq(struct sock_filter filter[], int idx, - const struct bpf_call *entry, unsigned int jtrue, + const struct notify *entry, unsigned int jtrue, unsigned int jfalse) { return eq(filter, idx, entry, jfalse, jtrue); } static unsigned int ge(struct sock_filter filter[], int idx, - const struct bpf_call *entry, unsigned int jtrue, + const struct notify *entry, unsigned int jtrue, unsigned int jfalse) { return lt(filter, idx, entry, jfalse, jtrue); } static unsigned int le(struct sock_filter filter[], int idx, - const struct bpf_call *entry, unsigned int jtrue, + const struct notify *entry, unsigned int jtrue, unsigned int jfalse) { return gt(filter, idx, entry, jfalse, jtrue); } static unsigned int and_eq (struct sock_filter filter[], int idx, - const struct bpf_call *entry, unsigned int jtrue, + const struct notify *entry, unsigned int jtrue, unsigned int jfalse) { unsigned int size = 0; - switch (entry->args[idx].type) { - case U64: + switch (entry->arg[idx].type) { + case BPF_U64: filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); filter[size++] = (struct sock_filter)AND( - get_lo(entry->args[idx].op2.v64)); + get_lo(entry->arg[idx].op2.v64)); filter[size++] = (struct sock_filter)EQ( - get_lo((entry->args[idx]).value.v64), 0, jfalse); + get_lo((entry->arg[idx]).value.v64), 0, jfalse); filter[size++] = (struct sock_filter)LOAD(HI_ARG(idx)); filter[size++] = (struct sock_filter)AND( - get_hi(entry->args[idx].op2.v64)); + get_hi(entry->arg[idx].op2.v64)); filter[size++] = (struct sock_filter)EQ( - get_hi(entry->args[idx].value.v64), jtrue, jfalse); + get_hi(entry->arg[idx].value.v64), jtrue, jfalse); break; - case U32: - + case BPF_U32: filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); filter[size++] = - (struct sock_filter)AND(entry->args[idx].op2.v32); + (struct sock_filter)AND(entry->arg[idx].op2.v32); filter[size++] = (struct sock_filter)EQ( - entry->args[idx].value.v32, jtrue, jfalse); + entry->arg[idx].value.v32, jtrue, jfalse); break; } @@ -374,55 +293,52 @@ static unsigned int and_eq (struct sock_filter filter[], int idx, } static unsigned int and_ne(struct sock_filter filter[], int idx, - const struct bpf_call *entry, unsigned int jtrue, + const struct notify *entry, unsigned int jtrue, unsigned int jfalse) { unsigned int size = 0; - switch (entry->args[idx].type) { - case U64: + switch (entry->arg[idx].type) { + case BPF_U64: filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); filter[size++] = (struct sock_filter)AND( - get_lo(entry->args[idx].op2.v64)); + get_lo(entry->arg[idx].op2.v64)); filter[size++] = (struct sock_filter)EQ( - get_lo((entry->args[idx]).value.v64), 0, jtrue + 3); + get_lo((entry->arg[idx]).value.v64), 0, jtrue + 3); filter[size++] = (struct sock_filter)LOAD(HI_ARG(idx)); filter[size++] = (struct sock_filter)AND( - get_hi(entry->args[idx].op2.v64)); + get_hi(entry->arg[idx].op2.v64)); filter[size++] = (struct sock_filter)EQ( - get_hi(entry->args[idx].value.v64), jfalse, jtrue); + get_hi(entry->arg[idx].value.v64), jfalse, jtrue); break; - case U32: - + case BPF_U32: filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); filter[size++] = - (struct sock_filter)AND(entry->args[idx].op2.v32); + (struct sock_filter)AND(entry->arg[idx].op2.v32); filter[size++] = (struct sock_filter)EQ( - entry->args[idx].value.v32, jfalse, jtrue); + entry->arg[idx].value.v32, jfalse, jtrue); break; } return size; } -unsigned int create_bfp_program(struct syscall_entry table[], - struct sock_filter filter[], - unsigned int n_syscall) +unsigned int filter_build(struct sock_filter filter[], unsigned int n) { unsigned int offset_left, offset_right; unsigned int n_nodes, notify, accept; unsigned int next_offset, offset; - const struct bpf_call *entry; + const struct notify *entry; unsigned int size = 0; unsigned int next_args_off; int nodes[MAX_JUMPS]; unsigned int i, j, k; unsigned n_checks; - create_lookup_nodes(nodes, n_syscall); + create_lookup_nodes(nodes, n); /* No nodes if there is a single syscall */ - n_nodes = (1 << count_shift_right(n_syscall - 1)) - 1; + n_nodes = (1 << count_shift_right(n - 1)) - 1; /* Pre */ /* cppcheck-suppress badBitmaskCheck */ @@ -438,7 +354,7 @@ unsigned int create_bfp_program(struct syscall_entry table[], BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))); /* pre-check instruction + load syscall number (4 instructions) */ - accept = size + n_nodes + n_syscall; + accept = size + n_nodes + n; notify = accept + 1; /* Insert nodes */ @@ -450,21 +366,22 @@ unsigned int create_bfp_program(struct syscall_entry table[], offset_left = left_child(i) - i - 1; offset_right = right_child(i) - i - 1; filter[size++] = (struct sock_filter)JGE( - table[nodes[i]].nr, offset_right, offset_left); + notify_call[i].nr, offset_right, offset_left); } } - next_offset = n_syscall + 1; + next_offset = n + 1; /* Insert leaves */ - for (i = 0; i < n_syscall; i++) { + for (i = 0; i < n; i++) { /* If the syscall doesn't have any arguments, then notify */ - if (check_args_syscall(&table[i])) + if (check_args_syscall_entry(notify_call + i)) offset = next_offset; else offset = notify - size - 1; - filter[size++] = (struct sock_filter)EQ(table[i].nr, offset, + filter[size++] = (struct sock_filter)EQ(notify_call[i].nr, + offset, accept - size); - next_offset += get_n_args_syscall_instr(&table[i]) - 1; + next_offset += get_n_args_syscall_instr(notify_call + i, n) - 1; } /* Seccomp accept and notify instruction */ filter[size++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, @@ -477,15 +394,20 @@ unsigned int create_bfp_program(struct syscall_entry table[], * entry. If a check on the argument isn't equal then it jumps to * check the following entry of the syscall and its arguments. */ - for (i = 0; i < n_syscall; i++) { + for (i = 0; i < n; i++) { bool has_arg = false; - for (j = 0; j < (table[i]).count; j++) { + unsigned int count = 0, x; + + for (x = 0; x < 6; x++) + count += notify_call[i].arg[x].cmp == NO_CHECK; + + for (j = 0; j < count; j++) { n_checks = 0; - entry = table[i].entry + j; + entry = notify_call + i + j; next_args_off = get_n_args_syscall_entry(entry); for (k = 0; k < 6; k++) { offset = next_args_off - n_checks; - switch (entry->args[k].cmp) { + switch (entry->arg[k].cmp) { case NO_CHECK: continue; case EQ: @@ -525,7 +447,7 @@ unsigned int create_bfp_program(struct syscall_entry table[], n_checks++; has_arg = true; } - if (check_args_syscall_entry(table[i].entry)) + if (check_args_syscall_entry(notify_call + i)) filter[size++] = (struct sock_filter)BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF); @@ -541,31 +463,121 @@ unsigned int create_bfp_program(struct syscall_entry table[], return size; } -static int compare_names(const void *a, const void *b) -{ - return strcmp(((struct syscall_numbers *)a)->name, - ((struct syscall_numbers *)b)->name); +/** + * struct filter_call_input - First input stage for cooker notification requests + * @notify: Notify on this system call + * @no_args: No argument comparisons are allowed for this call + * @args_set: Argument matches were already set up once for this call + * @arg: Argument specification + */ +struct filter_call_input { + bool notify; + bool no_args; + bool args_set; + struct bpf_arg arg[6]; +} filter_input[512] = { 0 }; + +static struct { + bool used; + struct bpf_arg arg[6]; +} filter_current_args; + +static long current_nr; + +/** + * 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; + long prev_nr = current_nr; + + if (nr >= 0) { + current_nr = nr; + call->notify = true; + } + + if (filter_current_args.used) { + struct filter_call_input *prev_call = filter_input + prev_nr; + + /* First time arguments for previous call are flushed? */ + if (!prev_call->args_set && !prev_call->no_args) { + prev_call->args_set = true; + memcpy(prev_call->arg, filter_current_args.arg, + sizeof(filter_current_args.arg)); + return; + } + + prev_call->args_set = true; + + /* ...not the first time: check exact overlap of matches */ + if (memcmp(prev_call->arg, filter_current_args.arg, + sizeof(filter_current_args.arg))) + prev_call->no_args = true; + + /* Flush temporary set of arguments */ + memset(&filter_current_args, 0, sizeof(filter_current_args)); + } +} + +/** + * filter_needs_deref() - Mark system call as ineligible for argument evaluation + */ +void filter_needs_deref(void) { + struct filter_call_input *call = filter_input + current_nr; + + call->no_args = true; } -int convert_bpf(char *file, struct bpf_call *entries, int n) +/** + * Use temporary filter_call_cur_args storage. When there's a new notification, + * or the parser is done, we flush these argument matches to filter_input, and + * check if they match (including no-matches) all the previous argument + * specification. If they don't, the arguments can't be evaluated in the filter. + */ +void filter_add_arg(int index, struct bpf_arg arg) { + struct filter_call_input *call = filter_input + current_nr; + + if (call->no_args) + return; + + memcpy(filter_current_args.arg + index, &arg, sizeof(arg)); + filter_current_args.used = true; +} + +unsigned int filter_close_input(void) { - int nt, fd, fsize; - struct syscall_entry table[N_SYSCALL]; - struct sock_filter filter[MAX_FILTER]; + struct notify *call = notify_call; + int i, count = 0; - qsort(numbers, sizeof(numbers) / sizeof(numbers[0]), sizeof(numbers[0]), - compare_names); + filter_notify(-1); - qsort(entries, n, sizeof(struct bpf_call), compare_bpf_call_names); - nt = construct_table(entries, n, table); + for (i = 0; i < 512; i++) { + if (filter_input[i].notify) { + count++; + call->nr = i; - fsize = create_bfp_program(table, filter, nt); + if (filter_input[i].no_args) + continue; - fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, - S_IRUSR | S_IWUSR); - write(fd, filter, sizeof(struct sock_filter) * fsize); + memcpy(call->arg, filter_input[i].arg, + sizeof(call->arg)); + } + } - close(fd); + return count; +} - return 0; +void filter_write(const char *path) +{ + struct sock_filter filter[MAX_FILTER]; + int fd, n; + + n = filter_close_input(); + n = filter_build(filter, n); + + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, + S_IRUSR | S_IWUSR); + write(fd, filter, sizeof(struct sock_filter) * n); + close(fd); } diff --git a/cooker/filter.h b/cooker/filter.h index 205fa06..7059a7c 100644 --- a/cooker/filter.h +++ b/cooker/filter.h @@ -44,40 +44,28 @@ #define MAX_JUMPS 128 #define EMPTY -1 -enum arg_type { U32, U64 }; +enum bpf_type { BPF_U32, BPF_U64 }; -union arg_value { +union bpf_value { uint32_t v32; uint64_t v64; }; -enum arg_cmp { NO_CHECK, EQ, NE, LE, LT, GE, GT, AND_EQ, AND_NE }; +enum bpf_cmp { NO_CHECK = 0, EQ, NE, LE, LT, GE, GT, AND_EQ, AND_NE }; -struct arg { - union arg_value value; - enum arg_type type; - enum arg_cmp cmp; - union arg_value op2; +struct bpf_arg { + union bpf_value value; + enum bpf_type type; + enum bpf_cmp cmp; + union bpf_value op2; }; -struct bpf_call { - char *name; - struct arg args[6]; -}; - -struct syscall_entry { - unsigned int count; - long nr; - const struct bpf_call *entry; -}; +void filter_notify(long nr); +void filter_needs_deref(void); +void filter_add_arg(int index, struct bpf_arg arg); +void filter_write(const char *path); void create_lookup_nodes(int jumps[], unsigned int n); unsigned int left_child(unsigned int parent_index); unsigned int right_child(unsigned int parent_index); - -unsigned int create_bfp_program(struct syscall_entry table[], - struct sock_filter filter[], - unsigned int n_syscall); -int convert_bpf(char *file, struct bpf_call *entries, int n); - #endif diff --git a/cooker/gluten.c b/cooker/gluten.c index 6460798..05d408f 100644 --- a/cooker/gluten.c +++ b/cooker/gluten.c @@ -32,6 +32,15 @@ size_t gluten_size[TYPE_COUNT] = { }; +const char *jump_name[JUMP_COUNT] = { "next block", "next match", "end" }; + +/** + * gluten_alloc() - Allocate in temporary (seitan read-write) data area + * @g: gluten context + * @size: Bytes to allocate + * + * Return: offset to allocated area + */ struct gluten_offset gluten_alloc(struct gluten_ctx *g, size_t size) { struct gluten_offset ret = g->dp; @@ -43,16 +52,59 @@ struct gluten_offset gluten_alloc(struct gluten_ctx *g, size_t size) return ret; } +/** + * gluten_alloc() - Allocate storage for given type in temporary data area + * @g: gluten context + * @type: Data type + * + * Return: offset to allocated area + */ struct gluten_offset gluten_alloc_type(struct gluten_ctx *g, enum type type) { return gluten_alloc(g, gluten_size[type]); } -void gluten_init(struct gluten_ctx *g) +void gluten_add_tag(struct gluten_ctx *g, const char *name, + struct gluten_offset offset) { - (void)g; + int i; + + for (i = 0; i < TAGS_MAX && g->tags[i].name; i++); + if (i == TAGS_MAX) + die("Too many tags"); + + g->tags[i].name = name; + g->tags[i].offset = offset; + + debug(" tag '%s' now refers to %s at %i", + name, gluten_offset_name[offset.type], offset.offset); +} - g->ip.type = g->lr.type = OFFSET_INSTRUCTION; +/** + * gluten_init() - Initialise gluten structures and layout + * @g: gluten context + */ +void gluten_init(struct gluten_ctx *g) +{ + g->ip.type = g->lr.type = g->mr.type = OFFSET_INSTRUCTION; + g->ip.offset = g->lr.offset = g->mr.offset = 0; g->dp.type = OFFSET_DATA; g->cp.type = OFFSET_RO_DATA; } + +void gluten_write(struct gluten_ctx *g, const char *path) +{ + ssize_t n; + int fd; + + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, + S_IRUSR | S_IWUSR); + + if ((n = write(fd, &g->g, sizeof(g->g))) == -1) + die("Failed to write gluten: %s", strerror(errno)); + + if (n != sizeof(g->g)) + die("Failed to write %i bytes of gluten", sizeof(g->g) - n); + + close(fd); +} diff --git a/cooker/gluten.h b/cooker/gluten.h index a48cd6d..edd3240 100644 --- a/cooker/gluten.h +++ b/cooker/gluten.h @@ -14,8 +14,8 @@ struct gluten_arg_data { size_t len; }; -struct gluten_ref_data { - char name[REF_NAMEMAX]; +struct gluten_tag_data { + const char *name; struct gluten_offset offset; size_t len; }; @@ -23,6 +23,7 @@ struct gluten_ref_data { struct gluten_ctx { struct gluten_offset ip; struct gluten_offset lr; + struct gluten_offset mr; struct gluten_offset cp; struct gluten_offset dp; @@ -31,14 +32,30 @@ struct gluten_ctx { struct gluten_arg_data match_dst[CALL_ARGS]; struct gluten_arg_data call_src[CALL_ARGS]; - struct gluten_ref_data refs[REFS_MAX]; + struct gluten_tag_data tags[TAGS_MAX]; + + struct arg *selected_arg[6]; +}; + +/** + * enum jump_type - Indicate direction of jump before linking phase + */ +enum jump_type { + JUMP_NEXT_BLOCK, + JUMP_NEXT_MATCH, + JUMP_END, + JUMP_COUNT, }; struct gluten_offset gluten_alloc(struct gluten_ctx *g, size_t size); struct gluten_offset gluten_alloc_type(struct gluten_ctx *g, enum type type); +void gluten_add_tag(struct gluten_ctx *g, const char *name, + struct gluten_offset offset); void gluten_init(struct gluten_ctx *g); void gluten_block_init(struct gluten_ctx *g); +void gluten_write(struct gluten_ctx *g, const char *path); extern size_t gluten_size[TYPE_COUNT]; +extern const char *jump_name[JUMP_COUNT]; #endif /* GLUTEN_H */ diff --git a/cooker/main.c b/cooker/main.c index 9965cff..5512d54 100644 --- a/cooker/main.c +++ b/cooker/main.c @@ -11,18 +11,29 @@ #include "cooker.h" #include "gluten.h" #include "parse.h" +#include "filter.h" +/** + * main() - Entry point for cooker + * @argc: Argument count + * @argv: Options: input filename, output filename + * + * Return: zero on success, doesn't return on failure + */ int main(int argc, char **argv) { struct gluten_ctx g = { 0 }; /* TODO: Options and usage */ - (void)argc; - (void)argv; + if (argc != 4) + die("%s INPUT GLUTEN BPF", argv[0]); gluten_init(&g); parse_file(&g, argv[1]); + gluten_write(&g, argv[2]); + filter_write(argv[3]); + return 0; } diff --git a/cooker/match.c b/cooker/match.c new file mode 100644 index 0000000..bd96a03 --- /dev/null +++ b/cooker/match.c @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/* seitan - Syscall Expressive Interpreter, Transformer and Notifier + * + * cooker/match.c - Parse "match" rules from JSON recipe into bytecode + * + * Copyright 2023 Red Hat GmbH + * Author: Stefano Brivio <sbrivio@redhat.com> + */ + +#include "parson.h" +#include "calls.h" +#include "cooker.h" +#include "gluten.h" +#include "emit.h" +#include "filter.h" +#include "util.h" + +#include "calls/net.h" + +/** + * struct rule_parser - Parsing handler for JSON rule type + * @type: JSON key name + * @fn: Parsing function + */ +struct rule_parser { + const char *type; + int (*fn)(struct gluten_ctx *g, JSON_Value *value); +}; + +/** + * arg_load() - Allocate and build bytecode for one syscall argument + * @g: gluten context + * @a: Argument description from model + * + * Return: offset where (full) argument is stored + */ +static struct gluten_offset arg_load(struct gluten_ctx *g, struct arg *a) +{ + int index = a->pos; + size_t size; + + if (a->type == SELECTED) { + if (g->selected_arg[index]->type != UNDEF) + size = g->selected_arg[index]->size; + else + die(" no storage size for argument %s", a->name); + } else { + size = a->size; + } + + if (!size) { + g->match_dst[index].offset.type = OFFSET_SECCOMP_DATA; + g->match_dst[index].offset.offset = index; + g->match_dst[index].len = 0; + return g->match_dst[index].offset; + } + + filter_needs_deref(); + + if (g->match_dst[index].len) /* Already allocated */ + return g->match_dst[index].offset; + + g->match_dst[index].offset = gluten_alloc(g, size); + g->match_dst[index].len = size; + + emit_load(g, g->match_dst[index].offset, index, size); + + return g->match_dst[index].offset; +} + +/** + * value_get_num() - Get numeric value from description matching JSON input + * @desc: Description of possible values from model + * @value: JSON value + * + * Return: numeric value + */ +static long long value_get_num(struct num *desc, JSON_Value *value) +{ + const char *s = NULL; + long long n; + + if (desc) { + s = json_value_get_string(value); + for (; desc->name && s && strcmp(s, desc->name); desc++); + if (s && !desc->name) + die(" Invalid value %s", s); + + n = desc->value; + } + + if (!s) { + if (json_value_get_type(value) != JSONNumber) + die(" Invalid value type"); + + n = json_value_get_number(value); + } + + return n; +} + +/** + * value_get() - Get generic value from description matching JSON input + * @desc: Description of possible values from model + * @type: Data type from model + * @value: JSON value + * @out: Corresponding bytecode value, set on return + */ +static void value_get(union desc desc, enum type type, JSON_Value *value, + union value *out) +{ + if (TYPE_IS_NUM(type)) + out->v_num = value_get_num(desc.d_num, value); +} + +/** + * select_desc() - Get description and type for selected value + * @g: gluten context + * @s: Possible selection choices + * @v: Selector value + * @pos: Index of syscall argument being parsed + * @type: Type of selected value, set on return + * @desc: Description of selected value, set on return + */ +static void select_desc(struct gluten_ctx *g, struct select *s, union value v, + int pos, enum type *type, union desc *desc) +{ + if (TYPE_IS_NUM(s->field->type)) { + struct select_num *d_num; + + for (d_num = s->desc.d_num; d_num->target.type; d_num++) { + if (d_num->value == v.v_num) { + if (d_num->target.pos == pos) { + *type = d_num->target.type; + *desc = d_num->target.desc; + } else { + pos = d_num->target.pos; + g->selected_arg[pos] = &d_num->target; + *type = NONE; + } + + return; + } + } + + if (!d_num->target.type) + die(" No match for numeric selector %i", v.v_num); + } + + die(" not supported yet"); +} + +/** + * parse_value() - Parse JSON value for generic item of data description + * @g: gluten context + * @index: Index of parent syscall argument + * @offset: Base offset of container field (actual offset for non-compound) + * @type: Data type, from model + * @str_len: Length of string, valid for STRING type only + * @desc: Description of possible values, from model + * @value: JSON value + */ +static void parse_value(struct gluten_ctx *g, int index, + struct gluten_offset offset, enum type type, + size_t str_len, union desc desc, JSON_Value *value) +{ + struct gluten_offset data_offset, const_offset; + const char *tag_name; + JSON_Object *tmp; + struct field *f; + union value v; + + if (type == SELECT) { + struct select *select = desc.d_select; + struct field *field = select->field; + JSON_Value *sel; + + if ((tmp = json_value_get_object(value))) { + if (!(sel = json_object_get_value(tmp, field->name))) + die(" no selector for '%s'", field->name); + } else { + sel = value; + } + + value_get(field->desc, field->type, sel, &v); + const_offset = emit_data(g, field->type, field->strlen, &v); + + data_offset = offset; + data_offset.offset += field->offset; + + emit_cmp_field(g, CMP_NE, field, data_offset, const_offset, + JUMP_NEXT_BLOCK); + + select_desc(g, select, v, index, &type, &desc); + + if (type == NONE) + return; + } + + if (json_value_get_type(value) == JSONObject && + (tmp = json_value_get_object(value)) && + (tag_name = json_object_get_string(tmp, "tag"))) { + if (TYPE_IS_COMPOUND(type)) + die("Tag reference '%s' to compound value", tag_name); + + debug(" setting tag reference '%s'", tag_name); + gluten_add_tag(g, tag_name, offset); + + value = json_object_get_value(tmp, "value"); + } + + /* Nothing to match on: just store as reference */ + if (!value) + return; + + switch (type) { + case INTFLAGS: + case LONGFLAGS: + case U32FLAGS: + /* fetch/combine expr algebra loop */ + case INTMASK: + /* calculate mask first */ + break; + case INT: + case LONG: + case U32: + v.v_num = value_get_num(desc.d_num, value); + const_offset = emit_data(g, type, 0, &v); + emit_cmp(g, CMP_NE, offset, const_offset, gluten_size[type], + JUMP_NEXT_BLOCK); + break; + case SELECT: + /* TODO: check how nested selects should work */ + parse_value(g, index, offset, type, 0, desc, value); + break; + case STRING: + v.v_str = json_value_get_string(value); + if (strlen(v.v_str) + 1 > str_len) + die(" string %s too long for field", v.v_str); + + const_offset = emit_data(g, STRING, strlen(v.v_str) + 1, &v); + emit_cmp(g, CMP_NE, offset, const_offset, strlen(v.v_str) + 1, + JUMP_NEXT_BLOCK); + break; + case STRUCT: + for (f = desc.d_struct; f->name; f++) { + JSON_Value *field_value; + + tmp = json_value_get_object(value); + field_value = json_object_get_value(tmp, f->name); + if (!field_value) + continue; + + parse_value(g, index, offset, f->type, f->strlen, + f->desc, field_value); + } + default: + ; + } +} + +/** + * parse_arg() - Parse syscall argument from JSON, following model + * @g: gluten context + * @name: Name of argument (key) in JSON and model + * @value: JSON value for argument + * @a: Argument description from model + */ +static void parse_arg(struct gluten_ctx *g, const char *name, JSON_Value *value, + struct arg *a) +{ + struct gluten_offset offset; + + debug(" Parsing match argument %s", name); + + offset = arg_load(g, a); + + parse_value(g, a->pos, offset, a->type, a->size, a->desc, value); +} + +/** + * parse_match() - Parse one syscall rule in "match" array + * @g: gluten context + * @obj: Matching rule for one syscall + * @args: Description of arguments from syscall model + */ +static void parse_match(struct gluten_ctx *g, JSON_Object *obj, + struct arg *args) +{ + unsigned count = 0; + struct arg *a; + + for (a = args; a->name; a++) { + struct arg *real_arg = a; + JSON_Value *value; + + if (a->type == SELECTED) { + if (!(real_arg = g->selected_arg[a->pos])) + die(" No selection for argument %s", a->name); + } + + if ((value = json_object_get_value(obj, real_arg->name))) { + count++; + parse_arg(g, real_arg->name, value, real_arg); + } + } + + if (count != json_object_get_count(obj)) + die(" Stray elements in match"); +} + +/** + * handle_matches() - Parse "match" array, find syscall models + * @g: gluten context + * @value: "match" object containing array of rules + */ +void handle_matches(struct gluten_ctx *g, JSON_Value *value) +{ + JSON_Array *matches = json_value_get_array(value); + unsigned i; + + for (i = 0; i < json_array_get_count(matches); i++) { + JSON_Object *match, *args; + struct call **set, *call; + const char *name; + + g->mr = g->ip; + + match = json_array_get_object(matches, i); + name = json_object_get_name(match, 0); + args = json_object_get_object(match, name); + debug(" Parsing match %i: %s", i, name); + + for (set = call_sets, call = set[0]; *set; ) { + if (!call->name) { + set++; + call = set[0]; + continue; + } + + if (!strcmp(name, call->name)) { + union value v = { .v_num = call->number }; + + debug(" Found description for %s", name); + emit_nr(g, emit_data(g, U64, 0, &v)); + + filter_notify(call->number); + + parse_match(g, args, call->args); + break; + } + call++; + } + + if (!*set) + die(" Unknown system call: %s", name); + + link_match(g); + } +} diff --git a/cooker/match.h b/cooker/match.h new file mode 100644 index 0000000..95c7b4d --- /dev/null +++ b/cooker/match.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2023 Red Hat GmbH + * Author: Stefano Brivio <sbrivio@redhat.com> + */ + +#ifndef MATCH_H +#define MATCH_H + +void handle_matches(struct gluten_ctx *g, JSON_Value *value); + +#endif /* MATCH_H */ diff --git a/cooker/parse.c b/cooker/parse.c index 0a87088..09b1e46 100644 --- a/cooker/parse.c +++ b/cooker/parse.c @@ -12,260 +12,80 @@ #include "calls.h" #include "cooker.h" #include "gluten.h" +#include "match.h" #include "emit.h" #include "util.h" #include "calls/net.h" -struct rule_parser { - const char *type; - int (*fn)(struct gluten_ctx *g, JSON_Value *value); -}; - -static int parse_match_load(struct gluten_ctx *g, struct arg *a) -{ - if (!a->size || g->match_dst[a->pos].len) - return 0; - - g->match_dst[a->pos].offset = gluten_alloc(g, a->size); - g->match_dst[a->pos].len = a->size; - - emit_load(g, g->match_dst[a->pos].offset, a->pos, a->size); - - return 0; -} - -static long long parse_match_expr_num(struct num *desc, JSON_Value *value) +static void handle_call(struct gluten_ctx *g, JSON_Value *value) { - const char *s = NULL; - long long n; - - if (desc) { - s = json_value_get_string(value); - for (; desc->name && s && strcmp(s, desc->name); desc++); - if (s && !desc->name) - die(" Invalid value %s", s); - - n = desc->value; - } - - if (!s) { - if (json_value_get_type(value) != JSONNumber) - die(" Invalid value type"); - - n = json_value_get_number(value); - } - - return n; -} - -static void parse_match_expr_value(union desc desc, enum type type, - JSON_Value *value, union value *out) -{ - if (TYPE_IS_NUM(type)) - out->v_num = parse_match_expr_num(desc.d_num, value); -} - -/** - * parse_match_select() - Get description and type for selected value - * @s: Possible selection choices - * @v: Selector value - * @type: Type of selected value, set on return - * @desc: Description of selected value, set on return - */ -static void parse_match_select(struct select *s, union value v, - enum type *type, union desc *desc) -{ - if (TYPE_IS_NUM(s->field->type)) { - struct select_num *d_num; - - for (d_num = s->desc.d_num; d_num->type; d_num++) { - if (d_num->value == v.v_num) { - *type = d_num->type; - *desc = d_num->desc; - return; - } - } - - if (!d_num->type) - die(" No match for numeric selector %i", v.v_num); - } - - die(" not supported yet"); -} - -/* - * parse_match_arg() - * load argument - * parse_match_key() - * compound types? demux, parse_match_expr - * parse_match_expr - * in/all/not/false-true array: demux, parse_match_expr_{num,string} - * parse_match_expr_{num,string} - * - * at terminal values - * store ref *pointers*! no copies - * emit additional bpf statement if syscall arg is also terminal -*/ -static int parse_match_key(struct gluten_ctx *g, int index, enum type type, - union desc desc, JSON_Value *value) -{ - JSON_Object *tmp; - const char *ref; - - if (type == SELECT) { - struct gluten_offset base_offset, const_offset; - struct select *select = desc.d_select; - struct field *field = select->field; - JSON_Value *selector; - union value v; - - if (!(tmp = json_value_get_object(value))) - die(" no object for compound value"); - - if (!(selector = json_object_get_value(tmp, field->name))) - die(" missing selector for '%s'", field->name); - - parse_match_expr_value(field->desc, field->type, selector, &v); - - base_offset = g->match_dst[index].offset; - const_offset = emit_data(g, field->type, &v); - emit_cmp_field(g, CMP_NE, field, base_offset, const_offset, - NEXT_BLOCK); - - parse_match_select(select, v, &type, &desc); - } - - if (json_value_get_type(value) == JSONObject && - (tmp = json_value_get_object(value)) && - (ref = json_object_get_string(tmp, "ref"))) { - if (TYPE_IS_COMPOUND(type)) - die("Reference '%s' to compound value"); - - debug(" setting reference '%s'", ref); - value = json_object_get_value(tmp, "value"); - - emit_load(g, gluten_alloc_type(g, type), index, type); - } - - /* Nothing to match on: just store as reference */ - if (!value) - return 0; - - switch (type) { - case INTFLAGS: - case LONGFLAGS: - case U32FLAGS: - /* fetch/combine expr algebra loop */ - case INTMASK: - /* calculate mask first */ - case INT: - case LONG: - case U32: - parse_match_expr_num(desc.d_num, value); - //emit_cmp(...); - default: - ; - } - - return 0; + (void)g; + (void)value; } -static int parse_match_arg(struct gluten_ctx *g, const char *name, - JSON_Value *value, struct arg *a) +static void handle_inject(struct gluten_ctx *g, JSON_Value *value) { - debug(" Parsing match argument %s", name); - - parse_match_load(g, a); - parse_match_key(g, a->pos, a->type, a->desc, value); - - return 0; + (void)g; + (void)value; } -static int parse_match(struct gluten_ctx *g, JSON_Object *obj, struct arg *args) +static void handle_limit(struct gluten_ctx *g, JSON_Value *value) { - unsigned count = 0; - struct arg *a; - - for (a = args; a->name; a++) { - JSON_Value *value; - - if ((value = json_object_get_value(obj, a->name))) { - count++; - parse_match_arg(g, a->name, value, a); - } - } - - if (count != json_object_get_count(obj)) - die(" Stray elements in match"); - - return 0; + (void)g; + (void)value; } -static int parse_matches(struct gluten_ctx *g, JSON_Value *value) +static void handle_return(struct gluten_ctx *g, JSON_Value *value) { - JSON_Array *matches = json_value_get_array(value); - unsigned i; - - for (i = 0; i < json_array_get_count(matches); i++) { - JSON_Object *match, *args; - struct call **set, *call; - const char *name; - - g->lr = g->ip; - - match = json_array_get_object(matches, i); - name = json_object_get_name(match, 0); - args = json_object_get_object(match, name); - debug(" Parsing match %i: %s", i, name); - - for (set = call_sets, call = set[0]; *set; call++) { - if (!call->name) { - set++; - continue; - } - - if (!strcmp(name, call->name)) { - debug(" Found handler for %s", name); - emit_nr(g, call->number); - - parse_match(g, args, call->args); - break; - } - } - - if (!*set) - die(" Unknown system call: %s", name); - } - - return 0; + (void)g; + (void)value; } -static int parse_call(struct gluten_ctx *g, JSON_Value *value) +static void handle_block(struct gluten_ctx *g, JSON_Value *value) { (void)g; (void)value; - return 0; } -static int parse_inject(struct gluten_ctx *g, JSON_Value *value) +static void handle_context(struct gluten_ctx *g, JSON_Value *value) { (void)g; (void)value; - return 0; } -struct rule_parser parsers[] = { - { "match", parse_matches }, - { "call", parse_call }, - { "inject", parse_inject }, +/** + * struct rule_parser - Parsing handler for JSON rule type + * @type: JSON key name + * @fn: Parsing function + */ +struct rule_parser { + const char *type; + void (*fn)(struct gluten_ctx *g, JSON_Value *value); +} parsers[] = { + { "match", handle_matches }, + { "call", handle_call }, + { "inject", handle_inject }, + { "limit", handle_limit }, + { "return", handle_return }, + { "block", handle_block }, + { "context", handle_context }, { NULL, NULL }, }; -static int parse_block(struct gluten_ctx *g, JSON_Object *block) +/** + * parse_block() - Parse a transformation block with rules + * @g: gluten context + * @block: Array of rules in block + */ +static void parse_block(struct gluten_ctx *g, JSON_Object *block) { unsigned i; + memset(g->selected_arg, 0, sizeof(g->selected_arg)); + memset(g->tags, 0, sizeof(g->tags)); + g->lr = g->ip; + for (i = 0; i < json_object_get_count(block); i++) { struct rule_parser *parser; JSON_Value *rule; @@ -285,10 +105,15 @@ static int parse_block(struct gluten_ctx *g, JSON_Object *block) die(" Invalid rule type: \"%s\"", type); } - return 0; + link_block(g); } -int parse_file(struct gluten_ctx *g, const char *path) +/** + * parse_file() - Entry point for parsing of a JSON input file + * @g: gluten context + * @path: Input file path + */ +void parse_file(struct gluten_ctx *g, const char *path) { JSON_Array *blocks; JSON_Value *root; @@ -306,6 +131,4 @@ int parse_file(struct gluten_ctx *g, const char *path) debug("Parsing block %i", i); parse_block(g, obj); } - - return 0; } diff --git a/cooker/parse.h b/cooker/parse.h index cb4a2f2..05079cc 100644 --- a/cooker/parse.h +++ b/cooker/parse.h @@ -6,6 +6,6 @@ #ifndef PARSE_H #define PARSE_H -int parse_file(struct gluten_ctx *g, const char *path); +void parse_file(struct gluten_ctx *g, const char *path); #endif /* PARSE_H */ diff --git a/operations.c b/operations.c index d404b87..f4adb0a 100644 --- a/operations.c +++ b/operations.c @@ -357,7 +357,7 @@ int op_cmp(const struct seccomp_notif *req, int notifier, struct gluten *g, (res < 0 && (cmp == CMP_LT || cmp == CMP_LE)) || (res > 0 && (cmp == CMP_GT || cmp == CMP_GE)) || (res != 0 && (cmp == CMP_NE))) - return op->jmp; + return op->jmp.offset; /* TODO: check boundaries */ return 0; } @@ -385,10 +385,10 @@ int op_resolve_fd(const struct seccomp_notif *req, int notifier, return 0; } -int eval(struct gluten *g, struct op *ops, const struct seccomp_notif *req, +int eval(struct gluten *g, const struct seccomp_notif *req, int notifier) { - struct op *op = ops; + struct op *op = (struct op *)g->inst; while (op->type != OP_END) { switch (op->type) { diff --git a/operations.h b/operations.h index f68ea89..6904b4f 100644 --- a/operations.h +++ b/operations.h @@ -40,8 +40,7 @@ struct arg_clone { }; int do_call(struct arg_clone *c); -int eval(struct gluten *g, struct op *ops, const struct seccomp_notif *req, - int notifier); +int eval(struct gluten *g, const struct seccomp_notif *req, int notifier); int op_call(const struct seccomp_notif *req, int notifier, struct gluten *g, struct op_call *op); int op_block(const struct seccomp_notif *req, int notifier, struct gluten *g, @@ -184,10 +184,10 @@ int main(int argc, char **argv) struct epoll_event ev, events[EPOLL_EVENTS]; struct seccomp_notif *req = (struct seccomp_notif *)req_b; struct arguments arguments; - struct op operations[INST_MAX]; char path[PATH_MAX + 1]; bool running = true; int pidfd, notifier; + struct gluten g; int fd, epollfd; int notifierfd; int nevents, i; @@ -195,7 +195,8 @@ int main(int argc, char **argv) arguments.pid = -1; parse(argc, argv, &arguments); fd = open(arguments.input_file, O_CLOEXEC | O_RDONLY); - /* TODO: Load bytecode */ + if (read(fd, &g, sizeof(g)) != sizeof(g)) + die("Failed to read gluten file"); close(fd); if (arguments.pid > 0) { @@ -237,7 +238,7 @@ int main(int argc, char **argv) /* The notifier fd was closed by the target */ running = false; } else if (notifier == events[i].data.fd) { - eval(NULL, &operations[0], req, notifier); + eval(&g, req, notifier); } } } |