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/nft.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/nft.hjson')
-rw-r--r-- | demo/nft.hjson | 221 |
1 files changed, 221 insertions, 0 deletions
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" } + } + } +] |