Home | History | Annotate | Line # | Download | only in libnpftest
      1 /*
      2  * NPF socket User/group id tests.
      3  *
      4  * Public Domain.
      5  */
      6 
      7 #ifdef _KERNEL
      8 #include <sys/types.h>
      9 #include <sys/cdefs.h>
     10 __KERNEL_RCSID(0, "$NetBSD: npf_rid_test.c,v 1.4 2026/03/08 16:59:24 christos Exp $");
     11 #endif
     12 
     13 #include "npf_impl.h"
     14 #include "npf_test.h"
     15 
     16 #include <netinet/in.h>
     17 #include <sys/socket.h>
     18 #include <sys/kauth.h>
     19 #include <sys/socketvar.h>
     20 #include <sys/lwp.h>
     21 #include <sys/cpu.h>
     22 
     23 #define	RESULT_PASS	0
     24 #define	RESULT_BLOCK	ENETUNREACH
     25 
     26 /* this port number suitable for testing */
     27 #define REMOTE_PORT	65500
     28 #define LOCAL_PORT	65000
     29 #define LOCAL_IP	"127.0.0.1"
     30 #define REMOTE_IP	LOCAL_IP
     31 
     32 static const struct test_case {
     33 	int		af;
     34 	const char *	src;
     35 	uint16_t	sport;
     36 	const char *	dst;
     37 	uint16_t	dport;
     38 	uint32_t	uid;
     39 	uint32_t	gid;
     40 	const char *	ifname;
     41 	int		di;
     42 	int 	ret;
     43 	int 	stateful_ret;
     44 } test_cases[] = {
     45 	{
     46 		/* pass in final from $local_ip4 user $Kojo = 1001 group $wheel = 20 */
     47 		.af = AF_INET,
     48 		.src = "10.1.1.4",			.sport = 9000,
     49 		.dst = LOCAL_IP,			.dport = LOCAL_PORT,
     50 		.ifname = IFNAME_EXT,			.di = PFIL_IN,
     51 		.uid = 1001,				.gid = 20, /* matches so pass it */
     52 		.ret = RESULT_PASS,			.stateful_ret = RESULT_PASS
     53 	},
     54 	{
     55 		/* connect on different UID and block */
     56 		.af = AF_INET,
     57 		.src = "10.1.1.4",			.sport = 9000,
     58 		.dst = LOCAL_IP,			.dport = LOCAL_PORT,
     59 		.ifname = IFNAME_EXT,			.di = PFIL_IN,
     60 		.uid = 1001,				.gid = 10, /* mismatch gid so block it */
     61 		.ret = RESULT_BLOCK,			.stateful_ret = RESULT_BLOCK
     62 	},
     63 	{
     64 		.af = AF_INET,
     65 		.src = "10.1.1.4",			.sport = 9000,
     66 		.dst = LOCAL_IP,			.dport = LOCAL_PORT,
     67 		.ifname = IFNAME_EXT,			.di = PFIL_IN,
     68 		.uid = 100,				.gid = 20, /* mismatch uid so block it */
     69 		.ret = RESULT_BLOCK,			.stateful_ret = RESULT_BLOCK
     70 	},
     71 
     72 
     73 	/* block out final to 127.0.0.1 user > $Kojo( > 1001) group 1 >< $wheel( IRG 1 >< 20) */
     74 	{
     75 		.af = AF_INET,
     76 		.src = LOCAL_IP,			.sport = LOCAL_PORT,
     77 		.dst = REMOTE_IP,			.dport = REMOTE_PORT,
     78 		.ifname = IFNAME_EXT,			.di = PFIL_OUT,
     79 		.uid = 1005,				.gid = 14, /* matches so blocks it */
     80 		.ret = RESULT_BLOCK,			.stateful_ret = RESULT_BLOCK
     81 	},
     82 	{
     83 		.af = AF_INET,
     84 		.src = LOCAL_IP,			.sport = LOCAL_PORT,
     85 		.dst = REMOTE_IP,			.dport = REMOTE_PORT,
     86 		.ifname = IFNAME_EXT,			.di = PFIL_OUT,
     87 		.uid = 1005,				.gid = 30, /* mismatch gid so pass it */
     88 		.ret = RESULT_PASS,			.stateful_ret = RESULT_PASS
     89 	},
     90 	{
     91 		.af = AF_INET,
     92 		.src = LOCAL_IP,			.sport = LOCAL_PORT,
     93 		.dst = REMOTE_IP,			.dport = REMOTE_PORT,
     94 		.ifname = IFNAME_EXT,			.di = PFIL_OUT,
     95 		.uid = 100,				.gid = 15, /* mismatch uid so pass it */
     96 		.ret = RESULT_PASS,			.stateful_ret = RESULT_PASS
     97 	},
     98 	{
     99 		.af = AF_INET,
    100 		.src = LOCAL_IP,			.sport = LOCAL_PORT,
    101 		.dst = REMOTE_IP,			.dport = REMOTE_PORT,
    102 		.ifname = IFNAME_EXT,			.di = PFIL_OUT,
    103 		.uid = 1010,				.gid = 11, /* matches so blocks it */
    104 		.ret = RESULT_BLOCK,			.stateful_ret = RESULT_BLOCK
    105 	},
    106 };
    107 
    108 static int
    109 run_raw_testcase(unsigned i, bool verbose)
    110 {
    111 	const struct test_case *t = &test_cases[i];
    112 	npf_t *npf = npf_getkernctx();
    113 	npf_cache_t *npc;
    114 	struct mbuf *m;
    115 	npf_rule_t *rl;
    116 	int slock, error;
    117 
    118 	m = mbuf_get_pkt(t->af, IPPROTO_UDP, t->src, t->dst, t->sport, t->dport);
    119 	npc = get_cached_pkt(m, t->ifname,  NPF_RULE_LAYER_3);
    120 
    121 	slock = npf_config_read_enter(npf);
    122 	rl = npf_ruleset_inspect(npc, npf_config_ruleset(npf), t->di,  NPF_RULE_LAYER_3);
    123 	if (rl) {
    124 		npf_match_info_t mi;
    125 		int id_match;
    126 
    127 		mutex_enter(softnet_lock);
    128 		id_match = npf_rule_match_rid(rl, npc, t->di);
    129 		mutex_exit(softnet_lock);
    130 		error = npf_rule_conclude(rl, &mi);
    131 		if (verbose)
    132 			printf("id match is ...%d\n", id_match);
    133 		if (id_match != -1 && !id_match) {
    134 			error = npf_rule_reverse(npc, &mi, error);
    135 		}
    136 
    137 	} else {
    138 		error = ENOENT;
    139 	}
    140 	npf_config_read_exit(npf, slock);
    141 
    142 	put_cached_pkt(npc);
    143 	return error;
    144 }
    145 
    146 static int
    147 run_handler_testcase(unsigned i)
    148 {
    149 	const struct test_case *t = &test_cases[i];
    150 	ifnet_t *ifp = npf_test_getif(t->ifname);
    151 	npf_t *npf = npf_getkernctx();
    152 	struct mbuf *m;
    153 	int error;
    154 
    155 	m = mbuf_get_pkt(t->af, IPPROTO_UDP, t->src, t->dst, t->sport, t->dport);
    156 	mutex_enter(softnet_lock);
    157 	error = npfk_packet_handler(npf, &m, ifp, t->di);
    158 	mutex_exit(softnet_lock);
    159 	if (m) {
    160 		m_freem(m);
    161 	}
    162 	return error;
    163 }
    164 
    165 /*
    166  * we create our specific server socket here which listens on
    167  * loopback address and port 65000. easier to test pcb lookup here since
    168  * it will be loaded into the protocol table.
    169  */
    170 static struct socket *
    171 test_socket(int dir, uid_t uid, gid_t gid)
    172 {
    173 	struct sockaddr_in server;
    174 	struct lwp *cur = curlwp;
    175 	void *p, *rp;
    176 
    177 	memset(&server, 0, sizeof(server));
    178 
    179 	server.sin_len = sizeof(server);
    180 	server.sin_family = AF_INET;
    181 	p = &server.sin_addr.s_addr;
    182 	npf_inet_pton(AF_INET, LOCAL_IP, p); /* we bind to 127.0.0.1 */
    183 	server.sin_port = htons(LOCAL_PORT);
    184 
    185 	struct socket *so;
    186 	int error = socreate(AF_INET, &so, SOCK_DGRAM, 0, cur, NULL);
    187 	if (error) {
    188 		printf("socket creation failed: error is %d\n", error);
    189 		return NULL;
    190 	}
    191 
    192 	solock(so);
    193 
    194 	kauth_cred_t cred = kauth_cred_alloc();
    195 	kauth_cred_seteuid(cred, uid);
    196 	kauth_cred_setegid(cred, gid);
    197 
    198 	kauth_cred_t old = so->so_cred;
    199 	so->so_cred = kauth_cred_dup(cred);
    200 	kauth_cred_free(old);
    201 
    202 	sounlock(so);
    203 
    204 	if ((error = sobind(so, (struct sockaddr *)&server, cur)) != 0) {
    205 		printf("bind failed %d\n", error);
    206 		return NULL;
    207 	}
    208 
    209 	if (dir == PFIL_OUT) {
    210 		/* connect to an additional remote address to set the 4 tuple addr-port state */
    211 		struct sockaddr_in remote;
    212 		memset(&remote, 0, sizeof(remote));
    213 
    214 		remote.sin_len = sizeof(remote);
    215 		remote.sin_family = AF_INET;
    216 		rp = &remote.sin_addr.s_addr;
    217 		npf_inet_pton(AF_INET, REMOTE_IP, rp); /* we connect to 127.0.0.1 */
    218 		remote.sin_port = htons(REMOTE_PORT);
    219 
    220 		solock(so);
    221 		if ((error = soconnect(so, (struct sockaddr *)&remote, cur)) != 0) {
    222 			printf("connect failed :%d\n", error);
    223 			return NULL;
    224 		}
    225 		sounlock(so);
    226 	}
    227 
    228 	return so;
    229 }
    230 
    231 static bool
    232 test_static(bool verbose)
    233 {
    234 	for (size_t i = 0; i < __arraycount(test_cases); i++) {
    235 		const struct test_case *t = &test_cases[i];
    236 		int error, serror;
    237 		struct socket *so;
    238 
    239 		so = test_socket(t->di, t->uid, t->gid);
    240 		if (so == NULL) {
    241 			printf("socket:\n");
    242 			return false;
    243 		}
    244 
    245 		if (npf_test_getif(t->ifname) == NULL) {
    246 			printf("Interface %s is not configured.\n", t->ifname);
    247 			return false;
    248 		}
    249 
    250 		error = run_raw_testcase(i, verbose);
    251 		serror = run_handler_testcase(i);
    252 
    253 		if (verbose) {
    254 			printf("rule test %zu:\texpected %d (stateful) and %d\n"
    255 			    "\t\t-> returned %d and %d\n",
    256 			    i + 1, t->stateful_ret, t->ret, serror, error);
    257 		}
    258 		CHECK_TRUE(error == t->ret);
    259 		CHECK_TRUE(serror == t->stateful_ret)
    260 
    261 		soclose(so);
    262 	}
    263 	return true;
    264 }
    265 
    266 bool
    267 npf_guid_test(bool verbose)
    268 {
    269 	soinit1();
    270 
    271 	bool ok;
    272 
    273 	ok = test_static(verbose);
    274 	CHECK_TRUE(ok);
    275 
    276 	return true;
    277 }
    278