Commit e8dbde59 authored by Phil Sutter's avatar Phil Sutter Committed by Pablo Neira Ayuso
Browse files

selftests: netfilter: Test nf_tables audit logging



Compare NETFILTER_CFG type audit logs emitted from kernel upon ruleset
modifications against expected output.

Signed-off-by: default avatarPhil Sutter <phil@nwl.cc>
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
parent 7fb818f2
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
# SPDX-License-Identifier: GPL-2.0-only
nf-queue
connect_close
audit_logread
+2 −2
Original line number Diff line number Diff line
@@ -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
+165 −0
Original line number Diff line number Diff line
// 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);
}
+1 −0
Original line number Diff line number Diff line
@@ -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
+108 −0
Original line number Diff line number Diff line
#!/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