diff options
Diffstat (limited to 'demo')
-rw-r--r-- | demo/Makefile | 9 | ||||
-rw-r--r-- | demo/fremovexattr.c | 11 | ||||
-rw-r--r-- | demo/fsetxattr.c | 11 | ||||
-rw-r--r-- | demo/iptables.hjson | 214 | ||||
-rw-r--r-- | demo/nft.hjson | 221 | ||||
-rw-r--r-- | demo/open_by_handle_at.c | 22 | ||||
-rw-r--r-- | demo/virtiofsd.hjson | 93 |
7 files changed, 581 insertions, 0 deletions
diff --git a/demo/Makefile b/demo/Makefile new file mode 100644 index 0000000..6b8558f --- /dev/null +++ b/demo/Makefile @@ -0,0 +1,9 @@ +SRCS=$(wildcard *.c) + +all: $(SRCS:%.c=%) + +clean: + $(RM) $(SRCS:%.c=%) + +%: %.c + $(CC) $(CFLAGS) -o $@ $<
\ No newline at end of file diff --git a/demo/fremovexattr.c b/demo/fremovexattr.c new file mode 100644 index 0000000..1483975 --- /dev/null +++ b/demo/fremovexattr.c @@ -0,0 +1,11 @@ +#include <sys/stat.h> +#include <sys/xattr.h> +#include <fcntl.h> +#include <string.h> + +int main(int argc, char **argv) +{ + int fd = open(argv[1], O_RDONLY); + + return fremovexattr(fd, argv[2]); +} diff --git a/demo/fsetxattr.c b/demo/fsetxattr.c new file mode 100644 index 0000000..6ee423a --- /dev/null +++ b/demo/fsetxattr.c @@ -0,0 +1,11 @@ +#include <sys/stat.h> +#include <sys/xattr.h> +#include <fcntl.h> +#include <string.h> + +int main(int argc, char **argv) +{ + int fd = open(argv[1], O_RDONLY); + + return fsetxattr(fd, argv[2], argv[3], strlen(argv[3]) + 1, 0); +} diff --git a/demo/iptables.hjson b/demo/iptables.hjson new file mode 100644 index 0000000..d24f476 --- /dev/null +++ b/demo/iptables.hjson @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* seitan - Syscall Expressive Interpreter, Transformer and Notifier + * + * demo/iptables.hjson - Example for iptables-nft: fetch ruleset, add rules + * + * Copyright (c) 2024 Red Hat GmbH + * Author: Stefano Brivio <sbrivio@redhat.com> + * + * Example of stand-alone usage: + * + * ./seitan-cooker \ + * -i demo/iptables.hjson -g demo/iptables.gluten -f demo/iptables.bpf + * ./seitan-eater \ + * -i demo/iptables.bpf -- /sbin/iptables -t mangle -A FORWARD -j LOG + * # blocks + * + * ./seitan -p $(pgrep seitan-eater) -i demo/iptables.gluten + * # as root or with CAP_NET_ADMIN: adds rule on behalf of caller + * + * ./seitan-eater \ + * -i demo/iptables.bpf -- /sbin/iptables -t mangle -D FORWARD -j LOG + * # blocks + * + * ./seitan -p $(pgrep seitan-eater) -i demo/iptables.gluten + * # as root or with CAP_NET_ADMIN: deletes rule on behalf of caller + */ + +[ + /* When the target process tries to open a netlink socket for netfilter, open + * one on behalf of the caller, owned by us, and replace it in the caller. + * + * For netlink operations, there's always a double permission check: + * both opener of the socket and sender of the messages need to have matching + * capabilities, see netlink_ns_capable() in net/netlink/af_netlink.c. + * + * This block takes care of the first part. + */ + { + "match": { + "socket": { + "family" : "netlink", + "type" : "raw", + "protocol" : "nl_netfilter" + } + /* socket(2) doesn't point to memory, so we can safely let any unrelated + * system calls proceed, directly in the caller, without replaying it + * ourselves. + */ + }, + "call": { + "socket": { + "family" : "netlink", + "type" : "raw", + "flags" : 0, + "protocol" : "nl_netfilter" + }, + "ret": "fd" + }, + "fd": { + "src": { "get": "fd" }, + "close_on_exec": true, + "return": true + } + }, + + /* Second part: send messages on behalf of the target process. + * + * First, the ones iptables needs to check for nftables compatibility, and to + * fetch tables, chains, rules and sets, including their generation (version) + * identifier. + * + * These are simple messages, without batches, using sendto(). + */ + { + "match": { + "sendto": { + "fd": { + "set": "fd" + }, + "buf": { + "set": "buf", + "value": { + "netlink": { + "type": { + "in": [ + "nf_compat_get", + "nf_getgen", + "nf_gettable", + "nf_getchain", + "nf_getrule", + "nf_getset" + ] + } + } + } + }, + "len" : { "set": "len" }, + "addr": { "set": "addr" } + } + }, + "call": { + "sendto": { + "fd": { "get": "fd" }, + "buf": { "get": "buf" }, + "len": { "get": "len" }, + "addr": { "get": "addr" }, + "flags": 0 + }, + "ret": "rc" + }, + "return": { "value": "rc", "error": "rc" } + }, + + /* sendto(2) points to memory, so we need to match on any unrelated sendto() + * call and replay it ourselves, but pretending we're the original process + * (see "context" below). Otherwise, a thread of the target process can fool + * us into sending other messages with our capability set. + */ + { + "match": { + "sendto": { + "fd": { "set": "fd" }, + "buf": { "set": "buf" }, + "len": { "set": "len" }, + "addr": { "set": "addr" }, + "flags": { "set": "flags" } + } + }, + "call": { + "sendto": { + "fd": { "get": "fd" }, + "buf": { "get": "buf" }, + "len": { "get": "len" }, + "addr": { "get": "addr" }, + "flags": { "get": "flags" } + }, + "context": { "uid": "caller", "gid": "caller" } + } + }, + + /* Now deal with the actual rule insertion or deletion: those are batched + * messages, using sendmsg(). Replay the message and relay return code and any + * error back. + */ + { + "match": { + "sendmsg": { + "fd": { "set": "fd" }, + "msg": { + "iov": { + "set": "buf", + "value": { + "netlink": { + "type": { "in": [ "nf_newrule", "nf_delrule" ] } + } + } + } + } + } + }, + "call": { + "sendmsg": { + "fd": { "get": "fd" }, + "msg": { + "name": { + "family": "netlink", + "pid": 0, + "groups": 0 + }, + "iovlen": 1, + "iov": { "get": "buf" } + }, + "flags": 0 + }, + "ret": "rc" + }, + "return": { "value": "rc", "error": "rc" } + }, + + /* Same as sendto(2): sendmsg(2) points to memory. Replay any unrelated system + * call with the credentials from the target process. + */ + { + "match": { + "sendmsg": { + "fd": { "set": "fd" }, + "msg": { + "name": { "set": "name" }, + "namelen": { "set": "namelen" }, + "iov": { "set": "buf" }, + "control": { "set": "control" }, + "controllen": { "set": "controllen" } + }, + "flags": { "set": "flags" } + } + }, + "call": { + "sendmsg": { + "fd": { "get": "fd" }, + "msg": { + "name": { "get": "name" }, + "namelen": { "get": "namelen" }, + "iov": { "get": "buf" }, + "iovlen": 1, + "control": { "get": "control" }, + "controllen": { "get": "controllen" } + }, + "flags": { "get": "flags" } + }, + "context": { "uid": "caller", "gid": "caller" } + } + } +] diff --git a/demo/nft.hjson b/demo/nft.hjson new file mode 100644 index 0000000..4ad7f1d --- /dev/null +++ b/demo/nft.hjson @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* seitan - Syscall Expressive Interpreter, Transformer and Notifier + * + * demo/nft.hjson - Example for nft(8): fetch ruleset, add/delete set elements + * + * Copyright (c) 2024 Red Hat GmbH + * Author: Stefano Brivio <sbrivio@redhat.com> + * + * Example of stand-alone usage. Prepare a table and a set for testing, first: + * + * nft add table ip test_t + * nft add set ip test_t test_s '{ type ipv4_addr ; }' + * + * Now add the set element: + * + * ./seitan-cooker \ + * -i demo/nft.hjson -g demo/nft.gluten -f demo/nft.bpf + * ./seitan-eater \ + * -i demo/nft.bpf -- /sbin/nft add element ip test_t test_s { 1.2.3.4 } + * # blocks + * + * ./seitan -p $(pgrep seitan-eater) -i demo/nft.gluten + * # as root or with CAP_NET_ADMIN: add element on behalf of caller + * + * nft list ruleset # Check that the new element is there + * + * ./seitan-eater \ + * -i demo/nft.bpf -- /sbin/nft delete element ip test_t test_s { 1.2.3.4 } + * # blocks + * + * ./seitan -p $(pgrep seitan-eater) -i demo/nft.gluten + * # as root or with CAP_NET_ADMIN: deletes element on behalf of caller + * + */ + +[ + /* Open netlink socket on behalf of the caller, owned by us, and replace in + * the caller. + * + * For netlink operations, there's always a double permission check: + * both opener of the socket and sender of the messages need to have matching + * capabilities, see netlink_ns_capable() in net/netlink/af_netlink.c. + * + * This block takes care of the first part. + */ + { + "match": { + "socket": { + "family" : "netlink", + "type" : "raw", + "protocol" : "nl_netfilter" + } + /* socket(2) doesn't point to memory, so we can safely let any unrelated + * system calls proceed, directly in the caller, without replaying it + * ourselves. + */ + }, + "call": { + "socket": { + "family" : "netlink", + "type" : "raw", + "flags" : 0, + "protocol" : "nl_netfilter" + }, + "ret": "fd" + }, + "fd": { + "src": { "get": "fd" }, + "close_on_exec": true, + "return": true + } + }, + + /* Second part: send messages on behalf of the target process. + * + * First, the ones to fetch tables, chains, sets, flow tables, and objects, + * including their generation (version) identifier. + * + * These are simple messages, without batches, using sendto(). + */ + { + "match": { + "sendto": { + "fd": { + "set": "fd" + }, + "buf": { + "set": "buf", + "value": { + "netlink": { + "type": { + "in": [ + "nf_getgen", + "nf_gettable", + "nf_getchain", + "nf_getobj", + "nf_getflowtable", + "nf_getset" + ] + } + } + } + }, + "len" : { "set": "len" }, + "addr": { "set": "addr" } + } + }, + "call": { + "sendto": { + "fd": { "get": "fd" }, + "buf": { "get": "buf" }, + "len": { "get": "len" }, + "addr": { "get": "addr" }, + "flags": 0 + }, + "ret": "rc" + }, + "return": { "value": "rc", "error": "rc" } + }, + + /* sendto(2) points to memory, so we need to match on any unrelated sendto() + * call and replay it ourselves, but pretending we're the original process + * (see "context" below). Otherwise, a thread of the target process can fool + * us into sending other messages with our capability set. + */ + { + "match": { + "sendto": { + "fd": { "set": "fd" }, + "buf": { "set": "buf" }, + "len": { "set": "len" }, + "addr": { "set": "addr" }, + "flags": { "set": "flags" } + } + }, + "call": { + "sendto": { + "fd": { "get": "fd" }, + "buf": { "get": "buf" }, + "len": { "get": "len" }, + "addr": { "get": "addr" }, + "flags": { "get": "flags" } + }, + "context": { "uid": "caller", "gid": "caller" } + } + }, + + /* Now deal with the actual element insertion or deletion: those are batched + * messages, using sendmsg(). Replay the message and relay return code and any + * error back. + */ + { + "match": { + "sendmsg": { + "fd": { "set": "fd" }, + "msg": { + "iov": { + "set": "buf", + "value": { + "netlink": { + "type": { "in": [ "nf_newsetelem", "nf_delsetelem" ] } + } + } + } + } + } + }, + "call": { + "sendmsg": { + "fd": { "get": "fd" }, + "msg": { + "name": { + "family": "netlink", + "pid": 0, + "groups": 0 + }, + "iovlen": 1, + "iov": { "get": "buf" } + }, + "flags": 0 + }, + "ret": "rc" + }, + "return": { "value": "rc", "error": "rc" } + }, + + /* Same as sendto(2): sendmsg(2) points to memory. Replay any unrelated system + * call with the credentials from the target process. + */ + { + "match": { + "sendmsg": { + "fd": { "set": "fd" }, + "msg": { + "name": { "set": "name" }, + "namelen": { "set": "namelen" }, + "iov": { "set": "buf" }, + "control": { "set": "control" }, + "controllen": { "set": "controllen" } + }, + "flags": { "set": "flags" } + } + }, + "call": { + "sendmsg": { + "fd": { "get": "fd" }, + "msg": { + "name": { "get": "name" }, + "namelen": { "get": "namelen" }, + "iov": { "get": "buf" }, + "iovlen": 1, + "control": { "get": "control" }, + "controllen": { "get": "controllen" } + }, + "flags": { "get": "flags" } + }, + "context": { "uid": "caller", "gid": "caller" } + } + } +] diff --git a/demo/open_by_handle_at.c b/demo/open_by_handle_at.c new file mode 100644 index 0000000..1f48eca --- /dev/null +++ b/demo/open_by_handle_at.c @@ -0,0 +1,22 @@ +#define _GNU_SOURCE +#include <sys/stat.h> +#include <sys/xattr.h> +#include <fcntl.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> + +int main(int argc, char **argv) +{ + struct { + struct file_handle f; + unsigned char h[MAX_HANDLE_SZ]; + } handle = { .f.handle_bytes = MAX_HANDLE_SZ }; + int mount_fd, fd; + char buf[BUFSIZ]; + + name_to_handle_at(AT_FDCWD, argv[1], &handle.f, &mount_fd, 0); + fd = open_by_handle_at(mount_fd, &handle.f, 0); + read(fd, buf, BUFSIZ); + fprintf(stdout, "%s", buf); +} diff --git a/demo/virtiofsd.hjson b/demo/virtiofsd.hjson new file mode 100644 index 0000000..8dfde03 --- /dev/null +++ b/demo/virtiofsd.hjson @@ -0,0 +1,93 @@ +[ + { + "match": [ + { + "fsetxattr": { + "fd": { "set": "fd" }, + "name": "security.overrated", + "value": { "set": "label" }, + "size": { "set": "size" }, + "flags": { "set": "flags" } + } + } + ], + + "call": [ + { + "fsetxattr": { + "fd": { "get": "fd" }, + "name": "security.overrated", + "value": { "get": "label" }, + "size": { "get": "size" }, + "flags": { "get": "flags" } + }, + "ret": "rc" + } + ], + + "return": { "value": "rc", "error": "rc" } + }, + + { + "match": [ + { + "fremovexattr": { + "fd": { "set": "fd" }, + "name": "security.overrated" + } + } + ], + + "call": [ + { + "fremovexattr": { + "fd": { "get": "fd" }, + "name": "security.overrated" + }, + "ret": "rc" + } + ], + + "return": { "value": "rc", "error": "rc" } + }, + + { + "match": [ + { + "open_by_handle_at": { + "mount": "/", + "handle": { + "handle_bytes": { "set": "len" }, + "f_handle": { "set": "handle" } + }, + "flags": { "set": "flags" } + } + } + ], + + "call": [ + { + "open": { + "path": "/", + "flags": "rdonly", + "mode": "S_IRUSR" + }, + "ret": "fdmount" + }, + { + "open_by_handle_at": { + "mount": { "get": "fdmount" }, + "handle": { + "handle_type": "ino32_gen", + "handle_bytes": { "get": "len" }, + "f_handle": { "get": "handle" } + }, + "flags": { "get": "flags" } + }, + "ret": "new_fd" + } + ], + + "fd": { "src": { "get": "new_fd" }, "close_on_exec": false, "return": true } + } +] |