diff options
Diffstat (limited to 'cooker')
-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 |
22 files changed, 1291 insertions, 509 deletions
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 */ |