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