selftests: netfilter: Test nf_tables audit logging
authorPhil Sutter <phil@nwl.cc>
Wed, 13 Sep 2023 13:51:37 +0000 (15:51 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Wed, 13 Sep 2023 19:57:50 +0000 (21:57 +0200)
Compare NETFILTER_CFG type audit logs emitted from kernel upon ruleset
modifications against expected output.

Signed-off-by: Phil Sutter <phil@nwl.cc>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
tools/testing/selftests/netfilter/.gitignore
tools/testing/selftests/netfilter/Makefile
tools/testing/selftests/netfilter/audit_logread.c [new file with mode: 0644]
tools/testing/selftests/netfilter/config
tools/testing/selftests/netfilter/nft_audit.sh [new file with mode: 0755]

index 4cb887b..4b2928e 100644 (file)
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 nf-queue
 connect_close
+audit_logread
index 3686bfa..321db88 100644 (file)
@@ -6,13 +6,13 @@ TEST_PROGS := nft_trans_stress.sh nft_fib.sh nft_nat.sh bridge_brouter.sh \
        nft_concat_range.sh nft_conntrack_helper.sh \
        nft_queue.sh nft_meta.sh nf_nat_edemux.sh \
        ipip-conntrack-mtu.sh conntrack_tcp_unreplied.sh \
-       conntrack_vrf.sh nft_synproxy.sh rpath.sh
+       conntrack_vrf.sh nft_synproxy.sh rpath.sh nft_audit.sh
 
 HOSTPKG_CONFIG := pkg-config
 
 CFLAGS += $(shell $(HOSTPKG_CONFIG) --cflags libmnl 2>/dev/null)
 LDLIBS += $(shell $(HOSTPKG_CONFIG) --libs libmnl 2>/dev/null || echo -lmnl)
 
-TEST_GEN_FILES =  nf-queue connect_close
+TEST_GEN_FILES =  nf-queue connect_close audit_logread
 
 include ../lib.mk
diff --git a/tools/testing/selftests/netfilter/audit_logread.c b/tools/testing/selftests/netfilter/audit_logread.c
new file mode 100644 (file)
index 0000000..a0a880f
--- /dev/null
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <linux/audit.h>
+#include <linux/netlink.h>
+
+static int fd;
+
+#define MAX_AUDIT_MESSAGE_LENGTH       8970
+struct audit_message {
+       struct nlmsghdr nlh;
+       union {
+               struct audit_status s;
+               char data[MAX_AUDIT_MESSAGE_LENGTH];
+       } u;
+};
+
+int audit_recv(int fd, struct audit_message *rep)
+{
+       struct sockaddr_nl addr;
+       socklen_t addrlen = sizeof(addr);
+       int ret;
+
+       do {
+               ret = recvfrom(fd, rep, sizeof(*rep), 0,
+                              (struct sockaddr *)&addr, &addrlen);
+       } while (ret < 0 && errno == EINTR);
+
+       if (ret < 0 ||
+           addrlen != sizeof(addr) ||
+           addr.nl_pid != 0 ||
+           rep->nlh.nlmsg_type == NLMSG_ERROR) /* short-cut for now */
+               return -1;
+
+       return ret;
+}
+
+int audit_send(int fd, uint16_t type, uint32_t key, uint32_t val)
+{
+       static int seq = 0;
+       struct audit_message msg = {
+               .nlh = {
+                       .nlmsg_len   = NLMSG_SPACE(sizeof(msg.u.s)),
+                       .nlmsg_type  = type,
+                       .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
+                       .nlmsg_seq   = ++seq,
+               },
+               .u.s = {
+                       .mask    = key,
+                       .enabled = key == AUDIT_STATUS_ENABLED ? val : 0,
+                       .pid     = key == AUDIT_STATUS_PID ? val : 0,
+               }
+       };
+       struct sockaddr_nl addr = {
+               .nl_family = AF_NETLINK,
+       };
+       int ret;
+
+       do {
+               ret = sendto(fd, &msg, msg.nlh.nlmsg_len, 0,
+                            (struct sockaddr *)&addr, sizeof(addr));
+       } while (ret < 0 && errno == EINTR);
+
+       if (ret != (int)msg.nlh.nlmsg_len)
+               return -1;
+       return 0;
+}
+
+int audit_set(int fd, uint32_t key, uint32_t val)
+{
+       struct audit_message rep = { 0 };
+       int ret;
+
+       ret = audit_send(fd, AUDIT_SET, key, val);
+       if (ret)
+               return ret;
+
+       ret = audit_recv(fd, &rep);
+       if (ret < 0)
+               return ret;
+       return 0;
+}
+
+int readlog(int fd)
+{
+       struct audit_message rep = { 0 };
+       int ret = audit_recv(fd, &rep);
+       const char *sep = "";
+       char *k, *v;
+
+       if (ret < 0)
+               return ret;
+
+       if (rep.nlh.nlmsg_type != AUDIT_NETFILTER_CFG)
+               return 0;
+
+       /* skip the initial "audit(...): " part */
+       strtok(rep.u.data, " ");
+
+       while ((k = strtok(NULL, "="))) {
+               v = strtok(NULL, " ");
+
+               /* these vary and/or are uninteresting, ignore */
+               if (!strcmp(k, "pid") ||
+                   !strcmp(k, "comm") ||
+                   !strcmp(k, "subj"))
+                       continue;
+
+               /* strip the varying sequence number */
+               if (!strcmp(k, "table"))
+                       *strchrnul(v, ':') = '\0';
+
+               printf("%s%s=%s", sep, k, v);
+               sep = " ";
+       }
+       if (*sep) {
+               printf("\n");
+               fflush(stdout);
+       }
+       return 0;
+}
+
+void cleanup(int sig)
+{
+       audit_set(fd, AUDIT_STATUS_ENABLED, 0);
+       close(fd);
+       if (sig)
+               exit(0);
+}
+
+int main(int argc, char **argv)
+{
+       struct sigaction act = {
+               .sa_handler = cleanup,
+       };
+
+       fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_AUDIT);
+       if (fd < 0) {
+               perror("Can't open netlink socket");
+               return -1;
+       }
+
+       if (sigaction(SIGTERM, &act, NULL) < 0 ||
+           sigaction(SIGINT, &act, NULL) < 0) {
+               perror("Can't set signal handler");
+               close(fd);
+               return -1;
+       }
+
+       audit_set(fd, AUDIT_STATUS_ENABLED, 1);
+       audit_set(fd, AUDIT_STATUS_PID, getpid());
+
+       while (1)
+               readlog(fd);
+}
index 4faf2ce..7c42b1b 100644 (file)
@@ -6,3 +6,4 @@ CONFIG_NFT_REDIR=m
 CONFIG_NFT_MASQ=m
 CONFIG_NFT_FLOW_OFFLOAD=m
 CONFIG_NF_CT_NETLINK=m
