diff options
author | Stefano Brivio <sbrivio@redhat.com> | 2024-08-13 18:50:33 +0200 |
---|---|---|
committer | Stefano Brivio <sbrivio@redhat.com> | 2024-08-13 19:00:35 +0200 |
commit | 9bf3b1cc7a94357c250f77f16829c96cbae801fe (patch) | |
tree | 56cbc184974b18d33aa288dda7b12e5a77c38a94 /demo/iptables.hjson | |
parent | d699dac08778c597eefac1067a325059925e87e6 (diff) | |
download | seitan-9bf3b1cc7a94357c250f77f16829c96cbae801fe.tar seitan-9bf3b1cc7a94357c250f77f16829c96cbae801fe.tar.gz seitan-9bf3b1cc7a94357c250f77f16829c96cbae801fe.tar.bz2 seitan-9bf3b1cc7a94357c250f77f16829c96cbae801fe.tar.lz seitan-9bf3b1cc7a94357c250f77f16829c96cbae801fe.tar.xz seitan-9bf3b1cc7a94357c250f77f16829c96cbae801fe.tar.zst seitan-9bf3b1cc7a94357c250f77f16829c96cbae801fe.zip |
We want to add and delete rules with iptables(8), and manipulate set
elements with nft(8).
These are the first users we encounter sending multiple netlink
messages in one sendmsg().
To support matching on those, we need to iterate over several
messages, looking for a matching one, or a mismatching one (depending
on quantifiers and match type), but we don't want to implement program
loops because of security design reasons.
We can't implement a generalised instruction that vectorises existing
ones, either, because we need to support universal and existential
quantifiers in fields that are repeated multiple times, once per each
netlink message, with bitwise operations and non-exact matching types.
Add vectorisation support to OP_CMP and OP_BITWISE instead, with a
generic description for a vector (only sequences of netlink messages
with length in nlmsghdr are supported at the moment) so that,
depending on the quantifiers, we'll repeat those operations as many
times as needed. This way, we don't risk any O(n^2) explosion, and we
are bound by O(m * n) instead, with m compare/bitwise operations for
a given expression, and n number of netlink messages.
Add demos for nft and iptables using the new concepts.
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
Diffstat (limited to 'demo/iptables.hjson')
-rw-r--r-- | demo/iptables.hjson | 214 |
1 files changed, 214 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" } + } + } +] |