diff options
author | Stefano Brivio <sbrivio@redhat.com> | 2023-05-02 09:48:50 +0200 |
---|---|---|
committer | Stefano Brivio <sbrivio@redhat.com> | 2023-05-02 10:39:32 +0200 |
commit | 82b77505f9420f11d614c2ae0f74153ca4ee3cb5 (patch) | |
tree | ec93abd39cd8c7ff2818c7446da6cc01864b2ae1 | |
parent | a2c20b2caf4e9cedb32b0930ffd7ca2e9ec72086 (diff) | |
download | seitan-82b77505f9420f11d614c2ae0f74153ca4ee3cb5.tar seitan-82b77505f9420f11d614c2ae0f74153ca4ee3cb5.tar.gz seitan-82b77505f9420f11d614c2ae0f74153ca4ee3cb5.tar.bz2 seitan-82b77505f9420f11d614c2ae0f74153ca4ee3cb5.tar.lz seitan-82b77505f9420f11d614c2ae0f74153ca4ee3cb5.tar.xz seitan-82b77505f9420f11d614c2ae0f74153ca4ee3cb5.tar.zst seitan-82b77505f9420f11d614c2ae0f74153ca4ee3cb5.zip |
cooker updates spilling all over the place
Only tangentially related:
- make seitan C99 again, so that I can build cooker without warnings
- make Makefiles make use of the usual conventions about assigning
directory paths in variables, drop numbers.h as requirement for
cooker and make it convenient to run stand-alone Makefiles
- fix up nr_syscalls.sh to be POSIX, otherwise it will give syntax
errors on my system
- define a single, common way to refer to offsets in gluten, and
functions to use those offsets in a safe way. Immediates are gone:
cooker will write any bit of "data" to the read-only section
- call const what has to be const
- define on-disk layout for gluten
- add OP_NR (to check syscall numbers), rename OP_COPY_ARGS to
OP_LOAD (it loads _selected_ stuff from arguments)
As for cooker itself:
- drop ARG_ and arg_ prefixes from struct names, and similar
- add/rework functions to build OP_NR, OP_LOAD, OP_CMP, and to
write constant data to gluten
- add parsing for "compound" arguments, but that's not completely
hooked into evaluation for numeric arguments yet
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
-rw-r--r-- | Makefile | 9 | ||||
-rw-r--r-- | common/gluten.h | 157 | ||||
-rw-r--r-- | common/util.h | 97 | ||||
-rw-r--r-- | cooker/Makefile | 36 | ||||
-rw-r--r-- | cooker/calls/net.c | 91 | ||||
-rw-r--r-- | cooker/cooker.h | 97 | ||||
-rw-r--r-- | cooker/emit.c | 94 | ||||
-rw-r--r-- | cooker/emit.h | 14 | ||||
-rw-r--r-- | cooker/gluten.c | 44 | ||||
-rw-r--r-- | cooker/gluten.h | 28 | ||||
-rw-r--r-- | cooker/parse.c | 100 | ||||
-rw-r--r-- | eater/Makefile | 2 | ||||
-rw-r--r-- | operations.c | 144 | ||||
-rw-r--r-- | operations.h | 3 | ||||
-rwxr-xr-x | scripts/nr_syscalls.sh | 44 |
15 files changed, 677 insertions, 283 deletions
@@ -3,14 +3,15 @@ # seitan - Syscall Expressive Interpreter, Transformer and Notifier # # Copyright 2023 Red Hat GmbH -# Authors: Alice Frosi <afrosi@redhat.com>, Stefano Brivio <sbrivio@redhat.com> +# Authors: Alice Frosi <afrosi@redhat.com> +# Stefano Brivio <sbrivio@redhat.com> DIR := $(shell pwd) -OUTDIR ?= $(DIR)/ +OUTDIR ?= $(DIR) export OUTDIR COMMON_DIR := $(DIR)/common -BIN := $(OUTDIR)seitan +BIN := $(OUTDIR)/seitan SRCS := seitan.c $(COMMON_DIR)/common.c $(COMMON_DIR)/util.c operations.c HEADERS := $(COMMON_DIR)/common.h $(COMMON_DIR)/gluten.h \ $(COMMON_DIR)/numbers.h $(COMMON_DIR)/util.h operations.h @@ -21,7 +22,7 @@ CFLAGS += -Wall -Wextra -pedantic -I$(COMMON_DIR) all: cooker eater seitan .PHONY: cooker -cooker: numbers.h +cooker: $(MAKE) -C cooker .PHONY: eater diff --git a/common/gluten.h b/common/gluten.h index eb965d9..61270d8 100644 --- a/common/gluten.h +++ b/common/gluten.h @@ -1,10 +1,49 @@ -#ifndef GLUTEN_H -#define GLUTEN_H +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2023 Red Hat GmbH + * Authors: Alice Frosi <afrosi@redhat.com> + * Stefano Brivio <sbrivio@redhat.com> + */ + +#ifndef COMMON_GLUTEN_H +#define COMMON_GLUTEN_H +#include <stddef.h> #include <stdint.h> #include <stdbool.h> +#include <linux/seccomp.h> + +#include "util.h" -#define MAX_FD_INJECTED 10 +extern struct seccomp_data anonymous_seccomp_data; + +#define HEADER_SIZE 4096 +#define INST_SIZE 4096 +#define RO_DATA_SIZE 4096 +#define DATA_SIZE 4096 + +#define INST_MAX 16 +#define OFFSET_MAX MAX(MAX(MAX(DATA_SIZE, RO_DATA_SIZE), \ + INST_MAX), \ + ARRAY_SIZE(anonymous_seccomp_data.args)) + +enum gluten_offset_type { + OFFSET_RO_DATA = 0, + OFFSET_DATA = 1, + OFFSET_SECCOMP_DATA = 2, + OFFSET_INSTRUCTION = 3, + OFFSET_TYPE_MAX = OFFSET_INSTRUCTION, +}; + +struct gluten_offset { +#ifdef __GNUC__ + enum gluten_offset_type type:BITS_PER_NUM(OFFSET_TYPE_MAX); +#else + uint16_t type:BITS_PER_NUM(OFFSET_TYPE_MAX); +#endif + uint16_t offset:BITS_PER_NUM(OFFSET_MAX); +}; + +BUILD_BUG_ON(BITS_PER_NUM(OFFSET_TYPE_MAX) + BITS_PER_NUM(OFFSET_MAX) > 16) enum ns_spec_type { NS_NONE, @@ -18,7 +57,7 @@ struct ns_spec { union { pid_t pid; char *path; - }; + } id; }; /* @@ -50,7 +89,7 @@ enum op_type { OP_INJECT, OP_INJECT_A, OP_RETURN, - OP_COPY_ARGS, + OP_LOAD, OP_END, OP_CMP, OP_RESOLVEDFD, @@ -61,12 +100,17 @@ enum value_type { REFERENCE, }; +struct op_nr { + long nr; + struct gluten_offset no_match; +}; + struct op_call { long nr; bool has_ret; void *args[6]; struct op_context context; - uint16_t ret_off; + struct gluten_offset ret; }; struct op_block { @@ -78,24 +122,12 @@ struct op_continue { }; struct op_return { - enum value_type type; - union { - int64_t value; - uint16_t value_off; - }; -}; - -struct fd_type { - enum value_type type; - union { - uint32_t fd; - uint16_t fd_off; - }; + struct gluten_offset val; }; struct op_inject { - struct fd_type newfd; - struct fd_type oldfd; + struct gluten_offset new_fd; + struct gluten_offset old_fd; }; struct copy_arg { @@ -104,8 +136,10 @@ struct copy_arg { size_t size; }; -struct op_copy_args { - struct copy_arg args[6]; +struct op_load { + struct gluten_offset src; + struct gluten_offset dst; + size_t size; }; enum op_cmp_type { @@ -118,8 +152,8 @@ enum op_cmp_type { }; struct op_cmp { - uint16_t s1_off; - uint16_t s2_off; + struct gluten_offset x; + struct gluten_offset y; size_t size; enum op_cmp_type cmp; unsigned int jmp; @@ -135,14 +169,77 @@ struct op_resolvedfd { struct op { enum op_type type; union { + struct op_nr nr; struct op_call call; struct op_block block; struct op_continue cont; struct op_return ret; - struct op_inject inj; - struct op_copy_args copy; + struct op_inject inject; + struct op_load load; struct op_cmp cmp; struct op_resolvedfd resfd; - }; -}; -#endif /* GLUTEN_H */ + } op; +}; + +#ifdef COOKER +# define GLUTEN_CONST +#else +# define GLUTEN_CONST const +#endif + +struct gluten { + GLUTEN_CONST char header[HEADER_SIZE]; + + GLUTEN_CONST char inst[INST_SIZE]; + + GLUTEN_CONST char ro_data[RO_DATA_SIZE]; + + char data[DATA_SIZE]; +} __attribute__((packed)); + +BUILD_BUG_ON(INST_SIZE < INST_MAX * sizeof(struct op)) + +#ifdef COOKER +static inline void *gluten_ptr(struct gluten *g, const struct gluten_offset x) +#else +static inline void *gluten_write_ptr(struct gluten *g, + const struct gluten_offset x) +#endif +{ + /* TODO: Boundary checks */ + + switch (x.type) { + case OFFSET_DATA: + return (char *)g->data + x.offset; +#ifdef COOKER + case OFFSET_RO_DATA: + return (char *)g->ro_data + x.offset; + case OFFSET_INSTRUCTION: + return (struct op *)(g->inst) + x.offset; +#endif + default: + return NULL; + } +} + +#ifndef COOKER +static inline const void *gluten_ptr(const struct seccomp_data *s, + struct gluten *g, + const struct gluten_offset x) +{ + switch (x.type) { + case OFFSET_DATA: + return g->data + x.offset; + case OFFSET_RO_DATA: + return g->ro_data + x.offset; + case OFFSET_SECCOMP_DATA: + return (const uint64_t *)s->args + x.offset; + case OFFSET_INSTRUCTION: + return (const struct op *)(g->inst) + x.offset; + default: + return NULL; + } +} +#endif + +#endif /* COMMON_GLUTEN_H */ diff --git a/common/util.h b/common/util.h index 84dc3db..102b55b 100644 --- a/common/util.h +++ b/common/util.h @@ -6,8 +6,22 @@ #ifndef UTIL_H #define UTIL_H +#include <string.h> +#include <stdint.h> + #define BIT(n) (1UL << (n)) +#ifndef MIN +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) +#endif +#ifndef MAX +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#endif + +#define ARRAY_SIZE(a) ((int)(sizeof(a) / sizeof((a)[0]))) + +#define BUILD_BUG_ON(x) extern int __y[1 - 2 * !!(x)]; + void err(const char *format, ...); void info(const char *format, ...); void debug(const char *format, ...); @@ -19,4 +33,87 @@ void debug(const char *format, ...); exit(EXIT_FAILURE); \ } while (0) +/** + * From the Linux kernel, include/linux/log2.h: + * + * Copyright (C) 2006 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * const_ilog2 - log base 2 of 32-bit or a 64-bit constant unsigned value + * @n: parameter + * + * Use this where sparse expects a true constant expression, e.g. for array + * indices. + */ +#define const_ilog2(n) \ +( \ + __builtin_constant_p(n) ? ( \ + (n) < 2 ? 0 : \ + (n) & (1ULL << 63) ? 63 : \ + (n) & (1ULL << 62) ? 62 : \ + (n) & (1ULL << 61) ? 61 : \ + (n) & (1ULL << 60) ? 60 : \ + (n) & (1ULL << 59) ? 59 : \ + (n) & (1ULL << 58) ? 58 : \ + (n) & (1ULL << 57) ? 57 : \ + (n) & (1ULL << 56) ? 56 : \ + (n) & (1ULL << 55) ? 55 : \ + (n) & (1ULL << 54) ? 54 : \ + (n) & (1ULL << 53) ? 53 : \ + (n) & (1ULL << 52) ? 52 : \ + (n) & (1ULL << 51) ? 51 : \ + (n) & (1ULL << 50) ? 50 : \ + (n) & (1ULL << 49) ? 49 : \ + (n) & (1ULL << 48) ? 48 : \ + (n) & (1ULL << 47) ? 47 : \ + (n) & (1ULL << 46) ? 46 : \ + (n) & (1ULL << 45) ? 45 : \ + (n) & (1ULL << 44) ? 44 : \ + (n) & (1ULL << 43) ? 43 : \ + (n) & (1ULL << 42) ? 42 : \ + (n) & (1ULL << 41) ? 41 : \ + (n) & (1ULL << 40) ? 40 : \ + (n) & (1ULL << 39) ? 39 : \ + (n) & (1ULL << 38) ? 38 : \ + (n) & (1ULL << 37) ? 37 : \ + (n) & (1ULL << 36) ? 36 : \ + (n) & (1ULL << 35) ? 35 : \ + (n) & (1ULL << 34) ? 34 : \ + (n) & (1ULL << 33) ? 33 : \ + (n) & (1ULL << 32) ? 32 : \ + (n) & (1ULL << 31) ? 31 : \ + (n) & (1ULL << 30) ? 30 : \ + (n) & (1ULL << 29) ? 29 : \ + (n) & (1ULL << 28) ? 28 : \ + (n) & (1ULL << 27) ? 27 : \ + (n) & (1ULL << 26) ? 26 : \ + (n) & (1ULL << 25) ? 25 : \ + (n) & (1ULL << 24) ? 24 : \ + (n) & (1ULL << 23) ? 23 : \ + (n) & (1ULL << 22) ? 22 : \ + (n) & (1ULL << 21) ? 21 : \ + (n) & (1ULL << 20) ? 20 : \ + (n) & (1ULL << 19) ? 19 : \ + (n) & (1ULL << 18) ? 18 : \ + (n) & (1ULL << 17) ? 17 : \ + (n) & (1ULL << 16) ? 16 : \ + (n) & (1ULL << 15) ? 15 : \ + (n) & (1ULL << 14) ? 14 : \ + (n) & (1ULL << 13) ? 13 : \ + (n) & (1ULL << 12) ? 12 : \ + (n) & (1ULL << 11) ? 11 : \ + (n) & (1ULL << 10) ? 10 : \ + (n) & (1ULL << 9) ? 9 : \ + (n) & (1ULL << 8) ? 8 : \ + (n) & (1ULL << 7) ? 7 : \ + (n) & (1ULL << 6) ? 6 : \ + (n) & (1ULL << 5) ? 5 : \ + (n) & (1ULL << 4) ? 4 : \ + (n) & (1ULL << 3) ? 3 : \ + (n) & (1ULL << 2) ? 2 : \ + 1) : \ + -1) + +#define BITS_PER_NUM(n) (const_ilog2(n) + 1) + #endif /* UTIL_H */ diff --git a/cooker/Makefile b/cooker/Makefile index 4f72448..be8f703 100644 --- a/cooker/Makefile +++ b/cooker/Makefile @@ -5,31 +5,25 @@ # cooker/Makefile - Makefile for seitan-cooker # # Copyright 2023 Red Hat GmbH -# Author: Stefano Brivio <sbrivio@redhat.com> +# Authors: Alice Frosi <afrosi@redhat.com> +# Stefano Brivio <sbrivio@redhat.com> -COMMON_DIR := ../common -SRCS := calls.c emit.c filter.c gluten.c main.c parse.c parson.c \ - $(COMMON_DIR)/util.c calls/net.c -HEADERS := calls.h cooker.h emit.h filter.h \ - gluten.h parse.h parson.h calls/net.h \ - $(COMMON_DIR)/util.h -BIN := $(OUTDIR)seitan-cooker +OUTDIR ?= .. +COMMON := ../common +BIN := $(OUTDIR)/seitan-cooker +CFLAGS := -O0 -g -Wall -Wextra -pedantic -std=c99 -I$(COMMON) -TARGET := $(shell $(CC) -dumpmachine) -TARGET_ARCH := $(shell echo $(TARGET) | cut -f1 -d- | tr [A-Z] [a-z]) -TARGET_ARCH := $(shell echo $(TARGET_ARCH) | sed 's/powerpc/ppc/') +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 -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/') - -CFLAGS += -DSEITAN_AUDIT_ARCH=AUDIT_ARCH_$(AUDIT_ARCH) -CFLAGS += -O0 -g -Wall -Wextra -pedantic -std=c99 -I$(COMMON_DIR) -cooker: $(SRCS) $(HEADERS) +$(BIN): $(SRCS) $(HEADERS) $(CC) $(CFLAGS) -o $(BIN) $(SRCS) -all: cooker +all: $(BIN) clean: - rm -f cooker + rm -f $(BIN) diff --git a/cooker/calls/net.c b/cooker/calls/net.c index c0949cc..370a3a1 100644 --- a/cooker/calls/net.c +++ b/cooker/calls/net.c @@ -39,7 +39,7 @@ n = sendmmsg(fd, *msgvec, vlen, flags) #include "../cooker.h" #include "../calls.h" -static struct arg_num af[] = { +static struct num af[] = { { "unix", AF_UNIX }, { "ipv4", AF_INET }, { "ipv6", AF_INET6 }, @@ -49,7 +49,7 @@ static struct arg_num af[] = { { 0 }, }; -static struct arg_num socket_types[] = { +static struct num socket_types[] = { { "stream", SOCK_STREAM }, { "dgram", SOCK_DGRAM }, { "seq", SOCK_SEQPACKET }, @@ -58,13 +58,13 @@ static struct arg_num socket_types[] = { { 0 }, }; -static struct arg_num socket_flags[] = { +static struct num socket_flags[] = { { "nonblock", SOCK_NONBLOCK }, { "cloexec", SOCK_CLOEXEC }, { 0 }, }; -static struct arg_num protocols[] = { +static struct num protocols[] = { { "ip", IPPROTO_IP }, { "icmp", IPPROTO_ICMP }, { "igmp", IPPROTO_IGMP }, @@ -83,86 +83,97 @@ static struct arg_num protocols[] = { }; static struct arg socket_args[] = { - { 0, "family", ARG_INT, 0, { .d_num = af } }, - { 1, "type", ARG_INTMASK, 0, { .d_num = socket_types } }, - { 1, "flags", ARG_INTFLAGS, 0, { .d_num = socket_flags } }, - { 2, "protocol", ARG_INT, 0, { .d_num = protocols } }, + { 0, "family", INT, 0, { .d_num = af } }, + { 1, "type", INTMASK, 0, { .d_num = socket_types } }, + { 1, "flags", INTFLAGS, 0, { .d_num = socket_flags } }, + { 2, "protocol", INT, 0, { .d_num = protocols } }, { 0 }, }; -static struct arg_struct connect_addr_unix[] = { - { "path", ARG_STRING, +static struct field connect_addr_unix[] = { + { + "path", STRING, offsetof(struct sockaddr_un, sun_path), - UNIX_PATH_MAX, { 0 } + UNIX_PATH_MAX, { 0 } }, { 0 }, }; -static struct arg_struct connect_addr_ipv4[] = { - { "port", ARG_PORT, +static struct field connect_addr_ipv4[] = { + { + "port", PORT, offsetof(struct sockaddr_in, sin_port), - 0, { 0 } + 0, { 0 } }, - { "addr", ARG_IPV4, + { + "addr", IPV4, offsetof(struct sockaddr_in, sin_addr), - 0, { 0 } + 0, { 0 } }, { 0 }, }; -static struct arg_struct connect_addr_ipv6[] = { - { "port", ARG_PORT, +static struct field connect_addr_ipv6[] = { + { + "port", PORT, offsetof(struct sockaddr_in6, sin6_port), - 0, { 0 } + 0, { 0 } }, - { "addr", ARG_IPV6, + { + "addr", IPV6, offsetof(struct sockaddr_in6, sin6_addr), - 0, { 0 } + 0, { 0 } }, { 0 }, }; -static struct arg_struct connect_addr_nl[] = { - { "pid", ARG_PID, +static struct field connect_addr_nl[] = { + { + "pid", PID, offsetof(struct sockaddr_nl, nl_pid), - 0, { 0 } + 0, { 0 } }, - { "groups", ARG_U32, + { + "groups", U32, offsetof(struct sockaddr_in6, sin6_addr), - 0, { 0 } + 0, { 0 } }, { 0 }, }; -static struct arg_struct connect_family = { - "family", ARG_INT, - offsetof(struct sockaddr, sa_family), - 0, { .d_num = af } +static struct field connect_family = { + "family", INT, + offsetof(struct sockaddr, sa_family), + 0, { .d_num = af } }; -static struct arg_select_num connect_addr_select_family[] = { - { AF_UNIX, ARG_STRUCT, { .d_struct = connect_addr_unix } }, - { AF_INET, ARG_STRUCT, { .d_struct = connect_addr_ipv4 } }, - { AF_INET6, ARG_STRUCT, { .d_struct = connect_addr_ipv6 } }, - { AF_NETLINK, ARG_STRUCT, { .d_struct = connect_addr_nl } }, +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 } }, { 0 }, }; -static struct arg_select connect_addr_select = { +static struct select connect_addr_select = { &connect_family, { .d_num = connect_addr_select_family } }; static struct arg connect_args[] = { - { 0, "fd", ARG_INT, 0, + { + 0, "fd", INT, 0, { 0 }, }, - { 0, "path", ARG_FDPATH, 0, + { + 0, "path", FDPATH, 0, { 0 }, }, - { 1, "addr", ARG_SELECT, sizeof(struct sockaddr_storage), + { + 1, "addr", SELECT, sizeof(struct sockaddr_storage), { .d_select = &connect_addr_select }, }, - { 2, "addrlen", ARG_LONG, 0, + { + 2, "addrlen", LONG, 0, { 0 }, }, }; diff --git a/cooker/cooker.h b/cooker/cooker.h index 53aa0db..a1cc360 100644 --- a/cooker/cooker.h +++ b/cooker/cooker.h @@ -11,78 +11,95 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/types.h> +#include <arpa/inet.h> #define REFS_MAX 256 +#define REF_NAMEMAX 256 #define CALL_ARGS 6 -struct arg_num; -struct arg_struct; -struct arg_select; +struct num; +struct field; +struct select; -union arg_value { - struct arg_num *d_num; - struct arg_struct *d_struct; - struct arg_select *d_select; +union desc { + struct num *d_num; + struct field *d_struct; + struct select *d_select; }; -enum arg_type { - ARG_INT, - ARG_INTMASK, - ARG_INTFLAGS, +union value { + int v_int; + uint32_t v_u32; + long long v_num; +}; + +enum type { + INT, + INTMASK, + INTFLAGS, - ARG_U32, - ARG_U32MASK, - ARG_U32FLAGS, + U32, + U32MASK, + U32FLAGS, - ARG_LONG, - ARG_LONGMASK, - ARG_LONGFLAGS, + LONG, + LONGMASK, + LONGFLAGS, - ARG_STRING, + STRING, - ARG_STRUCT, - ARG_SELECT, + STRUCT, + SELECT, - ARG_PID, + PID, - ARG_PORT, - ARG_IPV4, - ARG_IPV6, + PORT, + IPV4, + IPV6, - ARG_FDPATH, + FDPATH, - ARG_TYPE_END, + TYPE_END, }; -#define ARG_TYPE_COUNT (ARG_TYPE_END - 1) +#define TYPE_COUNT (TYPE_END - 1) + +#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 arg_num { +struct num { char *name; long long value; }; -struct arg_struct { +struct field { char *name; - enum arg_type type; - size_t offset; + enum type type; + off_t offset; size_t strlen; - union arg_value desc; + union desc desc; }; -struct arg_select_num { +struct select_num { long long value; - enum arg_type type; - union arg_value desc; + enum type type; + union desc desc; }; -struct arg_select { - struct arg_struct *field; +struct select { + struct field *field; union { - struct arg_select_num *d_num; + struct select_num *d_num; } desc; }; @@ -90,10 +107,10 @@ struct arg { int pos; char *name; - enum arg_type type; + enum type type; size_t size; - union arg_value desc; + union desc desc; }; #endif /* COOKER_H */ diff --git a/cooker/emit.c b/cooker/emit.c index a82529c..8c35f1d 100644 --- a/cooker/emit.c +++ b/cooker/emit.c @@ -2,7 +2,7 @@ /* seitan - Syscall Expressive Interpreter, Transformer and Notifier * - * cooker/emit.c - Generate gluten (bytecode) instructions + * cooker/emit.c - Generate gluten (bytecode) instructions and read-only data * * Copyright 2023 Red Hat GmbH * Author: Stefano Brivio <sbrivio@redhat.com> @@ -11,17 +11,97 @@ #include "cooker.h" #include "gluten.h" #include "util.h" +#include "emit.h" -int emit_nr(struct gluten_ctx *g, long number) +static const char *type_str[] = { + "INT", "INTMASK", "INTFLAGS", + "U32", "U32MASK", "U32FLAGS", + "LONG", "LONGMASK", "LONGFLAGS", + "STRING", + "STRUCT", "SELECT", + "PID", + "PORT", "IPV4", "IPV6", + "FDPATH", + NULL +}; + +static const char *cmp_type_str[] = { "EQ", "GT", "GE", "LT", "LE", NULL }; + +void emit_nr(struct gluten_ctx *g, long number) +{ + struct op_nr *nr = (struct op_nr *)gluten_ptr(&g->g, g->ip); + + nr->nr = number; + nr->no_match.type = OFFSET_INSTRUCTION; + nr->no_match.offset = NEXT_BLOCK; + + debug(" %i: OP_NR %li, < >", g->ip.offset, number); + + if (++g->ip.offset > INST_MAX) + die("Too many instructions"); +} + +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); + + 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); + + if (++g->ip.offset > INST_MAX) + die("Too many instructions"); +} + +void emit_cmp(struct gluten_ctx *g, enum op_cmp_type cmp, + struct gluten_offset x, struct gluten_offset y, size_t size, + enum jump_type jmp) { - debug(" %i: OP_NR %li, < >", g->ip++, number); + struct op_cmp *op = (struct op_cmp *)gluten_ptr(&g->g, g->ip); + + op->x = x; + op->y = y; + op->size = size; + op->cmp = cmp; + op->jmp = jmp; + + debug(" %i: OP_CMP (#%lu) %%%lu %s %%%lu", g->ip.offset, size, + x.offset, cmp_type_str[cmp], y.offset); - return 0; + if (++g->ip.offset > INST_MAX) + die("Too many instructions"); } -int emit_load(struct gluten_ctx *g, int offset, int index, size_t len) +void emit_cmp_field(struct gluten_ctx *g, enum op_cmp_type cmp, + struct field *field, + struct gluten_offset base, struct gluten_offset match, + enum jump_type jmp) { - debug(" %i: OP_LOAD #%i < %i (%lu)", g->ip++, offset, index, len); + base.offset += field->offset; + + emit_cmp(g, cmp, base, match, + field->strlen ? field->strlen : gluten_size[field->type], + jmp); +} + +struct gluten_offset emit_data(struct gluten_ctx *g, enum type type, + union value *value) +{ + void *p = gluten_ptr(&g->g, g->cp); + struct gluten_offset ret = g->cp; + + if (type == INT) { + *(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) + die(" Read-only data section exceeded"); + } - return 0; + return ret; } diff --git a/cooker/emit.h b/cooker/emit.h index 74264b1..94b2600 100644 --- a/cooker/emit.h +++ b/cooker/emit.h @@ -6,7 +6,17 @@ #ifndef EMIT_H #define EMIT_H -int emit_nr(struct gluten_ctx *g, long number); -int emit_load(struct gluten_ctx *g, int offset, int index, size_t len); +void emit_nr(struct gluten_ctx *g, long 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, + struct gluten_offset x, struct gluten_offset y, size_t size, + enum jump_type jmp); +void emit_cmp_field(struct gluten_ctx *g, enum op_cmp_type cmp, + struct field *field, + 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); #endif /* EMIT_H */ diff --git a/cooker/gluten.c b/cooker/gluten.c index 1116e6b..6460798 100644 --- a/cooker/gluten.c +++ b/cooker/gluten.c @@ -12,33 +12,47 @@ #include "gluten.h" #include "util.h" -#define GLUTEN_INST_SIZE BUFSIZ -#define GLUTEN_DATA_SIZE BUFSIZ +size_t gluten_size[TYPE_COUNT] = { + [INT] = sizeof(int), + [INTMASK] = sizeof(int), + [INTFLAGS] = sizeof(int), -static char gluten[GLUTEN_INST_SIZE + GLUTEN_DATA_SIZE]; + [U32] = sizeof(uint32_t), + [U32MASK] = sizeof(uint32_t), + [U32FLAGS] = sizeof(uint32_t), + + [LONG] = sizeof(long), + [LONGMASK] = sizeof(long), + [LONGFLAGS] = sizeof(long), + + [PID] = sizeof(pid_t), + [PORT] = sizeof(in_port_t), + [IPV4] = sizeof(struct in_addr), + [IPV6] = sizeof(struct in6_addr), -static size_t gluten_arg_storage[ARG_TYPE_COUNT] = { - [ARG_INT] = sizeof(int), - [ARG_INTMASK] = sizeof(int), }; -int gluten_alloc(struct gluten_ctx *g, size_t size) +struct gluten_offset gluten_alloc(struct gluten_ctx *g, size_t size) { - debug(" allocating %lu at offset %i", size, g->sp); - if ((g->sp += size) >= GLUTEN_DATA_SIZE) + struct gluten_offset ret = g->dp; + + debug(" allocating %lu at offset %i", size, g->dp.offset); + if ((g->dp.offset += size) >= DATA_SIZE) die("Temporary data size exceeded"); - return g->sp - size; + return ret; } -int gluten_alloc_type(struct gluten_ctx *g, enum arg_type type) +struct gluten_offset gluten_alloc_type(struct gluten_ctx *g, enum type type) { - return gluten_alloc(g, gluten_arg_storage[type]); + return gluten_alloc(g, gluten_size[type]); } -int gluten_init(struct gluten_ctx *g) +void gluten_init(struct gluten_ctx *g) { - g->gluten = gluten; + (void)g; - return 0; + g->ip.type = g->lr.type = OFFSET_INSTRUCTION; + g->dp.type = OFFSET_DATA; + g->cp.type = OFFSET_RO_DATA; } diff --git a/cooker/gluten.h b/cooker/gluten.h index 440029d..a48cd6d 100644 --- a/cooker/gluten.h +++ b/cooker/gluten.h @@ -6,22 +6,27 @@ #ifndef GLUTEN_H #define GLUTEN_H +#define COOKER +#include <gluten.h> + struct gluten_arg_data { - int offset; + struct gluten_offset offset; size_t len; }; struct gluten_ref_data { - int name; - int offset; + char name[REF_NAMEMAX]; + struct gluten_offset offset; size_t len; }; struct gluten_ctx { - int ip; - int lr; - int sp; - char *gluten; + struct gluten_offset ip; + struct gluten_offset lr; + struct gluten_offset cp; + struct gluten_offset dp; + + struct gluten g; struct gluten_arg_data match_dst[CALL_ARGS]; struct gluten_arg_data call_src[CALL_ARGS]; @@ -29,8 +34,11 @@ struct gluten_ctx { struct gluten_ref_data refs[REFS_MAX]; }; -int gluten_alloc(struct gluten_ctx *g, size_t size); -int gluten_alloc_type(struct gluten_ctx *g, enum arg_type type); -int gluten_init(struct gluten_ctx *g); +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_init(struct gluten_ctx *g); +void gluten_block_init(struct gluten_ctx *g); + +extern size_t gluten_size[TYPE_COUNT]; #endif /* GLUTEN_H */ diff --git a/cooker/parse.c b/cooker/parse.c index 9d8a7be..0a87088 100644 --- a/cooker/parse.c +++ b/cooker/parse.c @@ -35,7 +35,7 @@ static int parse_match_load(struct gluten_ctx *g, struct arg *a) return 0; } -static long long parse_match_expr_num(struct arg_num *desc, JSON_Value *value) +static long long parse_match_expr_num(struct num *desc, JSON_Value *value) { const char *s = NULL; long long n; @@ -59,35 +59,109 @@ static long long parse_match_expr_num(struct arg_num *desc, JSON_Value *value) return n; } -static int parse_match_key(struct gluten_ctx *g, int index, enum arg_type type, - union arg_value desc, JSON_Value *value) +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; - (void)index; + 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); - gluten_alloc_type(g, type); 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 ARG_INTFLAGS: - case ARG_LONGFLAGS: - case ARG_U32FLAGS: + case INTFLAGS: + case LONGFLAGS: + case U32FLAGS: /* fetch/combine expr algebra loop */ - case ARG_INTMASK: + case INTMASK: /* calculate mask first */ - case ARG_INT: - case ARG_LONG: - case ARG_U32: + case INT: + case LONG: + case U32: parse_match_expr_num(desc.d_num, value); //emit_cmp(...); default: @@ -139,7 +213,6 @@ static int parse_matches(struct gluten_ctx *g, JSON_Value *value) const char *name; g->lr = g->ip; - g->sp = 0; match = json_array_get_object(matches, i); name = json_object_get_name(match, 0); @@ -229,6 +302,7 @@ int parse_file(struct gluten_ctx *g, const char *path) blocks = json_value_get_array(root); for (i = 0; i < json_array_get_count(blocks); i++) { obj = json_array_get_object(blocks, i); + debug("Parsing block %i", i); parse_block(g, obj); } diff --git a/eater/Makefile b/eater/Makefile index 2e3db3f..2e08b1f 100644 --- a/eater/Makefile +++ b/eater/Makefile @@ -10,7 +10,7 @@ COMMON_DIR :=../common SRCS := $(COMMON_DIR)/common.c eater.c HEADERS := $(COMMON_DIR)/common.h -BIN := $(OUTDIR)seitan-eater +BIN := $(OUTDIR)/seitan-eater CFLAGS += -Wall -Wextra -pedantic -I$(COMMON_DIR) eater: $(SRCS) $(HEADERS) diff --git a/operations.c b/operations.c index 6e8d157..23d571c 100644 --- a/operations.c +++ b/operations.c @@ -1,6 +1,10 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later +/* seitan - Syscall Expressive Interpreter, Transformer and Notifier + * + * operations.c - Execution of bytecode operations + * * Copyright 2023 Red Hat GmbH - * Author: Alice Frosi <afrosi@redhat.com> + * Authors: Alice Frosi <afrosi@redhat.com> + * Stefano Brivio <sbrivio@redhat.com> */ #define _GNU_SOURCE @@ -111,11 +115,11 @@ static int set_namespaces(const struct op_call *a, int tpid) ns_name); break; case NS_SPEC_PID: - snprintf(path, sizeof(path), "/proc/%d/ns/%s", ns.pid, - ns_name); + snprintf(path, sizeof(path), "/proc/%d/ns/%s", + ns.id.pid, ns_name); break; case NS_SPEC_PATH: - snprintf(path, sizeof(path), "%s", ns.path); + snprintf(path, sizeof(path), "%s", ns.id.path); break; } @@ -152,14 +156,11 @@ static int execute_syscall(void *args) exit(0); } -int copy_args(struct seccomp_notif *req, struct op_copy_args *copy, void *data, - int notifier) +static int op_load(struct seccomp_notif *req, int notifier, struct gluten *g, + struct op_load *load) { char path[PATH_MAX]; - unsigned int i; - ssize_t nread; - void *dest; - int fd; + int fd, ret = 0; snprintf(path, sizeof(path), "/proc/%d/mem", req->pid); if ((fd = open(path, O_RDONLY | O_CLOEXEC)) < 0) { @@ -172,24 +173,17 @@ int copy_args(struct seccomp_notif *req, struct op_copy_args *copy, void *data, */ if (!is_cookie_valid(notifier, req->id)) { fprintf(stderr, "the seccomp request isn't valid anymore\n"); - return -1; - } - for (i = 0; i < 6; i++) { - if (copy->args[i].type == REFERENCE) { - dest = (uint16_t *)data + copy->args[i].args_off; - nread = pread(fd, dest, copy->args[i].size, - req->data.args[i]); - if (nread < 0) { - perror("pread"); - return -1; - } - } else { - memcpy((uint16_t *)data + copy->args[i].args_off, - &req->data.args[i], copy->args[i].size); - } + ret = -1; + goto out; } + + memcpy(gluten_write_ptr(g, load->dst), + gluten_ptr(&req->data, g, load->src), + load->size); + +out: close(fd); - return 0; + return ret; } static int resolve_fd(void *data, struct op_resolvedfd *resfd, pid_t pid) @@ -230,57 +224,55 @@ int do_call(struct arg_clone *c) return 0; } -static void set_inject_fields(uint64_t id, void *data, const struct op *a, +static void set_inject_fields(uint64_t id, struct gluten *g, + const struct op_inject *a, struct seccomp_notif_addfd *resp) { - const struct fd_type *new = &(a->inj).newfd; - const struct fd_type *old = &(a->inj).oldfd; - resp->flags = SECCOMP_ADDFD_FLAG_SETFD; resp->id = id; - if (new->type == IMMEDIATE) - resp->newfd = new->fd; - else - memcpy(&resp->newfd, (uint16_t *)data + new->fd_off, - sizeof(resp->newfd)); - if (old->type == IMMEDIATE) - resp->srcfd = old->fd; - else - memcpy(&resp->srcfd, (uint16_t *)data + old->fd_off, - sizeof(resp->srcfd)); + memcpy(&resp->newfd, gluten_ptr(NULL, g, a->new_fd), + sizeof(resp->newfd)); + memcpy(&resp->srcfd, gluten_ptr(NULL, g, a->new_fd), + sizeof(resp->srcfd)); + resp->newfd_flags = 0; } -static int op_cmp(void *data, const struct op_cmp *c) +static int op_cmp(struct seccomp_notif *req, int notifier, struct gluten *g, + struct op_cmp *op) { - enum op_cmp_type cmp = c->cmp; - int res = memcmp((uint16_t *)data + c->s1_off, - (uint16_t *)data + c->s2_off, c->size); + int res = memcmp(gluten_ptr(&req->data, g, op->x), + gluten_ptr(&req->data, g, op->y), op->size); + enum op_cmp_type cmp = op->cmp; + + (void)notifier; + if ((res == 0 && (cmp == CMP_EQ || cmp == CMP_LE || cmp == CMP_GE)) || - (res < 0 && (cmp == CMP_LT || cmp == CMP_LE)) || - (res > 0 && (cmp == CMP_GT || cmp == CMP_GE))) - return c->jmp; - else - return -1; + (res < 0 && (cmp == CMP_LT || cmp == CMP_LE)) || + (res > 0 && (cmp == CMP_GT || cmp == CMP_GE))) + return op->jmp; + + return -1; } -int do_operations(void *data, struct op operations[], struct seccomp_notif *req, - unsigned int n_operations, int notifyfd) +int do_operations(struct gluten *g, struct op *ops, struct seccomp_notif *req, + unsigned int n_ops, int notifyfd) { struct seccomp_notif_addfd resp_fd; struct seccomp_notif_resp resp; struct arg_clone c; unsigned int i; + struct op *op; int ret; - for (i = 0; i < n_operations; i++) { - switch (operations[i].type) { + for (i = 0, op = ops; i < n_ops; i++, op++) { + switch (op->type) { case OP_CALL: resp.id = req->id; resp.val = 0; resp.flags = 0; - c.args = &operations[i].call; + c.args = &ops[i].op.call; c.pid = req->pid; if (do_call(&c) == -1) { resp.error = -1; @@ -296,9 +288,8 @@ int do_operations(void *data, struct op operations[], struct seccomp_notif *req, * The result of the call needs to be save as * reference */ - if (operations[i].call.has_ret) { - memcpy((uint16_t *)data + - operations[i].call.ret_off, + if (ops[i].op.call.has_ret) { + memcpy(gluten_write_ptr(g, op->op.call.ret), &c.ret, sizeof(c.ret)); } break; @@ -306,7 +297,7 @@ int do_operations(void *data, struct op operations[], struct seccomp_notif *req, resp.id = req->id; resp.val = 0; resp.flags = 0; - resp.error = operations[i].block.error; + resp.error = ops[i].op.block.error; if (send_target(&resp, notifyfd) == -1) return -1; break; @@ -314,13 +305,10 @@ int do_operations(void *data, struct op operations[], struct seccomp_notif *req, resp.id = req->id; resp.flags = 0; resp.error = 0; - if (operations[i].ret.type == IMMEDIATE) - resp.val = operations[i].ret.value; - else - memcpy(&resp.val, - (uint16_t *)data + - operations[i].ret.value_off, - sizeof(resp.val)); + + memcpy(&resp.val, + gluten_ptr(&req->data, g, op->op.ret.val), + sizeof(resp.val)); if (send_target(&resp, notifyfd) == -1) return -1; @@ -335,39 +323,41 @@ int do_operations(void *data, struct op operations[], struct seccomp_notif *req, return -1; break; case OP_INJECT_A: - set_inject_fields(req->id, data, &operations[i], + set_inject_fields(req->id, g, &ops[i].op.inject, &resp_fd); resp_fd.flags |= SECCOMP_ADDFD_FLAG_SEND; if (send_inject_target(&resp_fd, notifyfd) == -1) return -1; break; case OP_INJECT: - set_inject_fields(req->id, data, &operations[i], + set_inject_fields(req->id, g, &ops[i].op.inject, &resp_fd); if (send_inject_target(&resp_fd, notifyfd) == -1) return -1; break; - case OP_COPY_ARGS: - if (copy_args(req, &operations[i].copy, data, - notifyfd) < 0) + case OP_LOAD: + if (op_load(req, notifyfd, g, &op->op.load)) return -1; + break; case OP_END: return 0; case OP_CMP: - if ((ret = op_cmp(data, &operations[i].cmp)) != -1) - i = ret; + ret = op_cmp(req, notifyfd, g, (struct op_cmp *)op); + if (ret == -1) + return -1; + + i = ret; break; case OP_RESOLVEDFD: - ret = resolve_fd(data, &operations[i].resfd, req->pid); + ret = resolve_fd(g->data, &ops[i].op.resfd, req->pid); if (ret == -1) return -1; else if (ret == 1) - i = operations[i].resfd.jmp; + i = ops[i].op.resfd.jmp; break; default: - fprintf(stderr, "unknow operation %d \n", - operations[i].type); + fprintf(stderr, "unknown operation %d \n", ops[i].type); } } return 0; diff --git a/operations.h b/operations.h index 3a4caa2..ecb6414 100644 --- a/operations.h +++ b/operations.h @@ -20,6 +20,7 @@ struct arg_clone { }; int do_call(struct arg_clone *c); -int do_operations(void *data, struct op operations[], struct seccomp_notif *req, +int do_operations(struct gluten *g, struct op operations[], + struct seccomp_notif *req, unsigned int n_operations, int notifyfd); #endif /* ACTIONS_H */ diff --git a/scripts/nr_syscalls.sh b/scripts/nr_syscalls.sh index 380d6f1..5110a9b 100755 --- a/scripts/nr_syscalls.sh +++ b/scripts/nr_syscalls.sh @@ -27,31 +27,31 @@ FOOTER_NUMBERS="}; #endif" -syscalls=( - "accept" - "bind" - "connect" - "getpeername" - "getsockname" - "getsockopt" - "listen" - "mount" - "openat" - "recvfrom" - "recvmmsg" - "recvmsg" - "sendmmsg" - "sendmsg" - "sendto" - "setsockopt" - "shutdown" - "socket" - "socketpair" -) +syscalls=" + accept + bind + connect + getpeername + getsockname + getsockopt + listen + mount + openat + recvfrom + recvmmsg + recvmsg + sendmmsg + sendmsg + sendto + setsockopt + shutdown + socket + socketpair +" printf '%s\n' "${HEADER_NUMBERS}" > "${OUT_NUMBERS}" # syscall_nr - Get syscall number from compiler, also note in numbers.h -__in="$(printf "#include <asm-generic/unistd.h>\n#include <sys/syscall.h>\n__NR_%s" ${syscalls[@]})" +__in="$(for c in ${syscalls}; do printf "#include <asm-generic/unistd.h>\n#include <sys/syscall.h>\n__NR_%s" $c; done)" __out="$(echo "${__in}" |cc -E -xc - -o - |sed -n '/\#.*$/!p'| sed '/^$/d')" awk -v AS="${syscalls[*]}" -v BS="${__out[*]}" \ |