aboutgitcodelistschat:MatrixIRC
path: root/src
diff options
context:
space:
mode:
authorAlice Frosi <afrosi@redhat.com>2023-03-24 10:07:48 +0100
committerAlice Frosi <afrosi@redhat.com>2023-03-24 15:38:07 +0100
commit069009f8e39238ec1a67fba6cfb287b9a0cac83e (patch)
tree77f817eb7b96178b71f3d573a83cec19f7fba09c /src
parent06b0f6d323c396ca1df000af96fdd07cc69b06e0 (diff)
downloadseitan-069009f8e39238ec1a67fba6cfb287b9a0cac83e.tar
seitan-069009f8e39238ec1a67fba6cfb287b9a0cac83e.tar.gz
seitan-069009f8e39238ec1a67fba6cfb287b9a0cac83e.tar.bz2
seitan-069009f8e39238ec1a67fba6cfb287b9a0cac83e.tar.lz
seitan-069009f8e39238ec1a67fba6cfb287b9a0cac83e.tar.xz
seitan-069009f8e39238ec1a67fba6cfb287b9a0cac83e.tar.zst
seitan-069009f8e39238ec1a67fba6cfb287b9a0cac83e.zip
Re-organize project and add license header
Diffstat (limited to 'src')
-rw-r--r--src/common/common.c51
-rw-r--r--src/common/common.h6
-rw-r--r--src/common/gluten.h138
-rw-r--r--src/cooker/Makefile20
-rw-r--r--src/cooker/calls.c18
-rw-r--r--src/cooker/calls.h23
-rw-r--r--src/cooker/calls/net.c174
-rw-r--r--src/cooker/calls/net.h11
-rw-r--r--src/cooker/cooker.h99
-rw-r--r--src/cooker/emit.c27
-rw-r--r--src/cooker/emit.h12
-rw-r--r--src/cooker/example.hjson67
-rw-r--r--src/cooker/example.hjson.license2
-rw-r--r--src/cooker/filter.c299
-rw-r--r--src/cooker/filter.h39
-rw-r--r--src/cooker/gluten.c44
-rw-r--r--src/cooker/gluten.h36
-rw-r--r--src/cooker/main.c28
-rw-r--r--src/cooker/parse.c237
-rw-r--r--src/cooker/parse.h11
-rw-r--r--src/cooker/parson.c2080
-rw-r--r--src/cooker/parson.h240
-rw-r--r--src/cooker/util.c29
-rw-r--r--src/cooker/util.h22
-rw-r--r--src/debug/Makefile37
-rw-r--r--src/debug/bpf_dbg.c32
-rw-r--r--src/debug/build.c33
-rw-r--r--src/debug/disasm.c281
-rw-r--r--src/debug/disasm.h14
-rw-r--r--src/eater/Makefile22
-rw-r--r--src/eater/eater.c143
-rw-r--r--src/seitan/Makefile35
-rw-r--r--src/seitan/operations.c361
-rw-r--r--src/seitan/operations.h26
-rw-r--r--src/seitan/seitan.c435
35 files changed, 5132 insertions, 0 deletions
diff --git a/src/common/common.c b/src/common/common.c
new file mode 100644
index 0000000..a8f79a2
--- /dev/null
+++ b/src/common/common.c
@@ -0,0 +1,51 @@
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+int find_fd_seccomp_notifier(const char *path)
+{
+ char entry[2 * PATH_MAX + 1];
+ char buf[PATH_MAX + 1];
+ struct dirent *dp;
+ ssize_t nbytes;
+ struct stat sb;
+ DIR *dirp;
+
+ if ((dirp = opendir(path)) == NULL) {
+ fprintf(stderr, "failed reading fds from proc: %s \n", path);
+ return -1;
+ }
+ while ((dp = readdir(dirp)) != NULL) {
+ snprintf(entry, sizeof(entry), "%s/%s", path, dp->d_name);
+ if (lstat(entry, &sb) == -1) {
+ perror("lstat");
+ }
+ /* Skip the entry if it isn't a symbolic link */
+ if (!S_ISLNK(sb.st_mode))
+ continue;
+
+ nbytes = readlink(entry, buf, PATH_MAX);
+ if (nbytes == -1) {
+ perror("readlink");
+ }
+ if (nbytes == PATH_MAX) {
+ perror("buffer overflow");
+ continue;
+ }
+ /*
+ * From man proc: For file descriptors that have no
+ * corresponding inode (e.g., file descriptors produced by
+ * bpf(2)..), the entry will be a symbolic link with contents
+ * of the form:
+ * anon_inode:<file-type>
+ */
+ if (strstr(buf, "anon_inode:seccomp notify") != NULL)
+ return atoi(dp->d_name);
+ }
+ fprintf(stderr, "seccomp notifier not found in %s\n", path);
+ return -1;
+}
diff --git a/src/common/common.h b/src/common/common.h
new file mode 100644
index 0000000..487032b
--- /dev/null
+++ b/src/common/common.h
@@ -0,0 +1,6 @@
+#ifndef COMMON_H_
+#define COMMON_H_
+
+int find_fd_seccomp_notifier(const char *pid);
+
+#endif
diff --git a/src/common/gluten.h b/src/common/gluten.h
new file mode 100644
index 0000000..8370cf5
--- /dev/null
+++ b/src/common/gluten.h
@@ -0,0 +1,138 @@
+#ifndef GLUTEN_H
+#define GLUTEN_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#define MAX_FD_INJECTED 10
+
+enum ns_spec_type {
+ NS_NONE,
+ NS_SPEC_TARGET,
+ NS_SPEC_PID,
+ NS_SPEC_PATH,
+};
+
+struct ns_spec {
+ enum ns_spec_type type;
+ union {
+ pid_t pid;
+ char *path;
+ };
+};
+
+/*
+ * enum ns_type - Type of namespaces
+ */
+enum ns_type {
+ NS_CGROUP,
+ NS_IPC,
+ NS_NET,
+ NS_MOUNT,
+ NS_PID,
+ NS_TIME,
+ NS_USER,
+ NS_UTS,
+};
+
+/*
+ * struct op_context - Description of the context where the call needs to be executed
+ * @ns: Descrption of the each namespace where the call needs to be executed
+ */
+struct op_context {
+ struct ns_spec ns[sizeof(enum ns_type)];
+};
+
+enum op_type {
+ OP_CALL,
+ OP_BLOCK,
+ OP_CONT,
+ OP_INJECT,
+ OP_INJECT_A,
+ OP_RETURN,
+ OP_COPY_ARGS,
+ OP_END,
+ OP_CMP,
+ OP_RESOLVEDFD,
+};
+
+enum value_type {
+ IMMEDIATE,
+ REFERENCE,
+};
+
+struct op_call {
+ long nr;
+ bool has_ret;
+ void *args[6];
+ struct op_context context;
+ uint16_t ret_off;
+};
+
+struct op_block {
+ int32_t error;
+};
+
+struct op_continue {
+ bool cont;
+};
+
+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 op_inject {
+ struct fd_type newfd;
+ struct fd_type oldfd;
+};
+
+struct copy_arg {
+ uint16_t args_off;
+ enum value_type type;
+ size_t size;
+};
+
+struct op_copy_args {
+ struct copy_arg args[6];
+};
+
+struct op_cmp {
+ uint16_t s1_off;
+ uint16_t s2_off;
+ size_t size;
+ unsigned int jmp;
+};
+
+struct op_resolvedfd {
+ uint16_t fd_off;
+ uint16_t path_off;
+ size_t path_size;
+ unsigned int jmp;
+};
+
+struct op {
+ enum op_type type;
+ union {
+ 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_cmp cmp;
+ struct op_resolvedfd resfd;
+ };
+};
+#endif /* GLUTEN_H */
diff --git a/src/cooker/Makefile b/src/cooker/Makefile
new file mode 100644
index 0000000..8741879
--- /dev/null
+++ b/src/cooker/Makefile
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# seitan - Syscall Expressive Interpreter, Transformer and Notifier
+#
+# cooker/Makefile - Makefile for seitan-cooker
+#
+# Copyright 2023 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+SRCS := calls.c emit.c gluten.c main.c parse.c parson.c util.c calls/net.c
+HEADERS := calls.h cooker.h emit.h gluten.h parse.h parson.h util.h calls/net.h
+BIN := $(OUTDIR)cooker
+
+cooker: $(SRCS) $(HEADERS)
+ $(CC) -O0 -g -Wall -Wextra -pedantic -std=c99 -o $(BIN) $(SRCS)
+
+all: cooker
+
+clean:
+ rm -f cooker
diff --git a/src/cooker/calls.c b/src/cooker/calls.c
new file mode 100644
index 0000000..74b5a06
--- /dev/null
+++ b/src/cooker/calls.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* seitan - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * cooker/calls.c - Known syscall sets
+ *
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#include "cooker.h"
+#include "calls.h"
+
+#include "calls/net.h"
+
+struct call *call_sets[] = {
+ syscalls_net, NULL,
+};
diff --git a/src/cooker/calls.h b/src/cooker/calls.h
new file mode 100644
index 0000000..5d46e14
--- /dev/null
+++ b/src/cooker/calls.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#ifndef CALLS_H
+#define CALLS_H
+
+/**
+ * struct call - Description of one known system call
+ * @number: Number from __NR_ macros, architecture dependent
+ * @name: Name for use in recipes
+ * @args: NULL-terminated array of argument descriptions
+ */
+struct call {
+ long number;
+ const char *name;
+ struct arg *args;
+};
+
+extern struct call *call_sets[];
+
+#endif /* CALLS_H */
diff --git a/src/cooker/calls/net.c b/src/cooker/calls/net.c
new file mode 100644
index 0000000..c0949cc
--- /dev/null
+++ b/src/cooker/calls/net.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* seitan - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * cooker/calls/net.c - Description of known networking system calls
+ *
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+/*
+fd = socket(family, type stream/dgram/..., protocol)
+fd = connect(fd, addr, addrlen)
+fd = accept(fd, addr, addrlen)
+n = sendto(fd, buf, len, flags, dst addr, addrlen)
+n = recvfrom(fd, buf, len, flags, src addr, addrlen)
+n = sendmsg(fd, msg, flags)
+n = recvmsg(fd, msg, flags)
+e = shutdown(fd, rd/wr/rdwr)
+e = bind(fd, addr, addrlen)
+e = listen(fd, backlog)
+e = getsockname(fd, bound addr, addrlen)
+e = getpeername(fd, peer addr, addrlen)
+e = socketpair(family, type stream/dgram/..., sockets[2])
+e = setsockopt(fd, level, optname, *optval, optlen)
+e = getsockopt(fd, level, optname, *optval, *optlen)
+n = recvmmsg(fd, *msgvec, vlen, flags, *timeout)
+n = sendmmsg(fd, *msgvec, vlen, flags)
+*/
+
+#include <asm-generic/unistd.h>
+#include <sys/syscall.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <linux/un.h>
+#include <linux/netlink.h>
+
+#include "../cooker.h"
+#include "../calls.h"
+
+static struct arg_num af[] = {
+ { "unix", AF_UNIX },
+ { "ipv4", AF_INET },
+ { "ipv6", AF_INET6 },
+ { "netlink", AF_NETLINK },
+ { "packet", AF_PACKET },
+ { "vsock", AF_VSOCK },
+ { 0 },
+};
+
+static struct arg_num socket_types[] = {
+ { "stream", SOCK_STREAM },
+ { "dgram", SOCK_DGRAM },
+ { "seq", SOCK_SEQPACKET },
+ { "raw", SOCK_RAW },
+ { "packet", SOCK_PACKET },
+ { 0 },
+};
+
+static struct arg_num socket_flags[] = {
+ { "nonblock", SOCK_NONBLOCK },
+ { "cloexec", SOCK_CLOEXEC },
+ { 0 },
+};
+
+static struct arg_num protocols[] = {
+ { "ip", IPPROTO_IP },
+ { "icmp", IPPROTO_ICMP },
+ { "igmp", IPPROTO_IGMP },
+ { "tcp", IPPROTO_TCP },
+ { "udp", IPPROTO_UDP },
+ { "ipv6", IPPROTO_IPV6 },
+ { "gre", IPPROTO_GRE },
+ { "esp", IPPROTO_ESP },
+ { "ah", IPPROTO_AH },
+ { "sctp", IPPROTO_SCTP },
+ { "udplite", IPPROTO_UDPLITE },
+ { "mpls", IPPROTO_MPLS },
+ { "raw", IPPROTO_RAW },
+ { "mptcp", IPPROTO_MPTCP },
+ { 0 },
+};
+
+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 },
+};
+
+static struct arg_struct connect_addr_unix[] = {
+ { "path", ARG_STRING,
+ offsetof(struct sockaddr_un, sun_path),
+ UNIX_PATH_MAX, { 0 }
+ },
+ { 0 },
+};
+
+static struct arg_struct connect_addr_ipv4[] = {
+ { "port", ARG_PORT,
+ offsetof(struct sockaddr_in, sin_port),
+ 0, { 0 }
+ },
+ { "addr", ARG_IPV4,
+ offsetof(struct sockaddr_in, sin_addr),
+ 0, { 0 }
+ },
+ { 0 },
+};
+
+static struct arg_struct connect_addr_ipv6[] = {
+ { "port", ARG_PORT,
+ offsetof(struct sockaddr_in6, sin6_port),
+ 0, { 0 }
+ },
+ { "addr", ARG_IPV6,
+ offsetof(struct sockaddr_in6, sin6_addr),
+ 0, { 0 }
+ },
+ { 0 },
+};
+
+static struct arg_struct connect_addr_nl[] = {
+ { "pid", ARG_PID,
+ offsetof(struct sockaddr_nl, nl_pid),
+ 0, { 0 }
+ },
+ { "groups", ARG_U32,
+ offsetof(struct sockaddr_in6, sin6_addr),
+ 0, { 0 }
+ },
+ { 0 },
+};
+
+static struct arg_struct connect_family = {
+ "family", ARG_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 } },
+ { 0 },
+};
+
+static struct arg_select connect_addr_select = {
+ &connect_family, { .d_num = connect_addr_select_family }
+};
+
+static struct arg connect_args[] = {
+ { 0, "fd", ARG_INT, 0,
+ { 0 },
+ },
+ { 0, "path", ARG_FDPATH, 0,
+ { 0 },
+ },
+ { 1, "addr", ARG_SELECT, sizeof(struct sockaddr_storage),
+ { .d_select = &connect_addr_select },
+ },
+ { 2, "addrlen", ARG_LONG, 0,
+ { 0 },
+ },
+};
+
+struct call syscalls_net[] = {
+ { __NR_connect, "connect", connect_args },
+ { __NR_socket, "socket", socket_args },
+ { 0 },
+};
diff --git a/src/cooker/calls/net.h b/src/cooker/calls/net.h
new file mode 100644
index 0000000..105bf4a
--- /dev/null
+++ b/src/cooker/calls/net.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_NET_H
+#define CALLS_NET_H
+
+extern struct call syscalls_net[];
+
+#endif /* CALLS_NET_H */
diff --git a/src/cooker/cooker.h b/src/cooker/cooker.h
new file mode 100644
index 0000000..53aa0db
--- /dev/null
+++ b/src/cooker/cooker.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#ifndef COOKER_H
+#define COOKER_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define REFS_MAX 256
+#define CALL_ARGS 6
+
+struct arg_num;
+struct arg_struct;
+struct arg_select;
+
+union arg_value {
+ struct arg_num *d_num;
+ struct arg_struct *d_struct;
+ struct arg_select *d_select;
+};
+
+enum arg_type {
+ ARG_INT,
+ ARG_INTMASK,
+ ARG_INTFLAGS,
+
+ ARG_U32,
+ ARG_U32MASK,
+ ARG_U32FLAGS,
+
+ ARG_LONG,
+ ARG_LONGMASK,
+ ARG_LONGFLAGS,
+
+ ARG_STRING,
+
+ ARG_STRUCT,
+ ARG_SELECT,
+
+ ARG_PID,
+
+ ARG_PORT,
+ ARG_IPV4,
+ ARG_IPV6,
+
+ ARG_FDPATH,
+
+ ARG_TYPE_END,
+};
+
+#define ARG_TYPE_COUNT (ARG_TYPE_END - 1)
+
+struct arg_num {
+ char *name;
+ long long value;
+};
+
+struct arg_struct {
+ char *name;
+ enum arg_type type;
+ size_t offset;
+
+ size_t strlen;
+
+ union arg_value desc;
+};
+
+struct arg_select_num {
+ long long value;
+
+ enum arg_type type;
+ union arg_value desc;
+};
+
+struct arg_select {
+ struct arg_struct *field;
+
+ union {
+ struct arg_select_num *d_num;
+ } desc;
+};
+
+struct arg {
+ int pos;
+ char *name;
+
+ enum arg_type type;
+ size_t size;
+
+ union arg_value desc;
+};
+
+#endif /* COOKER_H */
diff --git a/src/cooker/emit.c b/src/cooker/emit.c
new file mode 100644
index 0000000..a82529c
--- /dev/null
+++ b/src/cooker/emit.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* seitan - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * cooker/emit.c - Generate gluten (bytecode) instructions
+ *
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#include "cooker.h"
+#include "gluten.h"
+#include "util.h"
+
+int emit_nr(struct gluten_ctx *g, long number)
+{
+ debug(" %i: OP_NR %li, < >", g->ip++, number);
+
+ return 0;
+}
+
+int emit_load(struct gluten_ctx *g, int offset, int index, size_t len)
+{
+ debug(" %i: OP_LOAD #%i < %i (%lu)", g->ip++, offset, index, len);
+
+ return 0;
+}
diff --git a/src/cooker/emit.h b/src/cooker/emit.h
new file mode 100644
index 0000000..74264b1
--- /dev/null
+++ b/src/cooker/emit.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#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);
+
+#endif /* EMIT_H */
diff --git a/src/cooker/example.hjson b/src/cooker/example.hjson
new file mode 100644
index 0000000..45ed339
--- /dev/null
+++ b/src/cooker/example.hjson
@@ -0,0 +1,67 @@
+[
+ {
+ "match": [ /* qemu-pr-helper and similar */
+ { "connect": { "addr": { "family": "unix", "path": "/var/run/pr-helper.sock" }, "fd": { "ref": "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 }
+ },
+ {
+ "match": [ /* qemu creates a tap interface */
+ { "ioctl": { "path": "/dev/net/tun", "request": "TUNSETIFF", "ifr": { "name": "tap0", "flags": "IFF_TUN" } } }
+ ],
+ "limit": { "scope": "process", "count": 1 },
+ "call": { "ioctl": { "request": "TUNSETIFF", "path": "/dev/net/tun", "ifr": { "name": "tap0", "flags": "IFF_TUN", "ret": "x" } } },
+ "return": { "ref": "x" }
+ },
+ {
+ "match": [ /* CVE-2022-0185-style */
+ { "unshare": { "flags": { "has": { "newuser": true, "newnet": false } } } }
+ ],
+ "block": { }
+ },
+ {
+ "match": [ /* passt */
+ { "unshare": { "flags": { "has": [ "ipc", "mount", "uts", "pid" ] } } }
+ ],
+ "block": { }
+ },
+ {
+ "match": [ /* Giuseppe's example */
+ { "mknod": { "path": { "ref": "path" }, "mode": "c", "major": 1, "minor": { "in": [ 3, 5, 7, 8, 9 ], "ref": "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" }
+ }
+]
+
+/*
+ * INTFLAGS, LONGFLAGS, U32FLAGS
+ *
+ * "field": { "in": [ "ipc", "mount", "uts" ] }
+ * flags & set
+ * !!(flags & (ipc | mount | ns))
+ *
+ * "field": { "all": [ "ipc", "mount", "uts" ] }
+ * flags & set == set
+ * flags & (ipc | mount | ns) == (ipc | mount | ns)
+ *
+ * "field": { "not": [ "ipc", "mount", "uts" ] }
+ * !(flags & set)
+ *
+ * "field": { "ipc": false, "mount": true, "uts": false }
+ * flags & set == set
+ * !(flags & ipc) && (flags & mount) && !(flags & utc)
+ *
+ * "field": { "ipc" }
+ * flags == ipc
+ *
+ * INTMASK
+ * value = (target value & known values)
+ *
+ * INT, LONG, U32
+ * "arg": { "in": [ 0, 1 ] }
+ * arg == 0 || arg == 1
+ */
diff --git a/src/cooker/example.hjson.license b/src/cooker/example.hjson.license
new file mode 100644
index 0000000..2e3bd69
--- /dev/null
+++ b/src/cooker/example.hjson.license
@@ -0,0 +1,2 @@
+SPDX-FileCopyrightText: 2023 Red Hat GmbH <sbrivio@redhat.com>
+SPDX-License-Identifier: GPL-3.0-or-later
diff --git a/src/cooker/filter.c b/src/cooker/filter.c
new file mode 100644
index 0000000..dbda7ca
--- /dev/null
+++ b/src/cooker/filter.c
@@ -0,0 +1,299 @@
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "numbers.h"
+#include "filter.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);
+}
+
+static unsigned int count_shift_right(unsigned int n)
+{
+ unsigned int i = 0;
+ for (; n > 0; i++) {
+ n = n >> 1;
+ }
+ return i;
+}
+
+static void insert_pair(int jumps[], int arr[], unsigned int level)
+{
+ unsigned int i_a, i;
+ for (i = 0; i < level; i++) {
+ i_a = 2 * i + 1;
+ if (arr[i_a] == EMPTY) {
+ jumps[i] = arr[i_a - 1];
+ } else {
+ jumps[i] = arr[i_a];
+ }
+ }
+}
+
+unsigned int left_child(unsigned int parent_index)
+{
+ unsigned int level = count_shift_right(parent_index + 1);
+ /* 2^(level) -1 gives the beginning of the next interval */
+ unsigned int next_interval = (1 << level) - 1;
+ /* 2^(level -1) -1 gives the beginning of the current interval */
+ unsigned begin = (1 << (level - 1)) - 1;
+ unsigned i = parent_index - begin;
+ return next_interval + 2 * i;
+}
+
+unsigned int right_child(unsigned int parent_index)
+{
+ return left_child(parent_index) + 1;
+}
+
+void create_lookup_nodes(int jumps[], unsigned int n)
+{
+ unsigned int i, index;
+ unsigned int old_interval, interval;
+
+ for (i = 0; i < MAX_JUMPS; i++)
+ jumps[i] = EMPTY;
+
+ if (n < 2) {
+ jumps[0] = 0;
+ return;
+ }
+ old_interval = 1 << count_shift_right(n - 1);
+ interval = old_interval >> 1;
+
+ /* first scan populate the last level of jumps */
+ for (i = interval - 1, index = 1; index < old_interval && index < n;
+ i++, index += 2) {
+ jumps[i] = index;
+ }
+ if (n % 2 == 1) {
+ jumps[i] = index - 1;
+ }
+ for (old_interval = interval, interval = interval / 2; interval > 0;
+ old_interval = interval, interval = interval / 2) {
+ insert_pair(&jumps[interval - 1], &jumps[old_interval - 1],
+ interval);
+ }
+}
+
+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(const struct syscall_entry *table)
+{
+ unsigned i, k, n;
+ n = 0;
+ for (i = 0; i < table->count; i++)
+ for (k = 0; k < 6; k++)
+ if ((table->entry + i)->check_arg[k])
+ n++;
+ return n;
+}
+
+static unsigned int get_total_args(const struct syscall_entry table[],
+ unsigned int n_syscall)
+{
+ unsigned int i, n;
+ n = 0;
+ for (i = 0; i < n_syscall; i++) {
+ n += get_n_args(&table[i]);
+ }
+ return n;
+}
+
+unsigned int create_bpf_program_log(struct sock_filter filter[])
+{
+ filter[0] = (struct sock_filter)BPF_STMT(
+ BPF_LD | BPF_W | BPF_ABS,
+ (offsetof(struct seccomp_data, arch)));
+ filter[1] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,
+ SEITAN_AUDIT_ARCH, 0, 1);
+ filter[2] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K,
+ SECCOMP_RET_USER_NOTIF);
+ filter[3] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K,
+ SECCOMP_RET_ALLOW);
+ return 4;
+}
+
+unsigned int create_bfp_program(struct syscall_entry table[],
+ struct sock_filter filter[],
+ unsigned int n_syscall)
+{
+ unsigned int offset_left, offset_right;
+ unsigned int n_args, n_nodes;
+ unsigned int notify, accept;
+ unsigned int i, j, k, size;
+ unsigned int next_offset;
+ int nodes[MAX_JUMPS];
+
+ create_lookup_nodes(nodes, n_syscall);
+
+ size = 3;
+ /* No nodes if there is a single syscall */
+ n_nodes = (1 << count_shift_right(n_syscall - 1)) - 1;
+
+ n_args = get_total_args(table, n_syscall);
+
+ accept = 2 + n_nodes + 2 * n_syscall + n_args + 1;
+ notify = 2 + n_nodes + 2 * n_syscall + n_args + 2;
+
+ /* Pre */
+ /* cppcheck-suppress badBitmaskCheck */
+ filter[0] = (struct sock_filter)BPF_STMT(
+ BPF_LD | BPF_W | BPF_ABS,
+ (offsetof(struct seccomp_data, arch)));
+ filter[1] = (struct sock_filter)BPF_JUMP(
+ BPF_JMP | BPF_JEQ | BPF_K, SEITAN_AUDIT_ARCH, 0, accept - 2);
+ /* cppcheck-suppress badBitmaskCheck */
+ filter[2] = (struct sock_filter)BPF_STMT(
+ BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr)));
+
+ /* Insert nodes */
+ for (i = 0; i < n_nodes; i++) {
+ if (nodes[i] == EMPTY) {
+ filter[size++] =
+ (struct sock_filter)JUMPA(accept - size);
+ } else {
+ 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);
+ }
+ }
+
+ next_offset = n_syscall - 1;
+ /* Insert leaves */
+ for (i = 0; i < n_syscall; i++) {
+ filter[size++] = (struct sock_filter)EQ(
+ table[i].nr, next_offset, accept - size);
+ next_offset += get_n_args(&table[i]);
+ }
+
+ /*
+ * Insert args. Evaluate every args, if it doesn't match continue with
+ * the following, otherwise notify.
+ */
+ for (i = 0; i < n_syscall; i++) {
+ for (j = 0; j < (table[i]).count; j++) {
+ for (k = 0; k < 6; k++)
+ if ((table[i].entry + j)->check_arg[k]) {
+ filter[size++] = (struct sock_filter)EQ(
+ (table[i].entry + j)->args[k],
+ notify - size, 0);
+ }
+ }
+ filter[size++] = (struct sock_filter)JUMPA(accept - size);
+ }
+
+ /* Seccomp accept and notify instruction */
+ filter[size++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K,
+ SECCOMP_RET_ALLOW);
+ filter[size++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K,
+ SECCOMP_RET_USER_NOTIF);
+ return size;
+}
+
+static int compare_names(const void *a, const void *b)
+{
+ return strcmp(((struct syscall_numbers *)a)->name,
+ ((struct syscall_numbers *)b)->name);
+}
+
+int convert_bpf(char *file, struct bpf_call *entries, int n, bool log)
+{
+ int nt, fd, fsize;
+ struct syscall_entry table[N_SYSCALL];
+ struct sock_filter filter[MAX_FILTER];
+
+ qsort(numbers, sizeof(numbers) / sizeof(numbers[0]), sizeof(numbers[0]),
+ compare_names);
+
+ qsort(entries, n, sizeof(struct bpf_call), compare_bpf_call_names);
+ nt = construct_table(entries, n, table);
+
+ if (log)
+ fsize = create_bpf_program_log(filter);
+ else
+ fsize = create_bfp_program(table, filter, nt);
+
+ fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
+ S_IRUSR | S_IWUSR);
+ write(fd, filter, sizeof(struct sock_filter) * fsize);
+
+ close(fd);
+
+ return 0;
+}
diff --git a/src/cooker/filter.h b/src/cooker/filter.h
new file mode 100644
index 0000000..ee5ab12
--- /dev/null
+++ b/src/cooker/filter.h
@@ -0,0 +1,39 @@
+#ifndef FILTER_H_
+#define FILTER_H_
+
+#include <linux/filter.h>
+#include <linux/audit.h>
+#include <linux/seccomp.h>
+
+#define JGE(nr, right, left) \
+ BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, (nr), (right), (left))
+#define JUMPA(jump) BPF_JUMP(BPF_JMP | BPF_JA, (jump), 0, 0)
+#define EQ(nr, a1, a2) BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (nr), (a1), (a2))
+
+#define MAX_FILTER 1024
+
+#define MAX_JUMPS 128
+#define EMPTY -1
+
+struct bpf_call {
+ char *name;
+ int args[6];
+ bool check_arg[6];
+};
+
+struct syscall_entry {
+ unsigned int count;
+ long nr;
+ const struct bpf_call *entry;
+};
+
+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, bool log);
+
+#endif
diff --git a/src/cooker/gluten.c b/src/cooker/gluten.c
new file mode 100644
index 0000000..1116e6b
--- /dev/null
+++ b/src/cooker/gluten.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* seitan - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * cooker/gluten.c - gluten (bytecode) file and layout functions
+ *
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#include "cooker.h"
+#include "gluten.h"
+#include "util.h"
+
+#define GLUTEN_INST_SIZE BUFSIZ
+#define GLUTEN_DATA_SIZE BUFSIZ
+
+static char gluten[GLUTEN_INST_SIZE + GLUTEN_DATA_SIZE];
+
+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)
+{
+ debug(" allocating %lu at offset %i", size, g->sp);
+ if ((g->sp += size) >= GLUTEN_DATA_SIZE)
+ die("Temporary data size exceeded");
+
+ return g->sp - size;
+}
+
+int gluten_alloc_type(struct gluten_ctx *g, enum arg_type type)
+{
+ return gluten_alloc(g, gluten_arg_storage[type]);
+}
+
+int gluten_init(struct gluten_ctx *g)
+{
+ g->gluten = gluten;
+
+ return 0;
+}
diff --git a/src/cooker/gluten.h b/src/cooker/gluten.h
new file mode 100644
index 0000000..440029d
--- /dev/null
+++ b/src/cooker/gluten.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#ifndef GLUTEN_H
+#define GLUTEN_H
+
+struct gluten_arg_data {
+ int offset;
+ size_t len;
+};
+
+struct gluten_ref_data {
+ int name;
+ int offset;
+ size_t len;
+};
+
+struct gluten_ctx {
+ int ip;
+ int lr;
+ int sp;
+ char *gluten;
+
+ struct gluten_arg_data match_dst[CALL_ARGS];
+ struct gluten_arg_data call_src[CALL_ARGS];
+
+ 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);
+
+#endif /* GLUTEN_H */
diff --git a/src/cooker/main.c b/src/cooker/main.c
new file mode 100644
index 0000000..9965cff
--- /dev/null
+++ b/src/cooker/main.c
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* seitan - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * cooker/main.c - Entry point of seitan-cooker, the gluten (bytecode) generator
+ *
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#include "cooker.h"
+#include "gluten.h"
+#include "parse.h"
+
+int main(int argc, char **argv)
+{
+ struct gluten_ctx g = { 0 };
+
+ /* TODO: Options and usage */
+ (void)argc;
+ (void)argv;
+
+ gluten_init(&g);
+
+ parse_file(&g, argv[1]);
+
+ return 0;
+}
diff --git a/src/cooker/parse.c b/src/cooker/parse.c
new file mode 100644
index 0000000..9d8a7be
--- /dev/null
+++ b/src/cooker/parse.c
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* seitan - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * cooker/parse.c - JSON recipe parsing
+ *
+ * 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 "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 arg_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;
+}
+
+static int parse_match_key(struct gluten_ctx *g, int index, enum arg_type type,
+ union arg_value desc, JSON_Value *value)
+{
+ JSON_Object *tmp;
+ const char *ref;
+
+ (void)index;
+
+ if (json_value_get_type(value) == JSONObject &&
+ (tmp = json_value_get_object(value)) &&
+ (ref = json_object_get_string(tmp, "ref"))) {
+ debug(" setting reference '%s'", ref);
+ gluten_alloc_type(g, type);
+ value = json_object_get_value(tmp, "value");
+ }
+
+ if (!value)
+ return 0;
+
+ switch (type) {
+ case ARG_INTFLAGS:
+ case ARG_LONGFLAGS:
+ case ARG_U32FLAGS:
+ /* fetch/combine expr algebra loop */
+ case ARG_INTMASK:
+ /* calculate mask first */
+ case ARG_INT:
+ case ARG_LONG:
+ case ARG_U32:
+ parse_match_expr_num(desc.d_num, value);
+ //emit_cmp(...);
+ default:
+ ;
+ }
+
+ return 0;
+}
+
+static int parse_match_arg(struct gluten_ctx *g, const char *name,
+ JSON_Value *value, struct arg *a)
+{
+ debug(" Parsing match argument %s", name);
+
+ parse_match_load(g, a);
+ parse_match_key(g, a->pos, a->type, a->desc, value);
+
+ return 0;
+}
+
+static int parse_match(struct gluten_ctx *g, JSON_Object *obj, struct arg *args)
+{
+ 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;
+}
+
+static int parse_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->lr = g->ip;
+ g->sp = 0;
+
+ 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;
+}
+
+static int parse_call(struct gluten_ctx *g, JSON_Value *value)
+{
+ (void)g;
+ (void)value;
+ return 0;
+}
+
+static int parse_inject(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 },
+ { NULL, NULL },
+};
+
+static int parse_block(struct gluten_ctx *g, JSON_Object *block)
+{
+ unsigned i;
+
+ for (i = 0; i < json_object_get_count(block); i++) {
+ struct rule_parser *parser;
+ JSON_Value *rule;
+ const char *type;
+
+ type = json_object_get_name(block, i);
+ rule = json_object_get_value(block, type);
+
+ for (parser = parsers; parser->type; parser++) {
+ if (!strcmp(type, parser->type)) {
+ parser->fn(g, rule);
+ break;
+ }
+ }
+
+ if (!parser->type)
+ die(" Invalid rule type: \"%s\"", type);
+ }
+
+ return 0;
+}
+
+int parse_file(struct gluten_ctx *g, const char *path)
+{
+ JSON_Array *blocks;
+ JSON_Value *root;
+ JSON_Object *obj;
+ unsigned i;
+
+ root = json_parse_file_with_comments(path);
+ if (json_value_get_type(root) != JSONArray)
+ die("Invalid input file %s", 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);
+ }
+
+ return 0;
+}
diff --git a/src/cooker/parse.h b/src/cooker/parse.h
new file mode 100644
index 0000000..cb4a2f2
--- /dev/null
+++ b/src/cooker/parse.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 PARSE_H
+#define PARSE_H
+
+int parse_file(struct gluten_ctx *g, const char *path);
+
+#endif /* PARSE_H */
diff --git a/src/cooker/parson.c b/src/cooker/parson.c
new file mode 100644
index 0000000..a7a844a
--- /dev/null
+++ b/src/cooker/parson.c
@@ -0,0 +1,2080 @@
+/*
+ SPDX-License-Identifier: MIT
+
+ Parson ( http://kgabis.github.com/parson/ )
+ Copyright (c) 2012 - 2019 Krzysztof Gabis
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+#ifdef _MSC_VER
+#ifndef _CRT_SECURE_NO_WARNINGS
+#define _CRT_SECURE_NO_WARNINGS
+#endif /* _CRT_SECURE_NO_WARNINGS */
+#endif /* _MSC_VER */
+
+#include "parson.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+#include <errno.h>
+
+/* Apparently sscanf is not implemented in some "standard" libraries, so don't use it, if you
+ * don't have to. */
+#define sscanf THINK_TWICE_ABOUT_USING_SSCANF
+
+#define STARTING_CAPACITY 16
+#define MAX_NESTING 2048
+
+#define FLOAT_FORMAT "%1.17g" /* do not increase precision without incresing NUM_BUF_SIZE */
+#define NUM_BUF_SIZE 64 /* double printed with "%1.17g" shouldn't be longer than 25 bytes so let's be paranoid and use 64 */
+
+#define SIZEOF_TOKEN(a) (sizeof(a) - 1)
+#define SKIP_CHAR(str) ((*str)++)
+#define SKIP_WHITESPACES(str) while (isspace((unsigned char)(**str))) { SKIP_CHAR(str); }
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+#undef malloc
+#undef free
+
+#if defined(isnan) && defined(isinf)
+#define IS_NUMBER_INVALID(x) (isnan((x)) || isinf((x)))
+#else
+#define IS_NUMBER_INVALID(x) (((x) * 0.0) != 0.0)
+#endif
+
+static JSON_Malloc_Function parson_malloc = malloc;
+static JSON_Free_Function parson_free = free;
+
+static int parson_escape_slashes = 1;
+
+#define IS_CONT(b) (((unsigned char)(b) & 0xC0) == 0x80) /* is utf-8 continuation byte */
+
+/* Type definitions */
+typedef union json_value_value {
+ char *string;
+ double number;
+ JSON_Object *object;
+ JSON_Array *array;
+ int boolean;
+ int null;
+} JSON_Value_Value;
+
+struct json_value_t {
+ JSON_Value *parent;
+ JSON_Value_Type type;
+ JSON_Value_Value value;
+};
+
+struct json_object_t {
+ JSON_Value *wrapping_value;
+ char **names;
+ JSON_Value **values;
+ size_t count;
+ size_t capacity;
+};
+
+struct json_array_t {
+ JSON_Value *wrapping_value;
+ JSON_Value **items;
+ size_t count;
+ size_t capacity;
+};
+
+/* Various */
+static char * read_file(const char *filename);
+static void remove_comments(char *string, const char *start_token, const char *end_token);
+static char * parson_strndup(const char *string, size_t n);
+static char * parson_strdup(const char *string);
+static int hex_char_to_int(char c);
+static int parse_utf16_hex(const char *string, unsigned int *result);
+static int num_bytes_in_utf8_sequence(unsigned char c);
+static int verify_utf8_sequence(const unsigned char *string, int *len);
+static int is_valid_utf8(const char *string, size_t string_len);
+static int is_decimal(const char *string, size_t length);
+
+/* JSON Object */
+static JSON_Object * json_object_init(JSON_Value *wrapping_value);
+static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value);
+static JSON_Status json_object_addn(JSON_Object *object, const char *name, size_t name_len, JSON_Value *value);
+static JSON_Status json_object_resize(JSON_Object *object, size_t new_capacity);
+static JSON_Value * json_object_getn_value(const JSON_Object *object, const char *name, size_t name_len);
+static JSON_Status json_object_remove_internal(JSON_Object *object, const char *name, int free_value);
+static JSON_Status json_object_dotremove_internal(JSON_Object *object, const char *name, int free_value);
+static void json_object_free(JSON_Object *object);
+
+/* JSON Array */
+static JSON_Array * json_array_init(JSON_Value *wrapping_value);
+static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value);
+static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity);
+static void json_array_free(JSON_Array *array);
+
+/* JSON Value */
+static JSON_Value * json_value_init_string_no_copy(char *string);
+
+/* Parser */
+static JSON_Status skip_quotes(const char **string);
+static int parse_utf16(const char **unprocessed, char **processed);
+static char * process_string(const char *input, size_t len);
+static char * get_quoted_string(const char **string);
+static JSON_Value * parse_object_value(const char **string, size_t nesting);
+static JSON_Value * parse_array_value(const char **string, size_t nesting);
+static JSON_Value * parse_string_value(const char **string);
+static JSON_Value * parse_boolean_value(const char **string);
+static JSON_Value * parse_number_value(const char **string);
+static JSON_Value * parse_null_value(const char **string);
+static JSON_Value * parse_value(const char **string, size_t nesting);
+
+/* Serialization */
+static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty, char *num_buf);
+static int json_serialize_string(const char *string, char *buf);
+static int append_indent(char *buf, int level);
+static int append_string(char *buf, const char *string);
+
+/* Various */
+static char * parson_strndup(const char *string, size_t n) {
+ char *output_string = (char*)parson_malloc(n + 1);
+ if (!output_string) {
+ return NULL;
+ }
+ output_string[n] = '\0';
+ strncpy(output_string, string, n);
+ return output_string;
+}
+
+static char * parson_strdup(const char *string) {
+ return parson_strndup(string, strlen(string));
+}
+
+static int hex_char_to_int(char c) {
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ return c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ return c - 'A' + 10;
+ }
+ return -1;
+}
+
+static int parse_utf16_hex(const char *s, unsigned int *result) {
+ int x1, x2, x3, x4;
+ if (s[0] == '\0' || s[1] == '\0' || s[2] == '\0' || s[3] == '\0') {
+ return 0;
+ }
+ x1 = hex_char_to_int(s[0]);
+ x2 = hex_char_to_int(s[1]);
+ x3 = hex_char_to_int(s[2]);
+ x4 = hex_char_to_int(s[3]);
+ if (x1 == -1 || x2 == -1 || x3 == -1 || x4 == -1) {
+ return 0;
+ }
+ *result = (unsigned int)((x1 << 12) | (x2 << 8) | (x3 << 4) | x4);
+ return 1;
+}
+
+static int num_bytes_in_utf8_sequence(unsigned char c) {
+ if (c == 0xC0 || c == 0xC1 || c > 0xF4 || IS_CONT(c)) {
+ return 0;
+ } else if ((c & 0x80) == 0) { /* 0xxxxxxx */
+ return 1;
+ } else if ((c & 0xE0) == 0xC0) { /* 110xxxxx */
+ return 2;
+ } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx */
+ return 3;
+ } else if ((c & 0xF8) == 0xF0) { /* 11110xxx */
+ return 4;
+ }
+ return 0; /* won't happen */
+}
+
+static int verify_utf8_sequence(const unsigned char *string, int *len) {
+ unsigned int cp = 0;
+ *len = num_bytes_in_utf8_sequence(string[0]);
+
+ if (*len == 1) {
+ cp = string[0];
+ } else if (*len == 2 && IS_CONT(string[1])) {
+ cp = string[0] & 0x1F;
+ cp = (cp << 6) | (string[1] & 0x3F);
+ } else if (*len == 3 && IS_CONT(string[1]) && IS_CONT(string[2])) {
+ cp = ((unsigned char)string[0]) & 0xF;
+ cp = (cp << 6) | (string[1] & 0x3F);
+ cp = (cp << 6) | (string[2] & 0x3F);
+ } else if (*len == 4 && IS_CONT(string[1]) && IS_CONT(string[2]) && IS_CONT(string[3])) {
+ cp = string[0] & 0x7;
+ cp = (cp << 6) | (string[1] & 0x3F);
+ cp = (cp << 6) | (string[2] & 0x3F);
+ cp = (cp << 6) | (string[3] & 0x3F);
+ } else {
+ return 0;
+ }
+
+ /* overlong encodings */
+ if ((cp < 0x80 && *len > 1) ||
+ (cp < 0x800 && *len > 2) ||
+ (cp < 0x10000 && *len > 3)) {
+ return 0;
+ }
+
+ /* invalid unicode */
+ if (cp > 0x10FFFF) {
+ return 0;
+ }
+
+ /* surrogate halves */
+ if (cp >= 0xD800 && cp <= 0xDFFF) {
+ return 0;
+ }
+
+ return 1;
+}
+
+static int is_valid_utf8(const char *string, size_t string_len) {
+ int len = 0;
+ const char *string_end = string + string_len;
+ while (string < string_end) {
+ if (!verify_utf8_sequence((const unsigned char*)string, &len)) {
+ return 0;
+ }
+ string += len;
+ }
+ return 1;
+}
+
+static int is_decimal(const char *string, size_t length) {
+ if (length > 1 && string[0] == '0' && string[1] != '.') {
+ return 0;
+ }
+ if (length > 2 && !strncmp(string, "-0", 2) && string[2] != '.') {
+ return 0;
+ }
+ while (length--) {
+ if (strchr("xX", string[length])) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static char * read_file(const char * filename) {
+ FILE *fp = fopen(filename, "r");
+ size_t size_to_read = 0;
+ size_t size_read = 0;
+ long pos;
+ char *file_contents;
+ if (!fp) {
+ return NULL;
+ }
+ fseek(fp, 0L, SEEK_END);
+ pos = ftell(fp);
+ if (pos < 0) {
+ fclose(fp);
+ return NULL;
+ }
+ size_to_read = pos;
+ rewind(fp);
+ file_contents = (char*)parson_malloc(sizeof(char) * (size_to_read + 1));
+ if (!file_contents) {
+ fclose(fp);
+ return NULL;
+ }
+ size_read = fread(file_contents, 1, size_to_read, fp);
+ if (size_read == 0 || ferror(fp)) {
+ fclose(fp);
+ parson_free(file_contents);
+ return NULL;
+ }
+ fclose(fp);
+ file_contents[size_read] = '\0';
+ return file_contents;
+}
+
+static void remove_comments(char *string, const char *start_token, const char *end_token) {
+ int in_string = 0, escaped = 0;
+ size_t i;
+ char *ptr = NULL, current_char;
+ size_t start_token_len = strlen(start_token);
+ size_t end_token_len = strlen(end_token);
+ if (start_token_len == 0 || end_token_len == 0) {
+ return;
+ }
+ while ((current_char = *string) != '\0') {
+ if (current_char == '\\' && !escaped) {
+ escaped = 1;
+ string++;
+ continue;
+ } else if (current_char == '\"' && !escaped) {
+ in_string = !in_string;
+ } else if (!in_string && strncmp(string, start_token, start_token_len) == 0) {
+ for(i = 0; i < start_token_len; i++) {
+ string[i] = ' ';
+ }
+ string = string + start_token_len;
+ ptr = strstr(string, end_token);
+ if (!ptr) {
+ return;
+ }
+ for (i = 0; i < (ptr - string) + end_token_len; i++) {
+ string[i] = ' ';
+ }
+ string = ptr + end_token_len - 1;
+ }
+ escaped = 0;
+ string++;
+ }
+}
+
+/* JSON Object */
+static JSON_Object * json_object_init(JSON_Value *wrapping_value) {
+ JSON_Object *new_obj = (JSON_Object*)parson_malloc(sizeof(JSON_Object));
+ if (new_obj == NULL) {
+ return NULL;
+ }
+ new_obj->wrapping_value = wrapping_value;
+ new_obj->names = (char**)NULL;
+ new_obj->values = (JSON_Value**)NULL;
+ new_obj->capacity = 0;
+ new_obj->count = 0;
+ return new_obj;
+}
+
+static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value) {
+ if (name == NULL) {
+ return JSONFailure;
+ }
+ return json_object_addn(object, name, strlen(name), value);
+}
+
+static JSON_Status json_object_addn(JSON_Object *object, const char *name, size_t name_len, JSON_Value *value) {
+ size_t index = 0;
+ if (object == NULL || name == NULL || value == NULL) {
+ return JSONFailure;
+ }
+ if (json_object_getn_value(object, name, name_len) != NULL) {
+ return JSONFailure;
+ }
+ if (object->count >= object->capacity) {
+ size_t new_capacity = MAX(object->capacity * 2, STARTING_CAPACITY);
+ if (json_object_resize(object, new_capacity) == JSONFailure) {
+ return JSONFailure;
+ }
+ }
+ index = object->count;
+ object->names[index] = parson_strndup(name, name_len);
+ if (object->names[index] == NULL) {
+ return JSONFailure;
+ }
+ value->parent = json_object_get_wrapping_value(object);
+ object->values[index] = value;
+ object->count++;
+ return JSONSuccess;
+}
+
+static JSON_Status json_object_resize(JSON_Object *object, size_t new_capacity) {
+ char **temp_names = NULL;
+ JSON_Value **temp_values = NULL;
+
+ if ((object->names == NULL && object->values != NULL) ||
+ (object->names != NULL && object->values == NULL) ||
+ new_capacity == 0) {
+ return JSONFailure; /* Shouldn't happen */
+ }
+ temp_names = (char**)parson_malloc(new_capacity * sizeof(char*));
+ if (temp_names == NULL) {
+ return JSONFailure;
+ }
+ temp_values = (JSON_Value**)parson_malloc(new_capacity * sizeof(JSON_Value*));
+ if (temp_values == NULL) {
+ parson_free(temp_names);
+ return JSONFailure;
+ }
+ if (object->names != NULL && object->values != NULL && object->count > 0) {
+ memcpy(temp_names, object->names, object->count * sizeof(char*));
+ memcpy(temp_values, object->values, object->count * sizeof(JSON_Value*));
+ }
+ parson_free(object->names);
+ parson_free(object->values);
+ object->names = temp_names;
+ object->values = temp_values;
+ object->capacity = new_capacity;
+ return JSONSuccess;
+}
+
+static JSON_Value * json_object_getn_value(const JSON_Object *object, const char *name, size_t name_len) {
+ size_t i, name_length;
+ for (i = 0; i < json_object_get_count(object); i++) {
+ name_length = strlen(object->names[i]);
+ if (name_length != name_len) {
+ continue;
+ }
+ if (strncmp(object->names[i], name, name_len) == 0) {
+ return object->values[i];
+ }
+ }
+ return NULL;
+}
+
+static JSON_Status json_object_remove_internal(JSON_Object *object, const char *name, int free_value) {
+ size_t i = 0, last_item_index = 0;
+ if (object == NULL || json_object_get_value(object, name) == NULL) {
+ return JSONFailure;
+ }
+ last_item_index = json_object_get_count(object) - 1;
+ for (i = 0; i < json_object_get_count(object); i++) {
+ if (strcmp(object->names[i], name) == 0) {
+ parson_free(object->names[i]);
+ if (free_value) {
+ json_value_free(object->values[i]);
+ }
+ if (i != last_item_index) { /* Replace key value pair with one from the end */
+ object->names[i] = object->names[last_item_index];
+ object->values[i] = object->values[last_item_index];
+ }
+ object->count -= 1;
+ return JSONSuccess;
+ }
+ }
+ return JSONFailure; /* No execution path should end here */
+}
+
+static JSON_Status json_object_dotremove_internal(JSON_Object *object, const char *name, int free_value) {
+ JSON_Value *temp_value = NULL;
+ JSON_Object *temp_object = NULL;
+ const char *dot_pos = strchr(name, '.');
+ if (dot_pos == NULL) {
+ return json_object_remove_internal(object, name, free_value);
+ }
+ temp_value = json_object_getn_value(object, name, dot_pos - name);
+ if (json_value_get_type(temp_value) != JSONObject) {
+ return JSONFailure;
+ }
+ temp_object = json_value_get_object(temp_value);
+ return json_object_dotremove_internal(temp_object, dot_pos + 1, free_value);
+}
+
+static void json_object_free(JSON_Object *object) {
+ size_t i;
+ for (i = 0; i < object->count; i++) {
+ parson_free(object->names[i]);
+ json_value_free(object->values[i]);
+ }
+ parson_free(object->names);
+ parson_free(object->values);
+ parson_free(object);
+}
+
+/* JSON Array */
+static JSON_Array * json_array_init(JSON_Value *wrapping_value) {
+ JSON_Array *new_array = (JSON_Array*)parson_malloc(sizeof(JSON_Array));
+ if (new_array == NULL) {
+ return NULL;
+ }
+ new_array->wrapping_value = wrapping_value;
+ new_array->items = (JSON_Value**)NULL;
+ new_array->capacity = 0;
+ new_array->count = 0;
+ return new_array;
+}
+
+static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value) {
+ if (array->count >= array->capacity) {
+ size_t new_capacity = MAX(array->capacity * 2, STARTING_CAPACITY);
+ if (json_array_resize(array, new_capacity) == JSONFailure) {
+ return JSONFailure;
+ }
+ }
+ value->parent = json_array_get_wrapping_value(array);
+ array->items[array->count] = value;
+ array->count++;
+ return JSONSuccess;
+}
+
+static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity) {
+ JSON_Value **new_items = NULL;
+ if (new_capacity == 0) {
+ return JSONFailure;
+ }
+ new_items = (JSON_Value**)parson_malloc(new_capacity * sizeof(JSON_Value*));
+ if (new_items == NULL) {
+ return JSONFailure;
+ }
+ if (array->items != NULL && array->count > 0) {
+ memcpy(new_items, array->items, array->count * sizeof(JSON_Value*));
+ }
+ parson_free(array->items);
+ array->items = new_items;
+ array->capacity = new_capacity;
+ return JSONSuccess;
+}
+
+static void json_array_free(JSON_Array *array) {
+ size_t i;
+ for (i = 0; i < array->count; i++) {
+ json_value_free(array->items[i]);
+ }
+ parson_free(array->items);
+ parson_free(array);
+}
+
+/* JSON Value */
+static JSON_Value * json_value_init_string_no_copy(char *string) {
+ JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (!new_value) {
+ return NULL;
+ }
+ new_value->parent = NULL;
+ new_value->type = JSONString;
+ new_value->value.string = string;
+ return new_value;
+}
+
+/* Parser */
+static JSON_Status skip_quotes(const char **string) {
+ if (**string != '\"') {
+ return JSONFailure;
+ }
+ SKIP_CHAR(string);
+ while (**string != '\"') {
+ if (**string == '\0') {
+ return JSONFailure;
+ } else if (**string == '\\') {
+ SKIP_CHAR(string);
+ if (**string == '\0') {
+ return JSONFailure;
+ }
+ }
+ SKIP_CHAR(string);
+ }
+ SKIP_CHAR(string);
+ return JSONSuccess;
+}
+
+static int parse_utf16(const char **unprocessed, char **processed) {
+ unsigned int cp, lead, trail;
+ int parse_succeeded = 0;
+ char *processed_ptr = *processed;
+ const char *unprocessed_ptr = *unprocessed;
+ unprocessed_ptr++; /* skips u */
+ parse_succeeded = parse_utf16_hex(unprocessed_ptr, &cp);
+ if (!parse_succeeded) {
+ return JSONFailure;
+ }
+ if (cp < 0x80) {
+ processed_ptr[0] = (char)cp; /* 0xxxxxxx */
+ } else if (cp < 0x800) {
+ processed_ptr[0] = ((cp >> 6) & 0x1F) | 0xC0; /* 110xxxxx */
+ processed_ptr[1] = ((cp) & 0x3F) | 0x80; /* 10xxxxxx */
+ processed_ptr += 1;
+ } else if (cp < 0xD800 || cp > 0xDFFF) {
+ processed_ptr[0] = ((cp >> 12) & 0x0F) | 0xE0; /* 1110xxxx */
+ processed_ptr[1] = ((cp >> 6) & 0x3F) | 0x80; /* 10xxxxxx */
+ processed_ptr[2] = ((cp) & 0x3F) | 0x80; /* 10xxxxxx */
+ processed_ptr += 2;
+ } else if (cp >= 0xD800 && cp <= 0xDBFF) { /* lead surrogate (0xD800..0xDBFF) */
+ lead = cp;
+ unprocessed_ptr += 4; /* should always be within the buffer, otherwise previous sscanf would fail */
+ if (*unprocessed_ptr++ != '\\' || *unprocessed_ptr++ != 'u') {
+ return JSONFailure;
+ }
+ parse_succeeded = parse_utf16_hex(unprocessed_ptr, &trail);
+ if (!parse_succeeded || trail < 0xDC00 || trail > 0xDFFF) { /* valid trail surrogate? (0xDC00..0xDFFF) */
+ return JSONFailure;
+ }
+ cp = ((((lead - 0xD800) & 0x3FF) << 10) | ((trail - 0xDC00) & 0x3FF)) + 0x010000;
+ processed_ptr[0] = (((cp >> 18) & 0x07) | 0xF0); /* 11110xxx */
+ processed_ptr[1] = (((cp >> 12) & 0x3F) | 0x80); /* 10xxxxxx */
+ processed_ptr[2] = (((cp >> 6) & 0x3F) | 0x80); /* 10xxxxxx */
+ processed_ptr[3] = (((cp) & 0x3F) | 0x80); /* 10xxxxxx */
+ processed_ptr += 3;
+ } else { /* trail surrogate before lead surrogate */
+ return JSONFailure;
+ }
+ unprocessed_ptr += 3;
+ *processed = processed_ptr;
+ *unprocessed = unprocessed_ptr;
+ return JSONSuccess;
+}
+
+
+/* Copies and processes passed string up to supplied length.
+Example: "\u006Corem ipsum" -> lorem ipsum */
+static char* process_string(const char *input, size_t len) {
+ const char *input_ptr = input;
+ size_t initial_size = (len + 1) * sizeof(char);
+ size_t final_size = 0;
+ char *output = NULL, *output_ptr = NULL, *resized_output = NULL;
+ output = (char*)parson_malloc(initial_size);
+ if (output == NULL) {
+ goto error;
+ }
+ output_ptr = output;
+ while ((*input_ptr != '\0') && (size_t)(input_ptr - input) < len) {
+ if (*input_ptr == '\\') {
+ input_ptr++;
+ switch (*input_ptr) {
+ case '\"': *output_ptr = '\"'; break;
+ case '\\': *output_ptr = '\\'; break;
+ case '/': *output_ptr = '/'; break;
+ case 'b': *output_ptr = '\b'; break;
+ case 'f': *output_ptr = '\f'; break;
+ case 'n': *output_ptr = '\n'; break;
+ case 'r': *output_ptr = '\r'; break;
+ case 't': *output_ptr = '\t'; break;
+ case 'u':
+ if (parse_utf16(&input_ptr, &output_ptr) == JSONFailure) {
+ goto error;
+ }
+ break;
+ default:
+ goto error;
+ }
+ } else if ((unsigned char)*input_ptr < 0x20) {
+ goto error; /* 0x00-0x19 are invalid characters for json string (http://www.ietf.org/rfc/rfc4627.txt) */
+ } else {
+ *output_ptr = *input_ptr;
+ }
+ output_ptr++;
+ input_ptr++;
+ }
+ *output_ptr = '\0';
+ /* resize to new length */
+ final_size = (size_t)(output_ptr-output) + 1;
+ /* todo: don't resize if final_size == initial_size */
+ resized_output = (char*)parson_malloc(final_size);
+ if (resized_output == NULL) {
+ goto error;
+ }
+ memcpy(resized_output, output, final_size);
+ parson_free(output);
+ return resized_output;
+error:
+ parson_free(output);
+ return NULL;
+}
+
+/* Return processed contents of a string between quotes and
+ skips passed argument to a matching quote. */
+static char * get_quoted_string(const char **string) {
+ const char *string_start = *string;
+ size_t string_len = 0;
+ JSON_Status status = skip_quotes(string);
+ if (status != JSONSuccess) {
+ return NULL;
+ }
+ string_len = *string - string_start - 2; /* length without quotes */
+ return process_string(string_start + 1, string_len);
+}
+
+static JSON_Value * parse_value(const char **string, size_t nesting) {
+ if (nesting > MAX_NESTING) {
+ return NULL;
+ }
+ SKIP_WHITESPACES(string);
+ switch (**string) {
+ case '{':
+ return parse_object_value(string, nesting + 1);
+ case '[':
+ return parse_array_value(string, nesting + 1);
+ case '\"':
+ return parse_string_value(string);
+ case 'f': case 't':
+ return parse_boolean_value(string);
+ case '-':
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ return parse_number_value(string);
+ case 'n':
+ return parse_null_value(string);
+ default:
+ return NULL;
+ }
+}
+
+static JSON_Value * parse_object_value(const char **string, size_t nesting) {
+ JSON_Value *output_value = NULL, *new_value = NULL;
+ JSON_Object *output_object = NULL;
+ char *new_key = NULL;
+ output_value = json_value_init_object();
+ if (output_value == NULL) {
+ return NULL;
+ }
+ if (**string != '{') {
+ json_value_free(output_value);
+ return NULL;
+ }
+ output_object = json_value_get_object(output_value);
+ SKIP_CHAR(string);
+ SKIP_WHITESPACES(string);
+ if (**string == '}') { /* empty object */
+ SKIP_CHAR(string);
+ return output_value;
+ }
+ while (**string != '\0') {
+ new_key = get_quoted_string(string);
+ if (new_key == NULL) {
+ json_value_free(output_value);
+ return NULL;
+ }
+ SKIP_WHITESPACES(string);
+ if (**string != ':') {
+ parson_free(new_key);
+ json_value_free(output_value);
+ return NULL;
+ }
+ SKIP_CHAR(string);
+ new_value = parse_value(string, nesting);
+ if (new_value == NULL) {
+ parson_free(new_key);
+ json_value_free(output_value);
+ return NULL;
+ }
+ if (json_object_add(output_object, new_key, new_value) == JSONFailure) {
+ parson_free(new_key);
+ json_value_free(new_value);
+ json_value_free(output_value);
+ return NULL;
+ }
+ parson_free(new_key);
+ SKIP_WHITESPACES(string);
+ if (**string != ',') {
+ break;
+ }
+ SKIP_CHAR(string);
+ SKIP_WHITESPACES(string);
+ }
+ SKIP_WHITESPACES(string);
+ if (**string != '}' || /* Trim object after parsing is over */
+ json_object_resize(output_object, json_object_get_count(output_object)) == JSONFailure) {
+ json_value_free(output_value);
+ return NULL;
+ }
+ SKIP_CHAR(string);
+ return output_value;
+}
+
+static JSON_Value * parse_array_value(const char **string, size_t nesting) {
+ JSON_Value *output_value = NULL, *new_array_value = NULL;
+ JSON_Array *output_array = NULL;
+ output_value = json_value_init_array();
+ if (output_value == NULL) {
+ return NULL;
+ }
+ if (**string != '[') {
+ json_value_free(output_value);
+ return NULL;
+ }
+ output_array = json_value_get_array(output_value);
+ SKIP_CHAR(string);
+ SKIP_WHITESPACES(string);
+ if (**string == ']') { /* empty array */
+ SKIP_CHAR(string);
+ return output_value;
+ }
+ while (**string != '\0') {
+ new_array_value = parse_value(string, nesting);
+ if (new_array_value == NULL) {
+ json_value_free(output_value);
+ return NULL;
+ }
+ if (json_array_add(output_array, new_array_value) == JSONFailure) {
+ json_value_free(new_array_value);
+ json_value_free(output_value);
+ return NULL;
+ }
+ SKIP_WHITESPACES(string);
+ if (**string != ',') {
+ break;
+ }
+ SKIP_CHAR(string);
+ SKIP_WHITESPACES(string);
+ }
+ SKIP_WHITESPACES(string);
+ if (**string != ']' || /* Trim array after parsing is over */
+ json_array_resize(output_array, json_array_get_count(output_array)) == JSONFailure) {
+ json_value_free(output_value);
+ return NULL;
+ }
+ SKIP_CHAR(string);
+ return output_value;
+}
+
+static JSON_Value * parse_string_value(const char **string) {
+ JSON_Value *value = NULL;
+ char *new_string = get_quoted_string(string);
+ if (new_string == NULL) {
+ return NULL;
+ }
+ value = json_value_init_string_no_copy(new_string);
+ if (value == NULL) {
+ parson_free(new_string);
+ return NULL;
+ }
+ return value;
+}
+
+static JSON_Value * parse_boolean_value(const char **string) {
+ size_t true_token_size = SIZEOF_TOKEN("true");
+ size_t false_token_size = SIZEOF_TOKEN("false");
+ if (strncmp("true", *string, true_token_size) == 0) {
+ *string += true_token_size;
+ return json_value_init_boolean(1);
+ } else if (strncmp("false", *string, false_token_size) == 0) {
+ *string += false_token_size;
+ return json_value_init_boolean(0);
+ }
+ return NULL;
+}
+
+static JSON_Value * parse_number_value(const char **string) {
+ char *end;
+ double number = 0;
+ errno = 0;
+ number = strtod(*string, &end);
+ if (errno || !is_decimal(*string, end - *string)) {
+ return NULL;
+ }
+ *string = end;
+ return json_value_init_number(number);
+}
+
+static JSON_Value * parse_null_value(const char **string) {
+ size_t token_size = SIZEOF_TOKEN("null");
+ if (strncmp("null", *string, token_size) == 0) {
+ *string += token_size;
+ return json_value_init_null();
+ }
+ return NULL;
+}
+
+/* Serialization */
+#define APPEND_STRING(str) do { written = append_string(buf, (str));\
+ if (written < 0) { return -1; }\
+ if (buf != NULL) { buf += written; }\
+ written_total += written; } while(0)
+
+#define APPEND_INDENT(level) do { written = append_indent(buf, (level));\
+ if (written < 0) { return -1; }\
+ if (buf != NULL) { buf += written; }\
+ written_total += written; } while(0)
+
+static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty, char *num_buf)
+{
+ const char *key = NULL, *string = NULL;
+ JSON_Value *temp_value = NULL;
+ JSON_Array *array = NULL;
+ JSON_Object *object = NULL;
+ size_t i = 0, count = 0;
+ double num = 0.0;
+ int written = -1, written_total = 0;
+
+ switch (json_value_get_type(value)) {
+ case JSONArray:
+ array = json_value_get_array(value);
+ count = json_array_get_count(array);
+ APPEND_STRING("[");
+ if (count > 0 && is_pretty) {
+ APPEND_STRING("\n");
+ }
+ for (i = 0; i < count; i++) {
+ if (is_pretty) {
+ APPEND_INDENT(level+1);
+ }
+ temp_value = json_array_get_value(array, i);
+ written = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty, num_buf);
+ if (written < 0) {
+ return -1;
+ }
+ if (buf != NULL) {
+ buf += written;
+ }
+ written_total += written;
+ if (i < (count - 1)) {
+ APPEND_STRING(",");
+ }
+ if (is_pretty) {
+ APPEND_STRING("\n");
+ }
+ }
+ if (count > 0 && is_pretty) {
+ APPEND_INDENT(level);
+ }
+ APPEND_STRING("]");
+ return written_total;
+ case JSONObject:
+ object = json_value_get_object(value);
+ count = json_object_get_count(object);
+ APPEND_STRING("{");
+ if (count > 0 && is_pretty) {
+ APPEND_STRING("\n");
+ }
+ for (i = 0; i < count; i++) {
+ key = json_object_get_name(object, i);
+ if (key == NULL) {
+ return -1;
+ }
+ if (is_pretty) {
+ APPEND_INDENT(level+1);
+ }
+ written = json_serialize_string(key, buf);
+ if (written < 0) {
+ return -1;
+ }
+ if (buf != NULL) {
+ buf += written;
+ }
+ written_total += written;
+ APPEND_STRING(":");
+ if (is_pretty) {
+ APPEND_STRING(" ");
+ }
+ temp_value = json_object_get_value(object, key);
+ written = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty, num_buf);
+ if (written < 0) {
+ return -1;
+ }
+ if (buf != NULL) {
+ buf += written;
+ }
+ written_total += written;
+ if (i < (count - 1)) {
+ APPEND_STRING(",");
+ }
+ if (is_pretty) {
+ APPEND_STRING("\n");
+ }
+ }
+ if (count > 0 && is_pretty) {
+ APPEND_INDENT(level);
+ }
+ APPEND_STRING("}");
+ return written_total;
+ case JSONString:
+ string = json_value_get_string(value);
+ if (string == NULL) {
+ return -1;
+ }
+ written = json_serialize_string(string, buf);
+ if (written < 0) {
+ return -1;
+ }
+ if (buf != NULL) {
+ buf += written;
+ }
+ written_total += written;
+ return written_total;
+ case JSONBoolean:
+ if (json_value_get_boolean(value)) {
+ APPEND_STRING("true");
+ } else {
+ APPEND_STRING("false");
+ }
+ return written_total;
+ case JSONNumber:
+ num = json_value_get_number(value);
+ if (buf != NULL) {
+ num_buf = buf;
+ }
+ written = sprintf(num_buf, FLOAT_FORMAT, num);
+ if (written < 0) {
+ return -1;
+ }
+ if (buf != NULL) {
+ buf += written;
+ }
+ written_total += written;
+ return written_total;
+ case JSONNull:
+ APPEND_STRING("null");
+ return written_total;
+ case JSONError:
+ return -1;
+ default:
+ return -1;
+ }
+}
+
+static int json_serialize_string(const char *string, char *buf) {
+ size_t i = 0, len = strlen(string);
+ char c = '\0';
+ int written = -1, written_total = 0;
+ APPEND_STRING("\"");
+ for (i = 0; i < len; i++) {
+ c = string[i];
+ switch (c) {
+ case '\"': APPEND_STRING("\\\""); break;
+ case '\\': APPEND_STRING("\\\\"); break;
+ case '\b': APPEND_STRING("\\b"); break;
+ case '\f': APPEND_STRING("\\f"); break;
+ case '\n': APPEND_STRING("\\n"); break;
+ case '\r': APPEND_STRING("\\r"); break;
+ case '\t': APPEND_STRING("\\t"); break;
+ case '\x00': APPEND_STRING("\\u0000"); break;
+ case '\x01': APPEND_STRING("\\u0001"); break;
+ case '\x02': APPEND_STRING("\\u0002"); break;
+ case '\x03': APPEND_STRING("\\u0003"); break;
+ case '\x04': APPEND_STRING("\\u0004"); break;
+ case '\x05': APPEND_STRING("\\u0005"); break;
+ case '\x06': APPEND_STRING("\\u0006"); break;
+ case '\x07': APPEND_STRING("\\u0007"); break;
+ /* '\x08' duplicate: '\b' */
+ /* '\x09' duplicate: '\t' */
+ /* '\x0a' duplicate: '\n' */
+ case '\x0b': APPEND_STRING("\\u000b"); break;
+ /* '\x0c' duplicate: '\f' */
+ /* '\x0d' duplicate: '\r' */
+ case '\x0e': APPEND_STRING("\\u000e"); break;
+ case '\x0f': APPEND_STRING("\\u000f"); break;
+ case '\x10': APPEND_STRING("\\u0010"); break;
+ case '\x11': APPEND_STRING("\\u0011"); break;
+ case '\x12': APPEND_STRING("\\u0012"); break;
+ case '\x13': APPEND_STRING("\\u0013"); break;
+ case '\x14': APPEND_STRING("\\u0014"); break;
+ case '\x15': APPEND_STRING("\\u0015"); break;
+ case '\x16': APPEND_STRING("\\u0016"); break;
+ case '\x17': APPEND_STRING("\\u0017"); break;
+ case '\x18': APPEND_STRING("\\u0018"); break;
+ case '\x19': APPEND_STRING("\\u0019"); break;
+ case '\x1a': APPEND_STRING("\\u001a"); break;
+ case '\x1b': APPEND_STRING("\\u001b"); break;
+ case '\x1c': APPEND_STRING("\\u001c"); break;
+ case '\x1d': APPEND_STRING("\\u001d"); break;
+ case '\x1e': APPEND_STRING("\\u001e"); break;
+ case '\x1f': APPEND_STRING("\\u001f"); break;
+ case '/':
+ if (parson_escape_slashes) {
+ APPEND_STRING("\\/"); /* to make json embeddable in xml\/html */
+ } else {
+ APPEND_STRING("/");
+ }
+ break;
+ default:
+ if (buf != NULL) {
+ buf[0] = c;
+ buf += 1;
+ }
+ written_total += 1;
+ break;
+ }
+ }
+ APPEND_STRING("\"");
+ return written_total;
+}
+
+static int append_indent(char *buf, int level) {
+ int i;
+ int written = -1, written_total = 0;
+ for (i = 0; i < level; i++) {
+ APPEND_STRING(" ");
+ }
+ return written_total;
+}
+
+static int append_string(char *buf, const char *string) {
+ if (buf == NULL) {
+ return (int)strlen(string);
+ }
+ return sprintf(buf, "%s", string);
+}
+
+#undef APPEND_STRING
+#undef APPEND_INDENT
+
+/* Parser API */
+JSON_Value * json_parse_file(const char *filename) {
+ char *file_contents = read_file(filename);
+ JSON_Value *output_value = NULL;
+ if (file_contents == NULL) {
+ return NULL;
+ }
+ output_value = json_parse_string(file_contents);
+ parson_free(file_contents);
+ return output_value;
+}
+
+JSON_Value * json_parse_file_with_comments(const char *filename) {
+ char *file_contents = read_file(filename);
+ JSON_Value *output_value = NULL;
+ if (file_contents == NULL) {
+ return NULL;
+ }
+ output_value = json_parse_string_with_comments(file_contents);
+ parson_free(file_contents);
+ return output_value;
+}
+
+JSON_Value * json_parse_string(const char *string) {
+ if (string == NULL) {
+ return NULL;
+ }
+ if (string[0] == '\xEF' && string[1] == '\xBB' && string[2] == '\xBF') {
+ string = string + 3; /* Support for UTF-8 BOM */
+ }
+ return parse_value((const char**)&string, 0);
+}
+
+JSON_Value * json_parse_string_with_comments(const char *string) {
+ JSON_Value *result = NULL;
+ char *string_mutable_copy = NULL, *string_mutable_copy_ptr = NULL;
+ string_mutable_copy = parson_strdup(string);
+ if (string_mutable_copy == NULL) {
+ return NULL;
+ }
+ remove_comments(string_mutable_copy, "/*", "*/");
+ remove_comments(string_mutable_copy, "//", "\n");
+ string_mutable_copy_ptr = string_mutable_copy;
+ result = parse_value((const char**)&string_mutable_copy_ptr, 0);
+ parson_free(string_mutable_copy);
+ return result;
+}
+
+/* JSON Object API */
+
+JSON_Value * json_object_get_value(const JSON_Object *object, const char *name) {
+ if (object == NULL || name == NULL) {
+ return NULL;
+ }
+ return json_object_getn_value(object, name, strlen(name));
+}
+
+const char * json_object_get_string(const JSON_Object *object, const char *name) {
+ return json_value_get_string(json_object_get_value(object, name));
+}
+
+double json_object_get_number(const JSON_Object *object, const char *name) {
+ return json_value_get_number(json_object_get_value(object, name));
+}
+
+JSON_Object * json_object_get_object(const JSON_Object *object, const char *name) {
+ return json_value_get_object(json_object_get_value(object, name));
+}
+
+JSON_Array * json_object_get_array(const JSON_Object *object, const char *name) {
+ return json_value_get_array(json_object_get_value(object, name));
+}
+
+int json_object_get_boolean(const JSON_Object *object, const char *name) {
+ return json_value_get_boolean(json_object_get_value(object, name));
+}
+
+JSON_Value * json_object_dotget_value(const JSON_Object *object, const char *name) {
+ const char *dot_position = strchr(name, '.');
+ if (!dot_position) {
+ return json_object_get_value(object, name);
+ }
+ object = json_value_get_object(json_object_getn_value(object, name, dot_position - name));
+ return json_object_dotget_value(object, dot_position + 1);
+}
+
+const char * json_object_dotget_string(const JSON_Object *object, const char *name) {
+ return json_value_get_string(json_object_dotget_value(object, name));
+}
+
+double json_object_dotget_number(const JSON_Object *object, const char *name) {
+ return json_value_get_number(json_object_dotget_value(object, name));
+}
+
+JSON_Object * json_object_dotget_object(const JSON_Object *object, const char *name) {
+ return json_value_get_object(json_object_dotget_value(object, name));
+}
+
+JSON_Array * json_object_dotget_array(const JSON_Object *object, const char *name) {
+ return json_value_get_array(json_object_dotget_value(object, name));
+}
+
+int json_object_dotget_boolean(const JSON_Object *object, const char *name) {
+ return json_value_get_boolean(json_object_dotget_value(object, name));
+}
+
+size_t json_object_get_count(const JSON_Object *object) {
+ return object ? object->count : 0;
+}
+
+const char * json_object_get_name(const JSON_Object *object, size_t index) {
+ if (object == NULL || index >= json_object_get_count(object)) {
+ return NULL;
+ }
+ return object->names[index];
+}
+
+JSON_Value * json_object_get_value_at(const JSON_Object *object, size_t index) {
+ if (object == NULL || index >= json_object_get_count(object)) {
+ return NULL;
+ }
+ return object->values[index];
+}
+
+JSON_Value *json_object_get_wrapping_value(const JSON_Object *object) {
+ return object->wrapping_value;
+}
+
+int json_object_has_value (const JSON_Object *object, const char *name) {
+ return json_object_get_value(object, name) != NULL;
+}
+
+int json_object_has_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type) {
+ JSON_Value *val = json_object_get_value(object, name);
+ return val != NULL && json_value_get_type(val) == type;
+}
+
+int json_object_dothas_value (const JSON_Object *object, const char *name) {
+ return json_object_dotget_value(object, name) != NULL;
+}
+
+int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type) {
+ JSON_Value *val = json_object_dotget_value(object, name);
+ return val != NULL && json_value_get_type(val) == type;
+}
+
+/* JSON Array API */
+JSON_Value * json_array_get_value(const JSON_Array *array, size_t index) {
+ if (array == NULL || index >= json_array_get_count(array)) {
+ return NULL;
+ }
+ return array->items[index];
+}
+
+const char * json_array_get_string(const JSON_Array *array, size_t index) {
+ return json_value_get_string(json_array_get_value(array, index));
+}
+
+double json_array_get_number(const JSON_Array *array, size_t index) {
+ return json_value_get_number(json_array_get_value(array, index));
+}
+
+JSON_Object * json_array_get_object(const JSON_Array *array, size_t index) {
+ return json_value_get_object(json_array_get_value(array, index));
+}
+
+JSON_Array * json_array_get_array(const JSON_Array *array, size_t index) {
+ return json_value_get_array(json_array_get_value(array, index));
+}
+
+int json_array_get_boolean(const JSON_Array *array, size_t index) {
+ return json_value_get_boolean(json_array_get_value(array, index));
+}
+
+size_t json_array_get_count(const JSON_Array *array) {
+ return array ? array->count : 0;
+}
+
+JSON_Value * json_array_get_wrapping_value(const JSON_Array *array) {
+ return array->wrapping_value;
+}
+
+/* JSON Value API */
+JSON_Value_Type json_value_get_type(const JSON_Value *value) {
+ return value ? value->type : JSONError;
+}
+
+JSON_Object * json_value_get_object(const JSON_Value *value) {
+ return json_value_get_type(value) == JSONObject ? value->value.object : NULL;
+}
+
+JSON_Array * json_value_get_array(const JSON_Value *value) {
+ return json_value_get_type(value) == JSONArray ? value->value.array : NULL;
+}
+
+const char * json_value_get_string(const JSON_Value *value) {
+ return json_value_get_type(value) == JSONString ? value->value.string : NULL;
+}
+
+double json_value_get_number(const JSON_Value *value) {
+ return json_value_get_type(value) == JSONNumber ? value->value.number : 0;
+}
+
+int json_value_get_boolean(const JSON_Value *value) {
+ return json_value_get_type(value) == JSONBoolean ? value->value.boolean : -1;
+}
+
+JSON_Value * json_value_get_parent (const JSON_Value *value) {
+ return value ? value->parent : NULL;
+}
+
+void json_value_free(JSON_Value *value) {
+ switch (json_value_get_type(value)) {
+ case JSONObject:
+ json_object_free(value->value.object);
+ break;
+ case JSONString:
+ parson_free(value->value.string);
+ break;
+ case JSONArray:
+ json_array_free(value->value.array);
+ break;
+ default:
+ break;
+ }
+ parson_free(value);
+}
+
+JSON_Value * json_value_init_object(void) {
+ JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (!new_value) {
+ return NULL;
+ }
+ new_value->parent = NULL;
+ new_value->type = JSONObject;
+ new_value->value.object = json_object_init(new_value);
+ if (!new_value->value.object) {
+ parson_free(new_value);
+ return NULL;
+ }
+ return new_value;
+}
+
+JSON_Value * json_value_init_array(void) {
+ JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (!new_value) {
+ return NULL;
+ }
+ new_value->parent = NULL;
+ new_value->type = JSONArray;
+ new_value->value.array = json_array_init(new_value);
+ if (!new_value->value.array) {
+ parson_free(new_value);
+ return NULL;
+ }
+ return new_value;
+}
+
+JSON_Value * json_value_init_string(const char *string) {
+ char *copy = NULL;
+ JSON_Value *value;
+ size_t string_len = 0;
+ if (string == NULL) {
+ return NULL;
+ }
+ string_len = strlen(string);
+ if (!is_valid_utf8(string, string_len)) {
+ return NULL;
+ }
+ copy = parson_strndup(string, string_len);
+ if (copy == NULL) {
+ return NULL;
+ }
+ value = json_value_init_string_no_copy(copy);
+ if (value == NULL) {
+ parson_free(copy);
+ }
+ return value;
+}
+
+JSON_Value * json_value_init_number(double number) {
+ JSON_Value *new_value = NULL;
+ if (IS_NUMBER_INVALID(number)) {
+ return NULL;
+ }
+ new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (new_value == NULL) {
+ return NULL;
+ }
+ new_value->parent = NULL;
+ new_value->type = JSONNumber;
+ new_value->value.number = number;
+ return new_value;
+}
+
+JSON_Value * json_value_init_boolean(int boolean) {
+ JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (!new_value) {
+ return NULL;
+ }
+ new_value->parent = NULL;
+ new_value->type = JSONBoolean;
+ new_value->value.boolean = boolean ? 1 : 0;
+ return new_value;
+}
+
+JSON_Value * json_value_init_null(void) {
+ JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (!new_value) {
+ return NULL;
+ }
+ new_value->parent = NULL;
+ new_value->type = JSONNull;
+ return new_value;
+}
+
+JSON_Value * json_value_deep_copy(const JSON_Value *value) {
+ size_t i = 0;
+ JSON_Value *return_value = NULL, *temp_value_copy = NULL, *temp_value = NULL;
+ const char *temp_string = NULL, *temp_key = NULL;
+ char *temp_string_copy = NULL;
+ JSON_Array *temp_array = NULL, *temp_array_copy = NULL;
+ JSON_Object *temp_object = NULL, *temp_object_copy = NULL;
+
+ switch (json_value_get_type(value)) {
+ case JSONArray:
+ temp_array = json_value_get_array(value);
+ return_value = json_value_init_array();
+ if (return_value == NULL) {
+ return NULL;
+ }
+ temp_array_copy = json_value_get_array(return_value);
+ for (i = 0; i < json_array_get_count(temp_array); i++) {
+ temp_value = json_array_get_value(temp_array, i);
+ temp_value_copy = json_value_deep_copy(temp_value);
+ if (temp_value_copy == NULL) {
+ json_value_free(return_value);
+ return NULL;
+ }
+ if (json_array_add(temp_array_copy, temp_value_copy) == JSONFailure) {
+ json_value_free(return_value);
+ json_value_free(temp_value_copy);
+ return NULL;
+ }
+ }
+ return return_value;
+ case JSONObject:
+ temp_object = json_value_get_object(value);
+ return_value = json_value_init_object();
+ if (return_value == NULL) {
+ return NULL;
+ }
+ temp_object_copy = json_value_get_object(return_value);
+ for (i = 0; i < json_object_get_count(temp_object); i++) {
+ temp_key = json_object_get_name(temp_object, i);
+ temp_value = json_object_get_value(temp_object, temp_key);
+ temp_value_copy = json_value_deep_copy(temp_value);
+ if (temp_value_copy == NULL) {
+ json_value_free(return_value);
+ return NULL;
+ }
+ if (json_object_add(temp_object_copy, temp_key, temp_value_copy) == JSONFailure) {
+ json_value_free(return_value);
+ json_value_free(temp_value_copy);
+ return NULL;
+ }
+ }
+ return return_value;
+ case JSONBoolean:
+ return json_value_init_boolean(json_value_get_boolean(value));
+ case JSONNumber:
+ return json_value_init_number(json_value_get_number(value));
+ case JSONString:
+ temp_string = json_value_get_string(value);
+ if (temp_string == NULL) {
+ return NULL;
+ }
+ temp_string_copy = parson_strdup(temp_string);
+ if (temp_string_copy == NULL) {
+ return NULL;
+ }
+ return_value = json_value_init_string_no_copy(temp_string_copy);
+ if (return_value == NULL) {
+ parson_free(temp_string_copy);
+ }
+ return return_value;
+ case JSONNull:
+ return json_value_init_null();
+ case JSONError:
+ return NULL;
+ default:
+ return NULL;
+ }
+}
+
+size_t json_serialization_size(const JSON_Value *value) {
+ char num_buf[NUM_BUF_SIZE]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */
+ int res = json_serialize_to_buffer_r(value, NULL, 0, 0, num_buf);
+ return res < 0 ? 0 : (size_t)(res + 1);
+}
+
+JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) {
+ int written = -1;
+ size_t needed_size_in_bytes = json_serialization_size(value);
+ if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) {
+ return JSONFailure;
+ }
+ written = json_serialize_to_buffer_r(value, buf, 0, 0, NULL);
+ if (written < 0) {
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename) {
+ JSON_Status return_code = JSONSuccess;
+ FILE *fp = NULL;
+ char *serialized_string = json_serialize_to_string(value);
+ if (serialized_string == NULL) {
+ return JSONFailure;
+ }
+ fp = fopen(filename, "w");
+ if (fp == NULL) {
+ json_free_serialized_string(serialized_string);
+ return JSONFailure;
+ }
+ if (fputs(serialized_string, fp) == EOF) {
+ return_code = JSONFailure;
+ }
+ if (fclose(fp) == EOF) {
+ return_code = JSONFailure;
+ }
+ json_free_serialized_string(serialized_string);
+ return return_code;
+}
+
+char * json_serialize_to_string(const JSON_Value *value) {
+ JSON_Status serialization_result = JSONFailure;
+ size_t buf_size_bytes = json_serialization_size(value);
+ char *buf = NULL;
+ if (buf_size_bytes == 0) {
+ return NULL;
+ }
+ buf = (char*)parson_malloc(buf_size_bytes);
+ if (buf == NULL) {
+ return NULL;
+ }
+ serialization_result = json_serialize_to_buffer(value, buf, buf_size_bytes);
+ if (serialization_result == JSONFailure) {
+ json_free_serialized_string(buf);
+ return NULL;
+ }
+ return buf;
+}
+
+size_t json_serialization_size_pretty(const JSON_Value *value) {
+ char num_buf[NUM_BUF_SIZE]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */
+ int res = json_serialize_to_buffer_r(value, NULL, 0, 1, num_buf);
+ return res < 0 ? 0 : (size_t)(res + 1);
+}
+
+JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) {
+ int written = -1;
+ size_t needed_size_in_bytes = json_serialization_size_pretty(value);
+ if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) {
+ return JSONFailure;
+ }
+ written = json_serialize_to_buffer_r(value, buf, 0, 1, NULL);
+ if (written < 0) {
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename) {
+ JSON_Status return_code = JSONSuccess;
+ FILE *fp = NULL;
+ char *serialized_string = json_serialize_to_string_pretty(value);
+ if (serialized_string == NULL) {
+ return JSONFailure;
+ }
+ fp = fopen(filename, "w");
+ if (fp == NULL) {
+ json_free_serialized_string(serialized_string);
+ return JSONFailure;
+ }
+ if (fputs(serialized_string, fp) == EOF) {
+ return_code = JSONFailure;
+ }
+ if (fclose(fp) == EOF) {
+ return_code = JSONFailure;
+ }
+ json_free_serialized_string(serialized_string);
+ return return_code;
+}
+
+char * json_serialize_to_string_pretty(const JSON_Value *value) {
+ JSON_Status serialization_result = JSONFailure;
+ size_t buf_size_bytes = json_serialization_size_pretty(value);
+ char *buf = NULL;
+ if (buf_size_bytes == 0) {
+ return NULL;
+ }
+ buf = (char*)parson_malloc(buf_size_bytes);
+ if (buf == NULL) {
+ return NULL;
+ }
+ serialization_result = json_serialize_to_buffer_pretty(value, buf, buf_size_bytes);
+ if (serialization_result == JSONFailure) {
+ json_free_serialized_string(buf);
+ return NULL;
+ }
+ return buf;
+}
+
+void json_free_serialized_string(char *string) {
+ parson_free(string);
+}
+
+JSON_Status json_array_remove(JSON_Array *array, size_t ix) {
+ size_t to_move_bytes = 0;
+ if (array == NULL || ix >= json_array_get_count(array)) {
+ return JSONFailure;
+ }
+ json_value_free(json_array_get_value(array, ix));
+ to_move_bytes = (json_array_get_count(array) - 1 - ix) * sizeof(JSON_Value*);
+ memmove(array->items + ix, array->items + ix + 1, to_move_bytes);
+ array->count -= 1;
+ return JSONSuccess;
+}
+
+JSON_Status json_array_replace_value(JSON_Array *array, size_t ix, JSON_Value *value) {
+ if (array == NULL || value == NULL || value->parent != NULL || ix >= json_array_get_count(array)) {
+ return JSONFailure;
+ }
+ json_value_free(json_array_get_value(array, ix));
+ value->parent = json_array_get_wrapping_value(array);
+ array->items[ix] = value;
+ return JSONSuccess;
+}
+
+JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string) {
+ JSON_Value *value = json_value_init_string(string);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_array_replace_value(array, i, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number) {
+ JSON_Value *value = json_value_init_number(number);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_array_replace_value(array, i, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean) {
+ JSON_Value *value = json_value_init_boolean(boolean);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_array_replace_value(array, i, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_replace_null(JSON_Array *array, size_t i) {
+ JSON_Value *value = json_value_init_null();
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_array_replace_value(array, i, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_clear(JSON_Array *array) {
+ size_t i = 0;
+ if (array == NULL) {
+ return JSONFailure;
+ }
+ for (i = 0; i < json_array_get_count(array); i++) {
+ json_value_free(json_array_get_value(array, i));
+ }
+ array->count = 0;
+ return JSONSuccess;
+}
+
+JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value) {
+ if (array == NULL || value == NULL || value->parent != NULL) {
+ return JSONFailure;
+ }
+ return json_array_add(array, value);
+}
+
+JSON_Status json_array_append_string(JSON_Array *array, const char *string) {
+ JSON_Value *value = json_value_init_string(string);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_array_append_value(array, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_append_number(JSON_Array *array, double number) {
+ JSON_Value *value = json_value_init_number(number);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_array_append_value(array, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_append_boolean(JSON_Array *array, int boolean) {
+ JSON_Value *value = json_value_init_boolean(boolean);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_array_append_value(array, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_append_null(JSON_Array *array) {
+ JSON_Value *value = json_value_init_null();
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_array_append_value(array, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value) {
+ size_t i = 0;
+ JSON_Value *old_value;
+ if (object == NULL || name == NULL || value == NULL || value->parent != NULL) {
+ return JSONFailure;
+ }
+ old_value = json_object_get_value(object, name);
+ if (old_value != NULL) { /* free and overwrite old value */
+ json_value_free(old_value);
+ for (i = 0; i < json_object_get_count(object); i++) {
+ if (strcmp(object->names[i], name) == 0) {
+ value->parent = json_object_get_wrapping_value(object);
+ object->values[i] = value;
+ return JSONSuccess;
+ }
+ }
+ }
+ /* add new key value pair */
+ return json_object_add(object, name, value);
+}
+
+JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string) {
+ JSON_Value *value = json_value_init_string(string);
+ JSON_Status status = json_object_set_value(object, name, value);
+ if (status == JSONFailure) {
+ json_value_free(value);
+ }
+ return status;
+}
+
+JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number) {
+ JSON_Value *value = json_value_init_number(number);
+ JSON_Status status = json_object_set_value(object, name, value);
+ if (status == JSONFailure) {
+ json_value_free(value);
+ }
+ return status;
+}
+
+JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean) {
+ JSON_Value *value = json_value_init_boolean(boolean);
+ JSON_Status status = json_object_set_value(object, name, value);
+ if (status == JSONFailure) {
+ json_value_free(value);
+ }
+ return status;
+}
+
+JSON_Status json_object_set_null(JSON_Object *object, const char *name) {
+ JSON_Value *value = json_value_init_null();
+ JSON_Status status = json_object_set_value(object, name, value);
+ if (status == JSONFailure) {
+ json_value_free(value);
+ }
+ return status;
+}
+
+JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value) {
+ const char *dot_pos = NULL;
+ JSON_Value *temp_value = NULL, *new_value = NULL;
+ JSON_Object *temp_object = NULL, *new_object = NULL;
+ JSON_Status status = JSONFailure;
+ size_t name_len = 0;
+ if (object == NULL || name == NULL || value == NULL) {
+ return JSONFailure;
+ }
+ dot_pos = strchr(name, '.');
+ if (dot_pos == NULL) {
+ return json_object_set_value(object, name, value);
+ }
+ name_len = dot_pos - name;
+ temp_value = json_object_getn_value(object, name, name_len);
+ if (temp_value) {
+ /* Don't overwrite existing non-object (unlike json_object_set_value, but it shouldn't be changed at this point) */
+ if (json_value_get_type(temp_value) != JSONObject) {
+ return JSONFailure;
+ }
+ temp_object = json_value_get_object(temp_value);
+ return json_object_dotset_value(temp_object, dot_pos + 1, value);
+ }
+ new_value = json_value_init_object();
+ if (new_value == NULL) {
+ return JSONFailure;
+ }
+ new_object = json_value_get_object(new_value);
+ status = json_object_dotset_value(new_object, dot_pos + 1, value);
+ if (status != JSONSuccess) {
+ json_value_free(new_value);
+ return JSONFailure;
+ }
+ status = json_object_addn(object, name, name_len, new_value);
+ if (status != JSONSuccess) {
+ json_object_dotremove_internal(new_object, dot_pos + 1, 0);
+ json_value_free(new_value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string) {
+ JSON_Value *value = json_value_init_string(string);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_object_dotset_value(object, name, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number) {
+ JSON_Value *value = json_value_init_number(number);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_object_dotset_value(object, name, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean) {
+ JSON_Value *value = json_value_init_boolean(boolean);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_object_dotset_value(object, name, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_dotset_null(JSON_Object *object, const char *name) {
+ JSON_Value *value = json_value_init_null();
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_object_dotset_value(object, name, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_remove(JSON_Object *object, const char *name) {
+ return json_object_remove_internal(object, name, 1);
+}
+
+JSON_Status json_object_dotremove(JSON_Object *object, const char *name) {
+ return json_object_dotremove_internal(object, name, 1);
+}
+
+JSON_Status json_object_clear(JSON_Object *object) {
+ size_t i = 0;
+ if (object == NULL) {
+ return JSONFailure;
+ }
+ for (i = 0; i < json_object_get_count(object); i++) {
+ parson_free(object->names[i]);
+ json_value_free(object->values[i]);
+ }
+ object->count = 0;
+ return JSONSuccess;
+}
+
+JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value) {
+ JSON_Value *temp_schema_value = NULL, *temp_value = NULL;
+ JSON_Array *schema_array = NULL, *value_array = NULL;
+ JSON_Object *schema_object = NULL, *value_object = NULL;
+ JSON_Value_Type schema_type = JSONError, value_type = JSONError;
+ const char *key = NULL;
+ size_t i = 0, count = 0;
+ if (schema == NULL || value == NULL) {
+ return JSONFailure;
+ }
+ schema_type = json_value_get_type(schema);
+ value_type = json_value_get_type(value);
+ if (schema_type != value_type && schema_type != JSONNull) { /* null represents all values */
+ return JSONFailure;
+ }
+ switch (schema_type) {
+ case JSONArray:
+ schema_array = json_value_get_array(schema);
+ value_array = json_value_get_array(value);
+ count = json_array_get_count(schema_array);
+ if (count == 0) {
+ return JSONSuccess; /* Empty array allows all types */
+ }
+ /* Get first value from array, rest is ignored */
+ temp_schema_value = json_array_get_value(schema_array, 0);
+ for (i = 0; i < json_array_get_count(value_array); i++) {
+ temp_value = json_array_get_value(value_array, i);
+ if (json_validate(temp_schema_value, temp_value) == JSONFailure) {
+ return JSONFailure;
+ }
+ }
+ return JSONSuccess;
+ case JSONObject:
+ schema_object = json_value_get_object(schema);
+ value_object = json_value_get_object(value);
+ count = json_object_get_count(schema_object);
+ if (count == 0) {
+ return JSONSuccess; /* Empty object allows all objects */
+ } else if (json_object_get_count(value_object) < count) {
+ return JSONFailure; /* Tested object mustn't have less name-value pairs than schema */
+ }
+ for (i = 0; i < count; i++) {
+ key = json_object_get_name(schema_object, i);
+ temp_schema_value = json_object_get_value(schema_object, key);
+ temp_value = json_object_get_value(value_object, key);
+ if (temp_value == NULL) {
+ return JSONFailure;
+ }
+ if (json_validate(temp_schema_value, temp_value) == JSONFailure) {
+ return JSONFailure;
+ }
+ }
+ return JSONSuccess;
+ case JSONString: case JSONNumber: case JSONBoolean: case JSONNull:
+ return JSONSuccess; /* equality already tested before switch */
+ case JSONError: default:
+ return JSONFailure;
+ }
+}
+
+int json_value_equals(const JSON_Value *a, const JSON_Value *b) {
+ JSON_Object *a_object = NULL, *b_object = NULL;
+ JSON_Array *a_array = NULL, *b_array = NULL;
+ const char *a_string = NULL, *b_string = NULL;
+ const char *key = NULL;
+ size_t a_count = 0, b_count = 0, i = 0;
+ JSON_Value_Type a_type, b_type;
+ a_type = json_value_get_type(a);
+ b_type = json_value_get_type(b);
+ if (a_type != b_type) {
+ return 0;
+ }
+ switch (a_type) {
+ case JSONArray:
+ a_array = json_value_get_array(a);
+ b_array = json_value_get_array(b);
+ a_count = json_array_get_count(a_array);
+ b_count = json_array_get_count(b_array);
+ if (a_count != b_count) {
+ return 0;
+ }
+ for (i = 0; i < a_count; i++) {
+ if (!json_value_equals(json_array_get_value(a_array, i),
+ json_array_get_value(b_array, i))) {
+ return 0;
+ }
+ }
+ return 1;
+ case JSONObject:
+ a_object = json_value_get_object(a);
+ b_object = json_value_get_object(b);
+ a_count = json_object_get_count(a_object);
+ b_count = json_object_get_count(b_object);
+ if (a_count != b_count) {
+ return 0;
+ }
+ for (i = 0; i < a_count; i++) {
+ key = json_object_get_name(a_object, i);
+ if (!json_value_equals(json_object_get_value(a_object, key),
+ json_object_get_value(b_object, key))) {
+ return 0;
+ }
+ }
+ return 1;
+ case JSONString:
+ a_string = json_value_get_string(a);
+ b_string = json_value_get_string(b);
+ if (a_string == NULL || b_string == NULL) {
+ return 0; /* shouldn't happen */
+ }
+ return strcmp(a_string, b_string) == 0;
+ case JSONBoolean:
+ return json_value_get_boolean(a) == json_value_get_boolean(b);
+ case JSONNumber:
+ return fabs(json_value_get_number(a) - json_value_get_number(b)) < 0.000001; /* EPSILON */
+ case JSONError:
+ return 1;
+ case JSONNull:
+ return 1;
+ default:
+ return 1;
+ }
+}
+
+JSON_Value_Type json_type(const JSON_Value *value) {
+ return json_value_get_type(value);
+}
+
+JSON_Object * json_object (const JSON_Value *value) {
+ return json_value_get_object(value);
+}
+
+JSON_Array * json_array (const JSON_Value *value) {
+ return json_value_get_array(value);
+}
+
+const char * json_string (const JSON_Value *value) {
+ return json_value_get_string(value);
+}
+
+double json_number (const JSON_Value *value) {
+ return json_value_get_number(value);
+}
+
+int json_boolean(const JSON_Value *value) {
+ return json_value_get_boolean(value);
+}
+
+void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun) {
+ parson_malloc = malloc_fun;
+ parson_free = free_fun;
+}
+
+void json_set_escape_slashes(int escape_slashes) {
+ parson_escape_slashes = escape_slashes;
+}
diff --git a/src/cooker/parson.h b/src/cooker/parson.h
new file mode 100644
index 0000000..186fcb0
--- /dev/null
+++ b/src/cooker/parson.h
@@ -0,0 +1,240 @@
+/*
+ SPDX-License-Identifier: MIT
+
+ Parson ( http://kgabis.github.com/parson/ )
+ Copyright (c) 2012 - 2019 Krzysztof Gabis
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+#ifndef parson_parson_h
+#define parson_parson_h
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <stddef.h> /* size_t */
+
+/* Types and enums */
+typedef struct json_object_t JSON_Object;
+typedef struct json_array_t JSON_Array;
+typedef struct json_value_t JSON_Value;
+
+enum json_value_type {
+ JSONError = -1,
+ JSONNull = 1,
+ JSONString = 2,
+ JSONNumber = 3,
+ JSONObject = 4,
+ JSONArray = 5,
+ JSONBoolean = 6
+};
+typedef int JSON_Value_Type;
+
+enum json_result_t {
+ JSONSuccess = 0,
+ JSONFailure = -1
+};
+typedef int JSON_Status;
+
+typedef void * (*JSON_Malloc_Function)(size_t);
+typedef void (*JSON_Free_Function)(void *);
+
+/* Call only once, before calling any other function from parson API. If not called, malloc and free
+ from stdlib will be used for all allocations */
+void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun);
+
+/* Sets if slashes should be escaped or not when serializing JSON. By default slashes are escaped.
+ This function sets a global setting and is not thread safe. */
+void json_set_escape_slashes(int escape_slashes);
+
+/* Parses first JSON value in a file, returns NULL in case of error */
+JSON_Value * json_parse_file(const char *filename);
+
+/* Parses first JSON value in a file and ignores comments (/ * * / and //),
+ returns NULL in case of error */
+JSON_Value * json_parse_file_with_comments(const char *filename);
+
+/* Parses first JSON value in a string, returns NULL in case of error */
+JSON_Value * json_parse_string(const char *string);
+
+/* Parses first JSON value in a string and ignores comments (/ * * / and //),
+ returns NULL in case of error */
+JSON_Value * json_parse_string_with_comments(const char *string);
+
+/* Serialization */
+size_t json_serialization_size(const JSON_Value *value); /* returns 0 on fail */
+JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes);
+JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename);
+char * json_serialize_to_string(const JSON_Value *value);
+
+/* Pretty serialization */
+size_t json_serialization_size_pretty(const JSON_Value *value); /* returns 0 on fail */
+JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes);
+JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename);
+char * json_serialize_to_string_pretty(const JSON_Value *value);
+
+void json_free_serialized_string(char *string); /* frees string from json_serialize_to_string and json_serialize_to_string_pretty */
+
+/* Comparing */
+int json_value_equals(const JSON_Value *a, const JSON_Value *b);
+
+/* Validation
+ This is *NOT* JSON Schema. It validates json by checking if object have identically
+ named fields with matching types.
+ For example schema {"name":"", "age":0} will validate
+ {"name":"Joe", "age":25} and {"name":"Joe", "age":25, "gender":"m"},
+ but not {"name":"Joe"} or {"name":"Joe", "age":"Cucumber"}.
+ In case of arrays, only first value in schema is checked against all values in tested array.
+ Empty objects ({}) validate all objects, empty arrays ([]) validate all arrays,
+ null validates values of every type.
+ */
+JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value);
+
+/*
+ * JSON Object
+ */
+JSON_Value * json_object_get_value (const JSON_Object *object, const char *name);
+const char * json_object_get_string (const JSON_Object *object, const char *name);
+JSON_Object * json_object_get_object (const JSON_Object *object, const char *name);
+JSON_Array * json_object_get_array (const JSON_Object *object, const char *name);
+double json_object_get_number (const JSON_Object *object, const char *name); /* returns 0 on fail */
+int json_object_get_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */
+
+/* dotget functions enable addressing values with dot notation in nested objects,
+ just like in structs or c++/java/c# objects (e.g. objectA.objectB.value).
+ Because valid names in JSON can contain dots, some values may be inaccessible
+ this way. */
+JSON_Value * json_object_dotget_value (const JSON_Object *object, const char *name);
+const char * json_object_dotget_string (const JSON_Object *object, const char *name);
+JSON_Object * json_object_dotget_object (const JSON_Object *object, const char *name);
+JSON_Array * json_object_dotget_array (const JSON_Object *object, const char *name);
+double json_object_dotget_number (const JSON_Object *object, const char *name); /* returns 0 on fail */
+int json_object_dotget_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */
+
+/* Functions to get available names */
+size_t json_object_get_count (const JSON_Object *object);
+const char * json_object_get_name (const JSON_Object *object, size_t index);
+JSON_Value * json_object_get_value_at(const JSON_Object *object, size_t index);
+JSON_Value * json_object_get_wrapping_value(const JSON_Object *object);
+
+/* Functions to check if object has a value with a specific name. Returned value is 1 if object has
+ * a value and 0 if it doesn't. dothas functions behave exactly like dotget functions. */
+int json_object_has_value (const JSON_Object *object, const char *name);
+int json_object_has_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type);
+
+int json_object_dothas_value (const JSON_Object *object, const char *name);
+int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type);
+
+/* Creates new name-value pair or frees and replaces old value with a new one.
+ * json_object_set_value does not copy passed value so it shouldn't be freed afterwards. */
+JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value);
+JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string);
+JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number);
+JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean);
+JSON_Status json_object_set_null(JSON_Object *object, const char *name);
+
+/* Works like dotget functions, but creates whole hierarchy if necessary.
+ * json_object_dotset_value does not copy passed value so it shouldn't be freed afterwards. */
+JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value);
+JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string);
+JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number);
+JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean);
+JSON_Status json_object_dotset_null(JSON_Object *object, const char *name);
+
+/* Frees and removes name-value pair */
+JSON_Status json_object_remove(JSON_Object *object, const char *name);
+
+/* Works like dotget function, but removes name-value pair only on exact match. */
+JSON_Status json_object_dotremove(JSON_Object *object, const char *key);
+
+/* Removes all name-value pairs in object */
+JSON_Status json_object_clear(JSON_Object *object);
+
+/*
+ *JSON Array
+ */
+JSON_Value * json_array_get_value (const JSON_Array *array, size_t index);
+const char * json_array_get_string (const JSON_Array *array, size_t index);
+JSON_Object * json_array_get_object (const JSON_Array *array, size_t index);
+JSON_Array * json_array_get_array (const JSON_Array *array, size_t index);
+double json_array_get_number (const JSON_Array *array, size_t index); /* returns 0 on fail */
+int json_array_get_boolean(const JSON_Array *array, size_t index); /* returns -1 on fail */
+size_t json_array_get_count (const JSON_Array *array);
+JSON_Value * json_array_get_wrapping_value(const JSON_Array *array);
+
+/* Frees and removes value at given index, does nothing and returns JSONFailure if index doesn't exist.
+ * Order of values in array may change during execution. */
+JSON_Status json_array_remove(JSON_Array *array, size_t i);
+
+/* Frees and removes from array value at given index and replaces it with given one.
+ * Does nothing and returns JSONFailure if index doesn't exist.
+ * json_array_replace_value does not copy passed value so it shouldn't be freed afterwards. */
+JSON_Status json_array_replace_value(JSON_Array *array, size_t i, JSON_Value *value);
+JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string);
+JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number);
+JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean);
+JSON_Status json_array_replace_null(JSON_Array *array, size_t i);
+
+/* Frees and removes all values from array */
+JSON_Status json_array_clear(JSON_Array *array);
+
+/* Appends new value at the end of array.
+ * json_array_append_value does not copy passed value so it shouldn't be freed afterwards. */
+JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value);
+JSON_Status json_array_append_string(JSON_Array *array, const char *string);
+JSON_Status json_array_append_number(JSON_Array *array, double number);
+JSON_Status json_array_append_boolean(JSON_Array *array, int boolean);
+JSON_Status json_array_append_null(JSON_Array *array);
+
+/*
+ *JSON Value
+ */
+JSON_Value * json_value_init_object (void);
+JSON_Value * json_value_init_array (void);
+JSON_Value * json_value_init_string (const char *string); /* copies passed string */
+JSON_Value * json_value_init_number (double number);
+JSON_Value * json_value_init_boolean(int boolean);
+JSON_Value * json_value_init_null (void);
+JSON_Value * json_value_deep_copy (const JSON_Value *value);
+void json_value_free (JSON_Value *value);
+
+JSON_Value_Type json_value_get_type (const JSON_Value *value);
+JSON_Object * json_value_get_object (const JSON_Value *value);
+JSON_Array * json_value_get_array (const JSON_Value *value);
+const char * json_value_get_string (const JSON_Value *value);
+double json_value_get_number (const JSON_Value *value);
+int json_value_get_boolean(const JSON_Value *value);
+JSON_Value * json_value_get_parent (const JSON_Value *value);
+
+/* Same as above, but shorter */
+JSON_Value_Type json_type (const JSON_Value *value);
+JSON_Object * json_object (const JSON_Value *value);
+JSON_Array * json_array (const JSON_Value *value);
+const char * json_string (const JSON_Value *value);
+double json_number (const JSON_Value *value);
+int json_boolean(const JSON_Value *value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/cooker/util.c b/src/cooker/util.c
new file mode 100644
index 0000000..a2ecce0
--- /dev/null
+++ b/src/cooker/util.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* seitan - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * cooker/util.c - Convenience routines
+ *
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#define logfn(name) \
+void name(const char *format, ...) { \
+ va_list args; \
+ \
+ va_start(args, format); \
+ (void)vfprintf(stderr, format, args); \
+ va_end(args); \
+ if (format[strlen(format)] != '\n') \
+ fprintf(stderr, "\n"); \
+}
+
+logfn(err)
+logfn(info)
+logfn(debug)
+
diff --git a/src/cooker/util.h b/src/cooker/util.h
new file mode 100644
index 0000000..84dc3db
--- /dev/null
+++ b/src/cooker/util.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#ifndef UTIL_H
+#define UTIL_H
+
+#define BIT(n) (1UL << (n))
+
+void err(const char *format, ...);
+void info(const char *format, ...);
+void debug(const char *format, ...);
+
+#define die(...) \
+ do { \
+ fprintf(stderr, "%s:%i: ", __FILE__, __LINE__); \
+ err(__VA_ARGS__); \
+ exit(EXIT_FAILURE); \
+ } while (0)
+
+#endif /* UTIL_H */
diff --git a/src/debug/Makefile b/src/debug/Makefile
new file mode 100644
index 0000000..6ef7900
--- /dev/null
+++ b/src/debug/Makefile
@@ -0,0 +1,37 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# seitan - Syscall Expressive Interpreter, Transformer and Notifier
+#
+# debug/Makefile - Makefile for debug utilities: bpf_dbg
+#
+# Copyright 2023 Red Hat GmbH
+# Author: Alice Frosi <afrosi@redhat.com>
+
+SRCS := bpf_dbg.c disasm.c
+HEADERS := disasm.h
+BIN := $(OUTDIR)bpf_dbg
+CFLAGS += -Wall -Wextra -pedantic
+
+# TODO: remove this part together with the build binary
+# when cooker is ready
+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/')
+
+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/')
+
+bpf_dbg: $(SRCS) $(HEADERS)
+ $(CC) $(CFLAGS) -o $(BIN) $(SRCS)
+
+# TODO: remove when cooker is ready
+build: build.c ../cooker/filter.c ../cooker/filter.h ../common/numbers.h
+ $(CC) $(CFLAGS) -I../common -I../cooker -DSEITAN_AUDIT_ARCH=AUDIT_ARCH_$(AUDIT_ARCH)\
+ -o $(OUTDIR)build ../cooker/filter.c build.c
+
+all: $(BIN)
+
+clean:
+ rm -f $(BIN) build
diff --git a/src/debug/bpf_dbg.c b/src/debug/bpf_dbg.c
new file mode 100644
index 0000000..b84c713
--- /dev/null
+++ b/src/debug/bpf_dbg.c
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Alice Frosi <afrosi@redhat.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <linux/filter.h>
+#include <unistd.h>
+
+#include "disasm.h"
+
+int main(int argc, char **argv)
+{
+ struct sock_filter *filter;
+ size_t fd, n;
+
+ if (argc < 2) {
+ perror("missing input file");
+ exit(EXIT_FAILURE);
+ }
+ filter = calloc(SIZE_FILTER, sizeof(struct sock_filter));
+ fd = open(argv[1], O_CLOEXEC | O_RDONLY);
+
+ n = read(fd, filter, sizeof(struct sock_filter) * SIZE_FILTER);
+ close(fd);
+ printf("Read %ld entries\n", n / sizeof(struct sock_filter));
+ bpf_disasm_all(filter, n / sizeof(struct sock_filter));
+ free(filter);
+ return 0;
+}
diff --git a/src/debug/build.c b/src/debug/build.c
new file mode 100644
index 0000000..93ce97b
--- /dev/null
+++ b/src/debug/build.c
@@ -0,0 +1,33 @@
+#define _GNU_SOURCE
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "filter.h"
+
+struct bpf_call calls[] = {
+ {
+ .name = "connect",
+ .args = { 0, 111, 0, 0, 0, 0 },
+ .check_arg = { false, false, false, false, false, false },
+ },
+};
+
+int main(int argc, char **argv)
+{
+ int ret;
+ if (argc < 2) {
+ perror("missing input file");
+ exit(EXIT_FAILURE);
+ }
+ ret = convert_bpf(argv[1], calls, sizeof(calls) / sizeof(calls[0]),
+ true);
+ if (ret < 0) {
+ perror("converting bpf program");
+ exit(EXIT_FAILURE);
+ }
+ return 0;
+}
diff --git a/src/debug/disasm.c b/src/debug/disasm.c
new file mode 100644
index 0000000..fae96b7
--- /dev/null
+++ b/src/debug/disasm.c
@@ -0,0 +1,281 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Alice Frosi <afrosi@redhat.com>
+ */
+
+#define _GNU_SOURCE
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <linux/filter.h>
+
+#include "disasm.h"
+
+/* From linux tools/bpf/bpf_dbg.c */
+#define BPF_LDX_B (BPF_LDX | BPF_B)
+#define BPF_LDX_W (BPF_LDX | BPF_W)
+#define BPF_JMP_JA (BPF_JMP | BPF_JA)
+#define BPF_JMP_JEQ (BPF_JMP | BPF_JEQ)
+#define BPF_JMP_JGT (BPF_JMP | BPF_JGT)
+#define BPF_JMP_JGE (BPF_JMP | BPF_JGE)
+#define BPF_JMP_JSET (BPF_JMP | BPF_JSET)
+#define BPF_ALU_ADD (BPF_ALU | BPF_ADD)
+#define BPF_ALU_SUB (BPF_ALU | BPF_SUB)
+#define BPF_ALU_MUL (BPF_ALU | BPF_MUL)
+#define BPF_ALU_DIV (BPF_ALU | BPF_DIV)
+#define BPF_ALU_MOD (BPF_ALU | BPF_MOD)
+#define BPF_ALU_NEG (BPF_ALU | BPF_NEG)
+#define BPF_ALU_AND (BPF_ALU | BPF_AND)
+#define BPF_ALU_OR (BPF_ALU | BPF_OR)
+#define BPF_ALU_XOR (BPF_ALU | BPF_XOR)
+#define BPF_ALU_LSH (BPF_ALU | BPF_LSH)
+#define BPF_ALU_RSH (BPF_ALU | BPF_RSH)
+#define BPF_MISC_TAX (BPF_MISC | BPF_TAX)
+#define BPF_MISC_TXA (BPF_MISC | BPF_TXA)
+#define BPF_LD_B (BPF_LD | BPF_B)
+#define BPF_LD_H (BPF_LD | BPF_H)
+#define BPF_LD_W (BPF_LD | BPF_W)
+static const char *const op_table[] = {
+ [BPF_ST] = "st", [BPF_STX] = "stx", [BPF_LD_B] = "ldb",
+ [BPF_LD_H] = "ldh", [BPF_LD_W] = "ld", [BPF_LDX] = "ldx",
+ [BPF_LDX_B] = "ldxb", [BPF_JMP_JA] = "ja", [BPF_JMP_JEQ] = "jeq",
+ [BPF_JMP_JGT] = "jgt", [BPF_JMP_JGE] = "jge", [BPF_JMP_JSET] = "jset",
+ [BPF_ALU_ADD] = "add", [BPF_ALU_SUB] = "sub", [BPF_ALU_MUL] = "mul",
+ [BPF_ALU_DIV] = "div", [BPF_ALU_MOD] = "mod", [BPF_ALU_NEG] = "neg",
+ [BPF_ALU_AND] = "and", [BPF_ALU_OR] = "or", [BPF_ALU_XOR] = "xor",
+ [BPF_ALU_LSH] = "lsh", [BPF_ALU_RSH] = "rsh", [BPF_MISC_TAX] = "tax",
+ [BPF_MISC_TXA] = "txa", [BPF_RET] = "ret",
+};
+
+void bpf_disasm(const struct sock_filter f, unsigned int i)
+{
+ const char *op, *fmt;
+ int val = f.k;
+ char buf[256];
+
+ switch (f.code) {
+ case BPF_RET | BPF_K:
+ op = op_table[BPF_RET];
+ fmt = "#%#x";
+ break;
+ case BPF_RET | BPF_A:
+ op = op_table[BPF_RET];
+ fmt = "a";
+ break;
+ case BPF_RET | BPF_X:
+ op = op_table[BPF_RET];
+ fmt = "x";
+ break;
+ case BPF_MISC_TAX:
+ op = op_table[BPF_MISC_TAX];
+ fmt = "";
+ break;
+ case BPF_MISC_TXA:
+ op = op_table[BPF_MISC_TXA];
+ fmt = "";
+ break;
+ case BPF_ST:
+ op = op_table[BPF_ST];
+ fmt = "M[%d]";
+ break;
+ case BPF_STX:
+ op = op_table[BPF_STX];
+ fmt = "M[%d]";
+ break;
+ case BPF_LD_W | BPF_ABS:
+ op = op_table[BPF_LD_W];
+ fmt = "[%d]";
+ break;
+ case BPF_LD_H | BPF_ABS:
+ op = op_table[BPF_LD_H];
+ fmt = "[%d]";
+ break;
+ case BPF_LD_B | BPF_ABS:
+ op = op_table[BPF_LD_B];
+ fmt = "[%d]";
+ break;
+ case BPF_LD_W | BPF_LEN:
+ op = op_table[BPF_LD_W];
+ fmt = "#len";
+ break;
+ case BPF_LD_W | BPF_IND:
+ op = op_table[BPF_LD_W];
+ fmt = "[x+%d]";
+ break;
+ case BPF_LD_H | BPF_IND:
+ op = op_table[BPF_LD_H];
+ fmt = "[x+%d]";
+ break;
+ case BPF_LD_B | BPF_IND:
+ op = op_table[BPF_LD_B];
+ fmt = "[x+%d]";
+ break;
+ case BPF_LD | BPF_IMM:
+ op = op_table[BPF_LD_W];
+ fmt = "#%#x";
+ break;
+ case BPF_LDX | BPF_IMM:
+ op = op_table[BPF_LDX];
+ fmt = "#%#x";
+ break;
+ case BPF_LDX_B | BPF_MSH:
+ op = op_table[BPF_LDX_B];
+ fmt = "4*([%d]&0xf)";
+ break;
+ case BPF_LD | BPF_MEM:
+ op = op_table[BPF_LD_W];
+ fmt = "M[%d]";
+ break;
+ case BPF_LDX | BPF_MEM:
+ op = op_table[BPF_LDX];
+ fmt = "M[%d]";
+ break;
+ case BPF_JMP_JA:
+ op = op_table[BPF_JMP_JA];
+ fmt = "%d";
+ val = i + 1 + f.k;
+ break;
+ case BPF_JMP_JGT | BPF_X:
+ op = op_table[BPF_JMP_JGT];
+ fmt = "x";
+ break;
+ case BPF_JMP_JGT | BPF_K:
+ op = op_table[BPF_JMP_JGT];
+ fmt = "#%#x";
+ break;
+ case BPF_JMP_JGE | BPF_X:
+ op = op_table[BPF_JMP_JGE];
+ fmt = "x";
+ break;
+ case BPF_JMP_JGE | BPF_K:
+ op = op_table[BPF_JMP_JGE];
+ fmt = "#%#x";
+ break;
+ case BPF_JMP_JEQ | BPF_X:
+ op = op_table[BPF_JMP_JEQ];
+ fmt = "x";
+ break;
+ case BPF_JMP_JEQ | BPF_K:
+ op = op_table[BPF_JMP_JEQ];
+ fmt = "#%#x";
+ break;
+ case BPF_JMP_JSET | BPF_X:
+ op = op_table[BPF_JMP_JSET];
+ fmt = "x";
+ break;
+ case BPF_JMP_JSET | BPF_K:
+ op = op_table[BPF_JMP_JSET];
+ fmt = "#%#x";
+ break;
+ case BPF_ALU_NEG:
+ op = op_table[BPF_ALU_NEG];
+ fmt = "";
+ break;
+ case BPF_ALU_LSH | BPF_X:
+ op = op_table[BPF_ALU_LSH];
+ fmt = "x";
+ break;
+ case BPF_ALU_LSH | BPF_K:
+ op = op_table[BPF_ALU_LSH];
+ fmt = "#%d";
+ break;
+ case BPF_ALU_RSH | BPF_X:
+ op = op_table[BPF_ALU_RSH];
+ fmt = "x";
+ break;
+ case BPF_ALU_RSH | BPF_K:
+ op = op_table[BPF_ALU_RSH];
+ fmt = "#%d";
+ break;
+ case BPF_ALU_ADD | BPF_X:
+ op = op_table[BPF_ALU_ADD];
+ fmt = "x";
+ break;
+ case BPF_ALU_ADD | BPF_K:
+ op = op_table[BPF_ALU_ADD];
+ fmt = "#%d";
+ break;
+ case BPF_ALU_SUB | BPF_X:
+ op = op_table[BPF_ALU_SUB];
+ fmt = "x";
+ break;
+ case BPF_ALU_SUB | BPF_K:
+ op = op_table[BPF_ALU_SUB];
+ fmt = "#%d";
+ break;
+ case BPF_ALU_MUL | BPF_X:
+ op = op_table[BPF_ALU_MUL];
+ fmt = "x";
+ break;
+ case BPF_ALU_MUL | BPF_K:
+ op = op_table[BPF_ALU_MUL];
+ fmt = "#%d";
+ break;
+ case BPF_ALU_DIV | BPF_X:
+ op = op_table[BPF_ALU_DIV];
+ fmt = "x";
+ break;
+ case BPF_ALU_DIV | BPF_K:
+ op = op_table[BPF_ALU_DIV];
+ fmt = "#%d";
+ break;
+ case BPF_ALU_MOD | BPF_X:
+ op = op_table[BPF_ALU_MOD];
+ fmt = "x";
+ break;
+ case BPF_ALU_MOD | BPF_K:
+ op = op_table[BPF_ALU_MOD];
+ fmt = "#%d";
+ break;
+ case BPF_ALU_AND | BPF_X:
+ op = op_table[BPF_ALU_AND];
+ fmt = "x";
+ break;
+ case BPF_ALU_AND | BPF_K:
+ op = op_table[BPF_ALU_AND];
+ fmt = "#%#x";
+ break;
+ case BPF_ALU_OR | BPF_X:
+ op = op_table[BPF_ALU_OR];
+ fmt = "x";
+ break;
+ case BPF_ALU_OR | BPF_K:
+ op = op_table[BPF_ALU_OR];
+ fmt = "#%#x";
+ break;
+ case BPF_ALU_XOR | BPF_X:
+ op = op_table[BPF_ALU_XOR];
+ fmt = "x";
+ break;
+ case BPF_ALU_XOR | BPF_K:
+ op = op_table[BPF_ALU_XOR];
+ fmt = "#%#x";
+ break;
+ default:
+ op = "nosup";
+ fmt = "%#x";
+ val = f.code;
+ break;
+ }
+
+ memset(buf, 0, sizeof(buf));
+ snprintf(buf, sizeof(buf), fmt, val);
+ buf[sizeof(buf) - 1] = 0;
+
+ if ((BPF_CLASS(f.code) == BPF_JMP && BPF_OP(f.code) != BPF_JA)) {
+ printf("l%d:\t%s %s, l%d, l%d\n", i, op, buf, i + 1 + f.jt,
+ i + 1 + f.jf);
+ } else {
+ printf("l%d:\t%s %s\n", i, op, buf);
+ }
+}
+
+void bpf_disasm_all(const struct sock_filter *f, unsigned int len)
+{
+ unsigned int i;
+ for (i = 0; i < len; i++)
+ bpf_disasm(f[i], i);
+}
diff --git a/src/debug/disasm.h b/src/debug/disasm.h
new file mode 100644
index 0000000..c3e3ad9
--- /dev/null
+++ b/src/debug/disasm.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Alice Frosi <afrosi@redhat.com>
+ */
+
+#ifndef DISASM_H_
+#define DISASM_H_
+
+#define SIZE_FILTER 1024
+
+void bpf_disasm(const struct sock_filter f, unsigned int i);
+void bpf_disasm_all(const struct sock_filter *f, unsigned int len);
+
+#endif
diff --git a/src/eater/Makefile b/src/eater/Makefile
new file mode 100644
index 0000000..c70433f
--- /dev/null
+++ b/src/eater/Makefile
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# seitan - Syscall Expressive Interpreter, Transformer and Notifier
+#
+# eater/Makefile - Makefile for seitan-eater
+#
+# Copyright 2023 Red Hat GmbH
+# Author: Alice Frosi <afrosi@redhat.com>
+
+COMMON_DIR :=../common
+SRCS := $(COMMON_DIR)/common.c eater.c
+HEADERS := $(COMMON_DIR)/common.h
+BIN := $(OUTDIR)eater
+CFLAGS += -Wall -Wextra -pedantic -I$(COMMON_DIR)
+
+eater: $(SRCS) $(HEADERS)
+ $(CC) $(CFLAGS) -o $(BIN) $(SRCS)
+
+all: eater
+
+clean:
+ rm -f $(BIN)
diff --git a/src/eater/eater.c b/src/eater/eater.c
new file mode 100644
index 0000000..96a7b61
--- /dev/null
+++ b/src/eater/eater.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+/* SEITAN - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * src/eater/eater.c - Load BPF program and execute binary
+ *
+ * Copyright (c) 2022 Red Hat GmbH
+ * Authors: Stefano Brivio <sbrivio@redhat.com>, Alice Frosi <afrosi@redhat.com>
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <argp.h>
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+#include <sys/socket.h>
+#include <signal.h>
+
+#include <linux/audit.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include "common.h"
+
+extern char **environ;
+
+static char doc[] =
+ "Usage: seitan-eater: setain-eater -i <input file> -- program args1 args2...";
+
+/* Eater options */
+static struct argp_option options[] = { { "input", 'i', "FILE", 0,
+ "BPF filter input file", 0 },
+ { 0 } };
+
+struct arguments {
+ char *input_file;
+ unsigned int program_index;
+};
+
+static error_t parse_opt(int key, char *arg, struct argp_state *state)
+{
+ struct arguments *arguments = state->input;
+
+ if (state->quoted == 0)
+ arguments->program_index = state->next + 1;
+ switch (key) {
+ case 'i':
+ if (state->quoted == 0)
+ arguments->input_file = arg;
+ break;
+ case ARGP_KEY_END:
+ if (arguments->input_file == NULL)
+ argp_error(state, "missing input file");
+ if (state->argv[arguments->program_index] == NULL)
+ argp_error(state, "missing program");
+ break;
+ }
+
+ return 0;
+}
+
+static struct argp argp = { .options = options,
+ .parser = parse_opt,
+ .args_doc = NULL,
+ .doc = doc,
+ .children = NULL,
+ .help_filter = NULL,
+ .argp_domain = NULL };
+
+static int seccomp(unsigned int operation, unsigned int flags, void *args)
+{
+ return syscall(__NR_seccomp, operation, flags, args);
+}
+
+static void signal_handler(__attribute__((unused)) int s)
+{
+}
+
+/**
+ * main() - Entry point
+ * @argc: Argument count
+ * @argv: Seitan-eater and program arguments
+ *
+ * Return: 0 once interrupted, non-zero on failure
+ */
+int main(int argc, char **argv)
+{
+ struct sock_filter filter[1024];
+ struct arguments arguments;
+ struct sock_fprog prog;
+ struct sigaction act;
+ size_t n;
+ int fd, flags;
+
+ argp_parse(&argp, argc, argv, 0, 0, &arguments);
+ fd = open(arguments.input_file, O_CLOEXEC | O_RDONLY);
+ n = read(fd, filter, sizeof(filter));
+ close(fd);
+
+ prog.filter = filter;
+ prog.len = (unsigned short)(n / sizeof(filter[0]));
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) {
+ perror("prctl");
+ exit(EXIT_FAILURE);
+ }
+ if (seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER,
+ &prog) < 0) {
+ perror("seccomp");
+ exit(EXIT_FAILURE);
+ }
+ /*
+ * close-on-exec flag is set for the file descriptor by seccomp.
+ * We want to preserve the fd on the exec in this way we are able
+ * to easly find the notifier fd if seitan restarts.
+ */
+ fd = find_fd_seccomp_notifier("/proc/self/fd");
+ flags = fcntl(fd, F_GETFD);
+ if (fcntl(fd, F_SETFD, flags & !FD_CLOEXEC) < 0) {
+ perror("fcntl");
+ exit(EXIT_FAILURE);
+ }
+ act.sa_handler = signal_handler;
+ sigaction(SIGCONT, &act, NULL);
+ pause();
+
+ execvpe(argv[arguments.program_index], &argv[arguments.program_index],
+ environ);
+ if (errno != ENOENT) {
+ perror("execvpe");
+ exit(EXIT_FAILURE);
+ }
+ close(fd);
+ return EXIT_FAILURE;
+}
diff --git a/src/seitan/Makefile b/src/seitan/Makefile
new file mode 100644
index 0000000..8a0c106
--- /dev/null
+++ b/src/seitan/Makefile
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# seitan - Syscall Expressive Interpreter, Transformer and Notifier
+#
+# seitan/Makefile - Makefile for seitan
+#
+# Copyright 2023 Red Hat GmbH
+# Author: Alice Frosi <afrosi@redhat.com>
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+COMMON_DIR :=../common
+SRCS := seitan.c $(COMMON_DIR)/common.c operations.c
+HEADERS := $(COMMON_DIR)/common.h $(COMMON_DIR)/gluten.h operations.h
+BIN := $(OUTDIR)seitan
+
+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/')
+
+CFLAGS += -DTMP_DATA_SIZE=1000
+CFLAGS += -Wall -Wextra -pedantic -I$(COMMON_DIR)
+
+seitan: $(SRCS) $(HEADERS)
+ $(CC) $(CFLAGS) -o $(BIN) $(SRCS)
+
+all: seitan
+
+clean:
+ rm -f $(BIN)
diff --git a/src/seitan/operations.c b/src/seitan/operations.c
new file mode 100644
index 0000000..0327e57
--- /dev/null
+++ b/src/seitan/operations.c
@@ -0,0 +1,361 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Alice Frosi <afrosi@redhat.com>
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sched.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <linux/seccomp.h>
+#include <linux/filter.h>
+#include <linux/audit.h>
+#include <errno.h>
+
+#include "gluten.h"
+#include "operations.h"
+
+static bool is_cookie_valid(int notifyFd, uint64_t id)
+{
+ return ioctl(notifyFd, SECCOMP_IOCTL_NOTIF_ID_VALID, &id) == 0;
+}
+
+static int send_target(const struct seccomp_notif_resp *resp, int notifyfd)
+{
+ if (!is_cookie_valid(notifyfd, resp->id)) {
+ fprintf(stderr,
+ "the response id isn't valid\ncheck if the targets has already terminated\n");
+ return -1;
+ }
+ if (ioctl(notifyfd, SECCOMP_IOCTL_NOTIF_SEND, resp) < 0) {
+ if (errno != EINPROGRESS) {
+ perror("sending the response");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int send_inject_target(const struct seccomp_notif_addfd *resp,
+ int notifyfd)
+{
+ if (!is_cookie_valid(notifyfd, resp->id)) {
+ fprintf(stderr,
+ "the response id isn't valid\ncheck if the targets has already terminated\n");
+ return -1;
+ }
+ if (ioctl(notifyfd, SECCOMP_IOCTL_NOTIF_ADDFD, resp) < 0) {
+ if (errno != EINPROGRESS) {
+ perror("sending the response");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void proc_ns_name(unsigned i, char *ns)
+{
+ switch (i) {
+ case NS_CGROUP:
+ snprintf(ns, PATH_MAX + 1, "cgroup");
+ break;
+ case NS_IPC:
+ snprintf(ns, PATH_MAX + 1, "ipc");
+ break;
+ case NS_NET:
+ snprintf(ns, PATH_MAX + 1, "net");
+ break;
+ case NS_MOUNT:
+ snprintf(ns, PATH_MAX + 1, "mnt");
+ break;
+ case NS_PID:
+ snprintf(ns, PATH_MAX + 1, "pid");
+ break;
+ case NS_USER:
+ snprintf(ns, PATH_MAX + 1, "user");
+ break;
+ case NS_UTS:
+ snprintf(ns, PATH_MAX + 1, "uts");
+ break;
+ case NS_TIME:
+ snprintf(ns, PATH_MAX + 1, "time");
+ break;
+ default:
+ fprintf(stderr, "unrecognized namespace index %d\n", i);
+ }
+}
+
+static int set_namespaces(const struct op_call *a, int tpid)
+{
+ char path[PATH_MAX + 1];
+ char ns_name[PATH_MAX / 2];
+ struct ns_spec ns;
+ int fd;
+ unsigned int i;
+
+ for (i = 0, ns = (a->context).ns[i]; i < sizeof(enum ns_type);
+ i++, ns = (a->context).ns[i]) {
+ proc_ns_name(i, ns_name);
+ switch (ns.type) {
+ case NS_NONE:
+ continue;
+ case NS_SPEC_TARGET:
+ snprintf(path, sizeof(path), "/proc/%d/ns/%s", tpid,
+ ns_name);
+ break;
+ case NS_SPEC_PID:
+ snprintf(path, sizeof(path), "/proc/%d/ns/%s", ns.pid,
+ ns_name);
+ break;
+ case NS_SPEC_PATH:
+ snprintf(path, sizeof(path), "%s", ns.path);
+ break;
+ }
+
+ if ((fd = open(path, O_CLOEXEC)) < 0) {
+ fprintf(stderr, "open for file %s: %s", path,
+ strerror(errno));
+ return -1;
+ }
+
+ if (setns(fd, 0) != 0) {
+ perror("setns");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int execute_syscall(void *args)
+{
+ struct arg_clone *a = (struct arg_clone *)args;
+ const struct op_call *c = a->args;
+
+ if (set_namespaces(a->args, a->pid) < 0) {
+ exit(EXIT_FAILURE);
+ }
+ /* execute syscall */
+ a->ret = syscall(c->nr, c->args[0], c->args[1], c->args[2], c->args[3],
+ c->args[4], c->args[5]);
+ a->err = errno;
+ if (a->ret < 0) {
+ perror("syscall");
+ exit(EXIT_FAILURE);
+ }
+ exit(0);
+}
+
+int copy_args(struct seccomp_notif *req, struct op_copy_args *copy, void *data,
+ int notifier)
+{
+ char path[PATH_MAX];
+ unsigned int i;
+ ssize_t nread;
+ void *dest;
+ int fd;
+
+ snprintf(path, sizeof(path), "/proc/%d/mem", req->pid);
+ if ((fd = open(path, O_RDONLY | O_CLOEXEC)) < 0) {
+ perror("open mem");
+ return -1;
+ }
+
+ /*
+ * Avoid the TOCTOU and check if the read mappings are still valid
+ */
+ 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);
+ }
+ }
+ close(fd);
+ return 0;
+}
+
+static int resolve_fd(void *data, struct op_resolvedfd *resfd, pid_t pid)
+{
+ char fdpath[PATH_MAX], buf[PATH_MAX];
+ char *path = (char *)((uint16_t *)data + resfd->path_off);
+ int *fd = (int *)((uint16_t *)data + resfd->fd_off);
+ ssize_t nbytes;
+
+ snprintf(fdpath, PATH_MAX, "/proc/%d/fd/%d", pid, *fd);
+ if ((nbytes = readlink(fdpath, buf, resfd->path_size)) < 0) {
+ fprintf(stderr, "error reading %s\n", fdpath);
+ perror("readlink");
+ return -1;
+ }
+ if (strcmp(path, buf) == 0)
+ return 0;
+ else
+ return 1;
+}
+
+int do_call(struct arg_clone *c)
+{
+ char stack[STACK_SIZE];
+ pid_t child;
+
+ c->ret = -1;
+ c->err = 0;
+
+ /* Create a process that will be moved to the namespace */
+ child = clone(execute_syscall, stack + sizeof(stack),
+ CLONE_FILES | CLONE_VM | SIGCHLD, (void *)c);
+ if (child == -1) {
+ perror("clone");
+ return -1;
+ }
+ wait(NULL);
+ return 0;
+}
+
+static void set_inject_fields(uint64_t id, void *data, const struct op *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->srcfd, (uint16_t *)data + old->fd_off,
+ sizeof(resp->srcfd));
+ if (old->type == IMMEDIATE)
+ resp->srcfd = old->fd;
+ else
+ memcpy(&resp->srcfd, (uint16_t *)data + old->fd_off,
+ sizeof(resp->srcfd));
+ resp->newfd_flags = 0;
+}
+
+int do_operations(void *data, struct op operations[], struct seccomp_notif *req,
+ unsigned int n_operations, int pid, int notifyfd, uint64_t id)
+{
+ struct seccomp_notif_addfd resp_fd;
+ struct seccomp_notif_resp resp;
+ struct arg_clone c;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < n_operations; i++) {
+ switch (operations[i].type) {
+ case OP_CALL:
+ resp.id = id;
+ resp.val = 0;
+ resp.flags = 0;
+ c.args = &operations[i].call;
+ c.pid = pid;
+ if (do_call(&c) == -1) {
+ resp.error = -1;
+ if (send_target(&resp, notifyfd) == -1)
+ return -1;
+ }
+ if (c.err != 0) {
+ resp.error = -1;
+ if (send_target(&resp, notifyfd) == -1)
+ return c.err;
+ }
+ /*
+ * 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,
+ &c.ret, sizeof(c.ret));
+ }
+ break;
+ case OP_BLOCK:
+ resp.id = id;
+ resp.val = 0;
+ resp.flags = 0;
+ resp.error = operations[i].block.error;
+ if (send_target(&resp, notifyfd) == -1)
+ return -1;
+ break;
+ case OP_RETURN:
+ resp.id = 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));
+
+ if (send_target(&resp, notifyfd) == -1)
+ return -1;
+ break;
+
+ case OP_CONT:
+ resp.id = id;
+ resp.flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE;
+ resp.error = 0;
+ resp.val = 0;
+ if (send_target(&resp, notifyfd) == -1)
+ return -1;
+ break;
+ case OP_INJECT_A:
+ set_inject_fields(id, data, &operations[i], &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(id, data, &operations[i], &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)
+ return -1;
+ break;
+ case OP_END:
+ return 0;
+ case OP_CMP:
+ if (memcmp((uint16_t *)data + operations[i].cmp.s1_off,
+ (uint16_t *)data + operations[i].cmp.s2_off,
+ operations[i].cmp.size) != 0) {
+ i = operations[i].cmp.jmp;
+ }
+ break;
+ case OP_RESOLVEDFD:
+ ret = resolve_fd(data, &operations[i].resfd, pid);
+ if (ret == -1)
+ return -1;
+ else if (ret == 1)
+ i = operations[i].resfd.jmp;
+ break;
+ default:
+ fprintf(stderr, "unknow operation %d \n",
+ operations[i].type);
+ }
+ }
+ return 0;
+}
diff --git a/src/seitan/operations.h b/src/seitan/operations.h
new file mode 100644
index 0000000..3691a50
--- /dev/null
+++ b/src/seitan/operations.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Alice Frosi <afrosi@redhat.com>
+ */
+
+#ifndef ACIONS_H
+#define ACTIONS_H
+
+#include <errno.h>
+#include <linux/seccomp.h>
+
+#define STACK_SIZE (1024 * 1024 / 8)
+#define NS_NUM (sizeof(enum ns_type))
+
+struct arg_clone {
+ const struct op_call *args;
+ pid_t pid;
+ long ret;
+ int err;
+};
+
+int do_call(struct arg_clone *c);
+int do_operations(void *data, struct op operations[], struct seccomp_notif *req,
+ unsigned int n_operations, int tpid, int notifyfd,
+ uint64_t id);
+#endif /* ACTIONS_H */
diff --git a/src/seitan/seitan.c b/src/seitan/seitan.c
new file mode 100644
index 0000000..ff0c54b
--- /dev/null
+++ b/src/seitan/seitan.c
@@ -0,0 +1,435 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+/* SEITAN - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * src/seitan/seitan.c - Wait for processes, listen for syscalls, handle them
+ *
+ * Copyright (c) 2022 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>, Alice Frosi <afrosi@redhat.com>
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <signal.h>
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/epoll.h>
+#include <sys/types.h>
+#include <argp.h>
+#include <linux/netlink.h>
+#include <linux/connector.h>
+#include <linux/cn_proc.h>
+
+#include <linux/audit.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+
+#include "common.h"
+#include "gluten.h"
+#include "operations.h"
+
+#define EPOLL_EVENTS 8
+#define errExit(msg) \
+ do { \
+ perror(msg); \
+ exit(EXIT_FAILURE); \
+ } while (0)
+
+static char doc[] = "Usage: seitan: setain -pid <pid> -i <input file> ";
+
+/* Seitan options */
+static struct argp_option options[] = {
+ { "input", 'i', "FILE", 0, "Action input file", 0 },
+ { "output", 'o', "FILE", 0, "Log filtered syscall in the file", 0 },
+ { "pid", 'p', "pid", 0,
+ "Pid of process to monitor (cannot be used together with -socket)",
+ 0 },
+ { "socket", 's', "/tmp/seitan.sock", 0,
+ "Socket to pass the seccomp notifier fd (cannot be used together with -pid)",
+ 0 },
+ { 0 }
+};
+
+struct arguments {
+ char *input_file;
+ char *output_file;
+ char *socket;
+ int pid;
+};
+
+static error_t parse_opt(int key, char *arg, struct argp_state *state)
+{
+ struct arguments *arguments = state->input;
+
+ switch (key) {
+ case 'p':
+ arguments->pid = atoi(arg);
+ break;
+ case 'i':
+ arguments->input_file = arg;
+ break;
+ case 'o':
+ arguments->output_file = arg;
+ break;
+ case 's':
+ arguments->socket = arg;
+ break;
+ case ARGP_KEY_END:
+ if (arguments->input_file == NULL)
+ argp_error(state, "missing input file");
+ if (strcmp(arguments->socket, "") > 0 && arguments->pid > 0)
+ argp_error(
+ state,
+ "the -socket and -pid options cannot be used together");
+ break;
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+
+ return 0;
+}
+
+static struct argp argp = { .options = options,
+ .parser = parse_opt,
+ .args_doc = NULL,
+ .doc = doc,
+ .children = NULL,
+ .help_filter = NULL,
+ .argp_domain = NULL };
+
+static int nl_init(void)
+{
+ int s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
+ struct sockaddr_nl sa = {
+ .nl_family = AF_NETLINK,
+ .nl_groups = CN_IDX_PROC,
+ .nl_pid = getpid(),
+ };
+ struct req_t {
+ struct nlmsghdr nlh;
+ struct cn_msg cnm;
+ enum proc_cn_mcast_op mop;
+ } __attribute__((packed, aligned(NLMSG_ALIGNTO))) req = {
+ .nlh.nlmsg_type = NLMSG_DONE,
+ .nlh.nlmsg_pid = getpid(),
+
+ .cnm.id.idx = CN_IDX_PROC,
+ .cnm.id.val = CN_VAL_PROC,
+ .cnm.len = sizeof(enum proc_cn_mcast_op),
+
+ .mop = PROC_CN_MCAST_LISTEN,
+ };
+
+ bind(s, (struct sockaddr *)&sa, sizeof(sa));
+
+ req.nlh.nlmsg_len = sizeof(req);
+ send(s, &req, sizeof(req), 0);
+
+ return s;
+}
+
+static int event(int s)
+{
+ char path[PATH_MAX + 1], exe[PATH_MAX + 1];
+ struct proc_event *ev;
+ struct nlmsghdr *nlh;
+ struct cn_msg *cnh;
+ char buf[BUFSIZ];
+ ssize_t n;
+
+ if ((n = recv(s, &buf, sizeof(buf), 0)) <= 0)
+ return -EIO;
+
+ nlh = (struct nlmsghdr *)buf;
+ for (; NLMSG_OK(nlh, n); nlh = NLMSG_NEXT(nlh, n)) {
+ if (nlh->nlmsg_type == NLMSG_NOOP)
+ continue;
+
+ if ((nlh->nlmsg_type == NLMSG_ERROR) ||
+ (nlh->nlmsg_type == NLMSG_OVERRUN))
+ break;
+
+ cnh = NLMSG_DATA(nlh);
+ ev = (struct proc_event *)cnh->data;
+
+ if (ev->what != PROC_EVENT_EXEC)
+ continue;
+
+ snprintf(path, PATH_MAX, "/proc/%i/exe",
+ ev->event_data.exec.process_pid);
+
+ readlink(path, exe, PATH_MAX);
+ if (!strcmp(exe, "/usr/local/bin/seitan-eater") ||
+ !strcmp(exe, "/usr/bin/seitan-eater"))
+ return ev->event_data.exec.process_pid;
+
+ if (nlh->nlmsg_type == NLMSG_DONE)
+ break;
+ }
+
+ return -EAGAIN;
+}
+
+enum transform {
+ NONE,
+ FD1_UNIX,
+ FDRET_SRC,
+ DEV_CHECK,
+};
+
+struct table {
+ enum transform type;
+ long number;
+
+ char arg[6][1024];
+};
+
+static struct table t[16];
+
+static int pidfd_send_signal(int pidfd, int sig, siginfo_t *info,
+ unsigned int flags)
+{
+ return syscall(__NR_pidfd_send_signal, pidfd, sig, info, flags);
+}
+
+static void unblock_eater(int pidfd)
+{
+ if (pidfd_send_signal(pidfd, SIGCONT, NULL, 0) == -1) {
+ perror("pidfd_send_signal");
+ exit(EXIT_FAILURE);
+ }
+}
+
+int handle(struct seccomp_notif *req, int notifyfd)
+{
+ char path[PATH_MAX + 1];
+ struct sockaddr_un s_un;
+ int fd_unix;
+ unsigned i;
+ int mem;
+
+ for (i = 0; i < sizeof(t) / sizeof(t[0]); i++) {
+ if (t[i].number == req->data.nr)
+ break;
+ }
+
+ if (i == sizeof(t) / sizeof(t[0])) /* Not found */
+ return 1;
+
+ if (t[i].type != FD1_UNIX) /* Not implemented yet */
+ return 1;
+
+ /* FD1_UNIX here */
+ snprintf(path, sizeof(path), "/proc/%i/mem", req->pid);
+ fd_unix = req->data.args[0];
+
+ mem = open(path, O_RDONLY);
+ lseek(mem, req->data.args[1], SEEK_SET);
+ read(mem, &s_un, sizeof(s_un));
+ close(mem);
+
+ if (!strcmp(s_un.sun_path, t[i].arg[0])) {
+ int own_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ struct seccomp_notif_addfd addfd = {
+ .id = req->id,
+ .flags = SECCOMP_ADDFD_FLAG_SEND |
+ SECCOMP_ADDFD_FLAG_SETFD,
+ .srcfd = own_fd,
+ .newfd = fd_unix,
+ };
+
+ connect(own_fd, &s_un, sizeof(s_un));
+ ioctl(notifyfd, SECCOMP_IOCTL_NOTIF_ADDFD, &addfd);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int create_socket(const char *path)
+{
+ struct sockaddr_un addr;
+ int ret, conn;
+ int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ errExit("error creating UNIX socket");
+
+ strcpy(addr.sun_path, path);
+ addr.sun_family = AF_UNIX;
+ ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (ret < 0)
+ errExit("bind");
+
+ ret = listen(fd, 1);
+ if (ret < 0)
+ errExit("listen");
+ conn = accept(fd, NULL, NULL);
+ if (conn < 0)
+ errExit("accept");
+
+ return conn;
+}
+
+static int recvfd(int sockfd)
+{
+ struct msghdr msgh;
+ struct iovec iov;
+ int data, fd;
+ ssize_t nr;
+
+ union {
+ char buf[CMSG_SPACE(sizeof(int))];
+ struct cmsghdr align;
+ } controlMsg;
+ struct cmsghdr *cmsgp;
+
+ msgh.msg_name = NULL;
+ msgh.msg_namelen = 0;
+
+ msgh.msg_iov = &iov;
+ msgh.msg_iovlen = 1;
+ iov.iov_base = &data;
+ iov.iov_len = sizeof(int);
+
+ msgh.msg_control = controlMsg.buf;
+ msgh.msg_controllen = sizeof(controlMsg.buf);
+
+ nr = recvmsg(sockfd, &msgh, 0);
+ if (nr == -1)
+ errExit("recvmsg");
+
+ cmsgp = CMSG_FIRSTHDR(&msgh);
+
+ if (cmsgp == NULL || cmsgp->cmsg_len != CMSG_LEN(sizeof(int)) ||
+ cmsgp->cmsg_level != SOL_SOCKET || cmsgp->cmsg_type != SCM_RIGHTS) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ memcpy(&fd, CMSG_DATA(cmsgp), sizeof(int));
+ return fd;
+}
+
+static int write_syscall(int fd, struct seccomp_notif *req)
+{
+ char buf[1000];
+
+ /* TODO: Define format and print syscall with the right arguments */
+ snprintf(buf, sizeof(buf), "nr_syscall=%d\n", req->data.nr);
+ write(fd, buf, strlen(buf));
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int s = nl_init(), ret, pidfd, notifier;
+ char req_b[BUFSIZ];
+ struct epoll_event ev, events[EPOLL_EVENTS];
+ struct seccomp_notif *req = (struct seccomp_notif *)req_b;
+ struct arguments arguments;
+ char path[PATH_MAX + 1];
+ bool running = true;
+ bool output = false;
+ int fd, epollfd;
+ int notifierfd;
+ int nevents, i;
+ int fdout;
+
+ arguments.pid = -1;
+ argp_parse(&argp, argc, argv, 0, 0, &arguments);
+ fd = open(arguments.input_file, O_CLOEXEC | O_RDONLY);
+ read(fd, t, sizeof(t));
+ close(fd);
+
+ if (strcmp(arguments.output_file, "") > 0) {
+ output = true;
+ unlink(arguments.output_file);
+ if ((fdout = open(arguments.output_file,
+ O_CREAT | O_RDWR | O_TRUNC)) < 0)
+ errExit("open");
+ }
+
+ if (arguments.pid > 0) {
+ if ((pidfd = syscall(SYS_pidfd_open, arguments.pid, 0)) < 0)
+ errExit("pidfd_open");
+ snprintf(path, sizeof(path), "/proc/%d/fd", arguments.pid);
+ if ((notifierfd = find_fd_seccomp_notifier(path)) < 0)
+ errExit("failed getting fd of the notifier");
+ if ((notifier = syscall(SYS_pidfd_getfd, pidfd, notifierfd,
+ 0)) < 0)
+ errExit("pidfd_getfd");
+ /* Unblock seitan-loader */
+ unblock_eater(pidfd);
+ } else if (strcmp(arguments.socket, "") > 0) {
+ unlink(arguments.socket);
+ if ((fd = create_socket(arguments.socket)) < 0)
+ exit(EXIT_FAILURE);
+ if ((notifier = recvfd(fd)) < 0)
+ exit(EXIT_FAILURE);
+ } else {
+ while ((ret = event(s)) == -EAGAIN)
+ ;
+ if (ret < 0)
+ exit(EXIT_FAILURE);
+ }
+ sleep(1);
+
+ if ((epollfd = epoll_create1(0)) < 0) {
+ perror("epoll_create");
+ exit(EXIT_FAILURE);
+ }
+ ev.events = EPOLLIN;
+ ev.data.fd = notifier;
+ if (epoll_ctl(epollfd, EPOLL_CTL_ADD, notifier, &ev) == -1) {
+ perror("epoll_ctl: notifier");
+ exit(EXIT_FAILURE);
+ }
+
+ while (running) {
+ nevents = epoll_wait(epollfd, events, EPOLL_EVENTS, -1);
+ if (nevents < 0) {
+ perror("epoll_wait");
+ exit(EXIT_FAILURE);
+ }
+ memset(req, 0, sizeof(*req));
+ if (ioctl(notifier, SECCOMP_IOCTL_NOTIF_RECV, req) < 0)
+ errExit("recieving seccomp notification");
+ for (i = 0; i < nevents; ++i) {
+ if (events[i].events & EPOLLHUP) {
+ /* The notifier fd was closed by the target */
+ running = false;
+ } else if (notifier == events[i].data.fd) {
+ /*
+ * TODO: remove until we parse correctly the
+ * operations from the bytecode
+ */
+ struct op operations[] = {
+ { .type = OP_CONT },
+ };
+ if (do_operations(NULL, operations, req,
+ sizeof(operations) /
+ sizeof(operations[0]),
+ req->pid, notifier,
+ req->id) == -1)
+ errExit("failed executing operation");
+
+ if (output)
+ write_syscall(fdout, req);
+ }
+ }
+ }
+ if (strcmp(arguments.socket, "") > 0)
+ unlink(arguments.socket);
+}