// 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" }
}
}
]