aboutgitcodelistschat:MatrixIRC
path: root/demo
diff options
context:
space:
mode:
Diffstat (limited to 'demo')
-rw-r--r--demo/iptables.hjson214
-rw-r--r--demo/nft.hjson221
2 files changed, 435 insertions, 0 deletions
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" }
+ }
+ }
+]