+CONFIG_AUDIT=y
diff --git a/tools/testing/selftests/netfilter/nft_audit.sh b/tools/testing/selftests/netfilter/nft_audit.sh
new file mode 100755 (executable)
index 0000000..83c271b
--- /dev/null
@@ -0,0 +1,108 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Check that audit logs generated for nft commands are as expected.
+
+SKIP_RC=4
+RC=0
+
+nft --version >/dev/null 2>&1 || {
+       echo "SKIP: missing nft tool"
+       exit $SKIP_RC
+}
+
+logfile=$(mktemp)
+echo "logging into $logfile"
+./audit_logread >"$logfile" &
+logread_pid=$!
+trap 'kill $logread_pid; rm -f $logfile' EXIT
+exec 3<"$logfile"
+
+do_test() { # (cmd, log)
+       echo -n "testing for cmd: $1 ... "
+       cat <&3 >/dev/null
+       $1 >/dev/null || exit 1
+       sleep 0.1
+       res=$(diff -a -u <(echo "$2") - <&3)
+       [ $? -eq 0 ] && { echo "OK"; return; }
+       echo "FAIL"
+       echo "$res"
+       ((RC++))
+}
+
+nft flush ruleset
+
+for table in t1 t2; do
+       do_test "nft add table $table" \
+       "table=$table family=2 entries=1 op=nft_register_table"
+
+       do_test "nft add chain $table c1" \
+       "table=$table family=2 entries=1 op=nft_register_chain"
+
+       do_test "nft add chain $table c2; add chain $table c3" \
+       "table=$table family=2 entries=2 op=nft_register_chain"
+
+       cmd="add rule $table c1 counter"
+
+       do_test "nft $cmd" \
+       "table=$table family=2 entries=1 op=nft_register_rule"
+
+       do_test "nft $cmd; $cmd" \
+       "table=$table family=2 entries=2 op=nft_register_rule"
+
+       cmd=""
+       sep=""
+       for chain in c2 c3; do
+               for i in {1..3}; do
+                       cmd+="$sep add rule $table $chain counter"
+                       sep=";"
+               done
+       done
+       do_test "nft $cmd" \
+       "table=$table family=2 entries=6 op=nft_register_rule"
+done
+
+do_test 'nft reset rules t1 c2' \
+'table=t1 family=2 entries=3 op=nft_reset_rule'
+
+do_test 'nft reset rules table t1' \
+'table=t1 family=2 entries=3 op=nft_reset_rule
+table=t1 family=2 entries=3 op=nft_reset_rule
+table=t1 family=2 entries=3 op=nft_reset_rule'
+
+do_test 'nft reset rules' \
+'table=t1 family=2 entries=3 op=nft_reset_rule
+table=t1 family=2 entries=3 op=nft_reset_rule
+table=t1 family=2 entries=3 op=nft_reset_rule
+table=t2 family=2 entries=3 op=nft_reset_rule
+table=t2 family=2 entries=3 op=nft_reset_rule
+table=t2 family=2 entries=3 op=nft_reset_rule'
+
+for ((i = 0; i < 500; i++)); do
+       echo "add rule t2 c3 counter accept comment \"rule $i\""
+done | do_test 'nft -f -' \
+'table=t2 family=2 entries=500 op=nft_register_rule'
+
+do_test 'nft reset rules t2 c3' \
+'table=t2 family=2 entries=189 op=nft_reset_rule
+table=t2 family=2 entries=188 op=nft_reset_rule
+table=t2 family=2 entries=126 op=nft_reset_rule'
+
+do_test 'nft reset rules t2' \
+'table=t2 family=2 entries=3 op=nft_reset_rule
+table=t2 family=2 entries=3 op=nft_reset_rule
+table=t2 family=2 entries=186 op=nft_reset_rule
+table=t2 family=2 entries=188 op=nft_reset_rule
+table=t2 family=2 entries=129 op=nft_reset_rule'
+
+do_test 'nft reset rules' \
+'table=t1 family=2 entries=3 op=nft_reset_rule
+table=t1 family=2 entries=3 op=nft_reset_rule
+table=t1 family=2 entries=3 op=nft_reset_rule
+table=t2 family=2 entries=3 op=nft_reset_rule
+table=t2 family=2 entries=3 op=nft_reset_rule
+table=t2 family=2 entries=180 op=nft_reset_rule
+table=t2 family=2 entries=188 op=nft_reset_rule
+table=t2 family=2 entries=135 op=nft_reset_rule'
+
+exit $RC