aboutgitcodelistschat:MatrixIRC
path: root/demo/nft.hjson
blob: 4ad7f1de2c6d145eb4bd8f223274a20b1f7b282b (plain) (tree)




























































































































































































































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