diff --git a/lib/libnpf/npf.c b/lib/libnpf/npf.c index 80116a34799a3..eb179ea996ae4 100644 --- a/lib/libnpf/npf.c +++ b/lib/libnpf/npf.c @@ -735,6 +735,16 @@ npf_rule_setproc(nl_rule_t *rl, const char *name) return nvlist_error(rl->rule_dict); } + +/* use a single id hack for both user and group */ +int +npf_rule_setrid(nl_rule_t *rl, struct r_id rid, const char *name) +{ + uint64_t uid_element[3] = { rid.id[0], rid.id[1], rid.op }; + nvlist_add_number_array(rl->rule_dict, name, uid_element, 3); + return nvlist_error(rl->rule_dict); +} + void * npf_rule_export(nl_rule_t *rl, size_t *length) { @@ -843,6 +853,22 @@ npf_rule_getproc(nl_rule_t *rl) return dnvlist_get_string(rl->rule_dict, "rproc", NULL); } +int +npf_rule_getrid(struct r_id *r_id, nl_rule_t *rl, const char *key) +{ + if (nvlist_exists_number_array(rl->rule_dict, key)) { + size_t nitems; + const uint64_t *rid = nvlist_get_number_array(rl->rule_dict, key, &nitems); + assert(nitems == 3); + + r_id->id[0] = (uint32_t)rid[0]; + r_id->id[1] = (uint32_t)rid[1]; + r_id->op = (uint8_t)rid[2]; + return 0; + } + return -1; +} + uint64_t npf_rule_getid(nl_rule_t *rl) { diff --git a/lib/libnpf/npf.expsym b/lib/libnpf/npf.expsym index 2fd9d4feff750..2e9a2c9a036e6 100644 --- a/lib/libnpf/npf.expsym +++ b/lib/libnpf/npf.expsym @@ -68,6 +68,7 @@ npf_rule_getinfo npf_rule_getinterface npf_rule_getname npf_rule_getproc +npf_rule_getrid npf_rule_insert npf_rule_iterate npf_rule_setcode @@ -75,6 +76,7 @@ npf_rule_setinfo npf_rule_setkey npf_rule_setprio npf_rule_setproc +npf_rule_setrid npf_ruleset_add npf_ruleset_flush npf_ruleset_remkey diff --git a/lib/libnpf/npf.h b/lib/libnpf/npf.h index aab769f09882a..703dfe3966e3a 100644 --- a/lib/libnpf/npf.h +++ b/lib/libnpf/npf.h @@ -106,6 +106,7 @@ nl_rule_t * npf_rule_create(const char *, uint32_t, const char *); int npf_rule_setcode(nl_rule_t *, int, const void *, size_t); int npf_rule_setprio(nl_rule_t *, int); int npf_rule_setproc(nl_rule_t *, const char *); +int npf_rule_setrid(nl_rule_t *, struct r_id, const char *); int npf_rule_setkey(nl_rule_t *, const void *, size_t); int npf_rule_setinfo(nl_rule_t *, const void *, size_t); const char * npf_rule_getname(nl_rule_t *); @@ -113,6 +114,7 @@ uint32_t npf_rule_getattr(nl_rule_t *); const char * npf_rule_getinterface(nl_rule_t *); const void * npf_rule_getinfo(nl_rule_t *, size_t *); const char * npf_rule_getproc(nl_rule_t *); +int npf_rule_getrid(struct r_id *, nl_rule_t *, const char *); uint64_t npf_rule_getid(nl_rule_t *); const void * npf_rule_getcode(nl_rule_t *, int *, size_t *); bool npf_rule_exists_p(nl_config_t *, const char *); diff --git a/sys/modules/npf/Makefile b/sys/modules/npf/Makefile index f2a4085cb2d22..cc0ec7620ef7f 100644 --- a/sys/modules/npf/Makefile +++ b/sys/modules/npf/Makefile @@ -13,7 +13,7 @@ SRCS= npf.c npf_alg.c npf_conf.c npf_ctl.c npf_handler.c SRCS+= npf_bpf.c npf_if.c npf_inet.c npf_mbuf.c npf_nat.c SRCS+= npf_params.c npf_ruleset.c npf_rproc.c SRCS+= npf_conn.c npf_conndb.c npf_connkey.c npf_portmap.c -SRCS+= npf_state.c npf_state_tcp.c npf_tableset.c +SRCS+= npf_state.c npf_state_tcp.c npf_tableset.c npf_socket.c SRCS+= lpm.c npf_sendpkt.c npf_worker.c npf_os.c npf_ifaddr.c SRCS+= nvlist.c nvpair.c nv_kern_netbsd.c dnvlist.c diff --git a/sys/net/npf/files.npf b/sys/net/npf/files.npf index 80727f79f5d39..be7dd787cd9ee 100644 --- a/sys/net/npf/files.npf +++ b/sys/net/npf/files.npf @@ -32,6 +32,7 @@ file net/npf/npf_portmap.c npf file net/npf/npf_alg.c npf file net/npf/npf_sendpkt.c npf file net/npf/npf_worker.c npf +file net/npf/npf_socket.c npf file net/npf/npf_os.c npf file net/npf/npf_ifaddr.c npf diff --git a/sys/net/npf/npf.h b/sys/net/npf/npf.h index d9d5f77ea0d51..75f8c8b993dac 100644 --- a/sys/net/npf/npf.h +++ b/sys/net/npf/npf.h @@ -59,6 +59,16 @@ typedef union { uint32_t word32[4]; } npf_addr_t; + +/* + * use a single type for both user id and group id + */ +struct r_id { + uint32_t id[2]; + uint8_t op; +}; + +typedef struct r_id rid_t; typedef uint8_t npf_netmask_t; #define NPF_MAX_NETMASK (128) @@ -372,6 +382,19 @@ typedef enum { NPF_STATS_COUNT } npf_stats_t; +/* unary and binary operators */ +enum { + NPF_OP_NONE, + NPF_OP_EQ, + NPF_OP_NE, + NPF_OP_LE, + NPF_OP_LT, + NPF_OP_GE, + NPF_OP_GT, + NPF_OP_XRG, + NPF_OP_IRG +}; + #define NPF_STATS_SIZE (sizeof(uint64_t) * NPF_STATS_COUNT) #endif /* _NPF_NET_H_ */ diff --git a/sys/net/npf/npf_ctl.c b/sys/net/npf/npf_ctl.c index 708846826694b..ca8399d943837 100644 --- a/sys/net/npf/npf_ctl.c +++ b/sys/net/npf/npf_ctl.c @@ -378,6 +378,16 @@ npf_mk_singlerule(npf_t *npf, const nvlist_t *req, nvlist_t *resp, npf_rule_setcode(rl, type, bc, clen); } + /* user and group ids filt option if set */ + if (nvlist_exists_number_array(req, "r_user")) { + npf_rule_setrid(req, rl, "r_user"); + } + + if (nvlist_exists_number_array(req, "r_group")) { + npf_rule_setrid(req, rl, "r_group"); + } + + *rlret = rl; return 0; err: diff --git a/sys/net/npf/npf_handler.c b/sys/net/npf/npf_handler.c index 279614490ce2a..1a1e67f31fc62 100644 --- a/sys/net/npf/npf_handler.c +++ b/sys/net/npf/npf_handler.c @@ -148,7 +148,7 @@ npfk_packet_handler(npf_t *npf, struct mbuf **mp, ifnet_t *ifp, int di) npf_conn_t *con; npf_rule_t *rl; npf_rproc_t *rp; - int error, decision, flags; + int error, decision, flags, id_match; npf_match_info_t mi; bool mff; @@ -239,11 +239,20 @@ npfk_packet_handler(npf_t *npf, struct mbuf **mp, ifnet_t *ifp, int di) KASSERT(rp == NULL); rp = npf_rule_getrproc(rl); + /* check for matching process uid/gid before concluding */ + id_match = npf_rule_match_rid(rl, &npc, di); + /* Conclude with the rule and release the lock. */ error = npf_rule_conclude(rl, &mi); npf_config_read_exit(npf, slock); - if (error) { + /* reverse between pass and block conditions */ + if (id_match != -1 && !id_match) { + error = npf_rule_reverse(&npc, &mi, error); + } + + /* reject packets whose addr-port pair matches no sockets */ + if (id_match == ENOTCONN || error) { npf_stats_inc(npf, NPF_STAT_BLOCK_RULESET); goto block; } diff --git a/sys/net/npf/npf_impl.h b/sys/net/npf/npf_impl.h index 921b11f939a66..201255f198e9d 100644 --- a/sys/net/npf/npf_impl.h +++ b/sys/net/npf/npf_impl.h @@ -95,6 +95,7 @@ typedef struct npf_conndb npf_conndb_t; typedef struct npf_table npf_table_t; typedef struct npf_tableset npf_tableset_t; typedef struct npf_algset npf_algset_t; +typedef uint32_t (*get_rid_t)(kauth_cred_t); #ifdef __NetBSD__ typedef void ebr_t; @@ -428,6 +429,7 @@ void npf_ruleset_gc(npf_ruleset_t *); npf_rule_t * npf_ruleset_inspect(npf_cache_t *, const npf_ruleset_t *, const int, const int); int npf_rule_conclude(const npf_rule_t *, npf_match_info_t *); +int npf_rule_reverse(npf_cache_t *, npf_match_info_t *, int); /* Rule interface. */ npf_rule_t * npf_rule_alloc(npf_t *, const nvlist_t *); @@ -439,6 +441,10 @@ npf_natpolicy_t *npf_rule_getnat(const npf_rule_t *); void npf_rule_setnat(npf_rule_t *, npf_natpolicy_t *); npf_rproc_t * npf_rule_getrproc(const npf_rule_t *); +int npf_socket_lookup_rid(npf_cache_t *, get_rid_t, uint32_t *, int); +void npf_rule_setrid(const nvlist_t *, npf_rule_t *, const char *); +int npf_rule_match_rid(npf_rule_t *, npf_cache_t *, int); + void npf_ext_init(npf_t *); void npf_ext_fini(npf_t *); int npf_ext_construct(npf_t *, const char *, @@ -470,6 +476,8 @@ void npf_state_tcp_sysinit(npf_t *); void npf_state_tcp_sysfini(npf_t *); bool npf_state_tcp(npf_cache_t *, npf_state_t *, npf_flow_t); int npf_state_tcp_timeout(npf_t *, const npf_state_t *); +/* uid/gid process matching */ +int npf_match_rid(rid_t *, uint32_t); /* Portmap. */ void npf_portmap_sysinit(void); diff --git a/sys/net/npf/npf_ruleset.c b/sys/net/npf/npf_ruleset.c index a9fb858c54cbe..f92b9b37b4dac 100644 --- a/sys/net/npf/npf_ruleset.c +++ b/sys/net/npf/npf_ruleset.c @@ -44,6 +44,7 @@ __KERNEL_RCSID(0, "$NetBSD: npf_ruleset.c,v 1.52 2023/08/08 16:10:41 kardel Exp #include #include #include +#include #include #include @@ -118,6 +119,9 @@ struct npf_rule { LIST_ENTRY(npf_rule) r_aentry; nvlist_t * r_info; size_t r_info_len; + + rid_t uid; + rid_t gid; }; #define SKIPTO_ADJ_FLAG (1U << 31) @@ -655,9 +659,19 @@ npf_rule_alloc(npf_t *npf, const nvlist_t *rule) } memcpy(rl->r_key, key, len); } + + /* no gid/uid set yet */ + rl->gid.op = rl->uid.op = NPF_OP_NONE; return rl; } +static void +npf_rid_export(nvlist_t *rl, struct r_id rid, const char *name) +{ + uint64_t uid_element[3] = { rid.id[0], rid.id[1], rid.op }; + nvlist_add_number_array(rl, name, uid_element, 3); +} + static nvlist_t * npf_rule_export(npf_t *npf, const npf_rule_t *rl) { @@ -691,6 +705,12 @@ npf_rule_export(npf_t *npf, const npf_rule_t *rl) if (rl->r_info) { nvlist_add_binary(rule, "info", rl->r_info, rl->r_info_len); } + if (rl->uid.op != NPF_OP_NONE) { + npf_rid_export(rule, rl->uid, "r_user"); + } + if (rl->gid.op != NPF_OP_NONE) { + npf_rid_export(rule, rl->gid, "r_group"); + } if ((rp = npf_rule_getrproc(rl)) != NULL) { const char *rname = npf_rproc_getname(rp); nvlist_add_string(rule, "rproc", rname); @@ -716,6 +736,24 @@ npf_rule_setcode(npf_rule_t *rl, const int type, void *code, size_t size) rl->r_jcode = npf_bpf_compile(code, size); } +void +npf_rule_setrid(const nvlist_t *req, npf_rule_t *rl, const char *name) +{ + size_t nitems; + rid_t id; + const uint64_t *rid = nvlist_get_number_array(req, name, &nitems); + KASSERT(nitems == 3); + + id.id[0] = (uint32_t)rid[0]; + id.id[1] = (uint32_t)rid[1]; + id.op = (uint8_t)rid[2]; + + if (!strcmp(name, "r_user")) + rl->uid = id; + else if (!strcmp(name, "r_group")) + rl->gid = id; +} + /* * npf_rule_setrproc: assign a rule procedure and hold a reference on it. */ @@ -929,6 +967,80 @@ npf_ruleset_inspect(npf_cache_t *npc, const npf_ruleset_t *rlset, return final_rl; } +/* + * just exchange the flag attributes for pass/block for the diff protocols. + * for passing, we set the STATEFULNESS for TCP connection establishment + * if ret == 0, it is for a pass to be changed to block + * non-zero ret indicates a block to pass + * when we change to block, we assume the default RST rerturn for TCP + * when we change to pass, we ensure no bit field for RST for tcp and ICMP for udp + * finally change the ret condition too + */ +int +npf_rule_reverse(npf_cache_t *npc, npf_match_info_t *mi, int ret) +{ + KASSERT(npf_iscached(npc, NPC_LAYER4)); + switch(npc->npc_proto) { + case IPPROTO_TCP: + if (ret == 0) /* switch pass to block */ { + mi->mi_retfl &= !(NPF_RULE_PASS | NPF_RULE_STATEFUL | + NPF_RULE_GSTATEFUL); + mi->mi_retfl |= NPF_RULE_RETRST; + } + else /* block to pass */ { + mi->mi_retfl &= !(NPF_RULE_RETRST); + mi->mi_retfl |= (NPF_RULE_PASS | NPF_RULE_STATEFUL | + NPF_RULE_GSTATEFUL); + } + break; + case IPPROTO_UDP: + if (ret == 0) /* pass to block */ { + mi->mi_retfl &= !(NPF_RULE_PASS); + mi->mi_retfl |= NPF_RULE_RETICMP; + } + else /* block to pass */ { + mi->mi_retfl &= !(NPF_RULE_RETICMP); + mi->mi_retfl |= NPF_RULE_PASS; + } + break; + } + + return (ret == 0) ? ENETUNREACH : 0; +} + +/* only perform uid/gid checks when set */ +int +npf_rule_match_rid(npf_rule_t *rl, npf_cache_t *npc, int dir) +{ + uint32_t sock_gid, sock_uid; + bool uid_matched = false, gid_matched = false; + + if (rl->gid.op == NPF_OP_NONE && rl->uid.op == NPF_OP_NONE) + return -1; /* quickly return if packet has nothing to do with rids */ + + KASSERT(npf_iscached(npc, NPC_IP46)); + KASSERT(npf_iscached(npc, NPC_LAYER4)); + + if (rl->gid.op != NPF_OP_NONE) { + if (npf_socket_lookup_rid(npc, kauth_cred_getegid, &sock_gid, dir) == -1) + return ENOTCONN; + + gid_matched = npf_match_rid(&rl->gid, sock_gid); + } + if (rl->uid.op != NPF_OP_NONE) { + if (npf_socket_lookup_rid(npc, kauth_cred_geteuid, &sock_uid, dir) == -1) + return ENOTCONN; + + uid_matched = npf_match_rid(&rl->uid, sock_uid); + } + + /* if both uid and gid are set on rule, both must be matching to agree */ + if (rl->gid.op && rl->uid.op) + return gid_matched && uid_matched; + else + return gid_matched || uid_matched; +} + /* * npf_rule_conclude: return decision and the flags for conclusion. * diff --git a/sys/net/npf/npf_socket.c b/sys/net/npf/npf_socket.c new file mode 100644 index 0000000000000..b5cae979c100f --- /dev/null +++ b/sys/net/npf/npf_socket.c @@ -0,0 +1,221 @@ +/*- + * Copyright (c) 2025 Emmanuel Nyarko + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#ifdef INET6 +#include +#include +#ifdef __NetBSD__ +#include +#endif /* __NetBSD__ */ +#endif /* INET6 */ + +#include "npf_impl.h" + +extern struct inpcbtable tcbtable; /* head of queue of active tcpcb's */ +extern struct inpcbtable udbtable; + +static struct socket * npf_ip6_socket(npf_cache_t *, int); +static struct socket * npf_ip_socket(npf_cache_t *, int); +static int npf_match(uint8_t, uint32_t, uint32_t, uint32_t); + +/* +* NPF process socket module +*/ + +int +npf_match_rid(rid_t *rid, uint32_t uid_lookup) +{ + return npf_match(rid->op, rid->id[0], rid->id[1], uid_lookup); +} + +static int +npf_match(uint8_t op, uint32_t rid1, uint32_t rid2, uint32_t id_lp) +{ + switch (op) { + case NPF_OP_IRG: + return id_lp > rid1 && id_lp < rid2; + case NPF_OP_XRG: + return id_lp < rid1 || id_lp > rid2; + case NPF_OP_EQ: + return id_lp == rid1; + case NPF_OP_NE: + return id_lp != rid1; + case NPF_OP_LT: + return id_lp < rid1; + case NPF_OP_LE: + return id_lp <= rid1; + case NPF_OP_GT: + return id_lp > rid1; + case NPF_OP_GE: + return id_lp >= rid1; + } + return 0; /* never reached */ +} + +int +npf_socket_lookup_rid(npf_cache_t *npc, get_rid_t get_rid, uint32_t *rid, int dir) +{ + struct socket *so = NULL; + + KASSERT(npf_iscached(npc, NPC_IP46)); + + if (npf_iscached(npc, NPC_IP4)) { + so = npf_ip_socket(npc, dir); + } else if (npf_iscached(npc, NPC_IP6)) { + so = npf_ip6_socket(npc, dir); + } + + if (so == NULL || so->so_cred == NULL) + return -1; + + *rid = get_rid(so->so_cred); + return 0; +} + +static struct socket * +npf_ip_socket(npf_cache_t *npc, int dir) +{ + struct inpcbtable *tb = NULL; + struct in_addr saddr, daddr; + uint16_t sport, dport; + struct socket *so = NULL; + struct inpcb *inp = NULL; + +#define in_pcbhashlookup(tbl, saddr, sport, daddr, dport) \ + inpcb_lookup(tbl, saddr, sport, daddr, dport, NULL) +#define in_pcblookup_listen(tbl, addr, port) \ + inpcb_lookup_bound(tbl, addr, port) + + KASSERT(npf_iscached(npc, NPC_LAYER4)); + KASSERT(npf_iscached(npc, NPC_IP4)); + + struct tcphdr *tcp = npc->npc_l4.tcp; + struct udphdr *udp = npc->npc_l4.udp; + struct ip *ip = npc->npc_ip.v4; + + switch(npc->npc_proto) { + case IPPROTO_TCP: + sport = tcp->th_sport; + dport = tcp->th_dport; + tb = &tcbtable; + break; + case IPPROTO_UDP: + sport = udp->uh_sport; + dport = udp->uh_dport; + tb = &udbtable; + break; + default: + return NULL; + } + + if (dir == PFIL_IN) { + saddr = ip->ip_src; + daddr = ip->ip_dst; + } else { + uint16_t p_temp; + /* swap ports and addresses */ + p_temp = sport; + sport = dport; + dport = p_temp; + saddr = ip->ip_dst; + daddr = ip->ip_src; + } + + inp = in_pcbhashlookup(tb, saddr, sport, daddr, dport); + if (inp == NULL) { + inp = in_pcblookup_listen(tb, daddr, dport); + if (inp == NULL) { + return NULL; + } + } + + so = inp->inp_socket; + return so; +} + +static struct socket * +npf_ip6_socket(npf_cache_t *npc, int dir) +{ + struct inpcbtable *tb = NULL; + const struct in6_addr *s6addr, *d6addr; + uint16_t sport, dport; + struct inpcb *in6p = NULL; + struct socket *so = NULL; + +#define in6_pcbhashlookup(tbl, saddr, sport, daddr, dport) \ + in6pcb_lookup(tbl, saddr, sport, daddr, dport, 0, NULL) + +#define in6_pcblookup_listen(tbl, addr, port) \ + in6pcb_lookup_bound(tbl, addr, port, 0) + + KASSERT(npf_iscached(npc, NPC_LAYER4)); + KASSERT(npf_iscached(npc, NPC_IP6)); + + struct tcphdr *tcp = npc->npc_l4.tcp; + struct udphdr *udp = npc->npc_l4.udp; + struct ip6_hdr *ip6 = npc->npc_ip.v6; + + switch(npc->npc_proto) { + case IPPROTO_TCP: + sport = tcp->th_sport; + dport = tcp->th_dport; + tb = &tcbtable; + break; + case IPPROTO_UDP: + sport = udp->uh_sport; + dport = udp->uh_dport; + tb = &udbtable; + break; + default: + return NULL; + } + + if (dir == PFIL_IN) { + s6addr = &ip6->ip6_src; + d6addr = &ip6->ip6_dst; + } else { + uint16_t p_temp; + /* swap ports and addresses */ + p_temp = sport; + sport = dport; + dport = p_temp; + s6addr = &ip6->ip6_dst; + d6addr = &ip6->ip6_src; + } + in6p = in6_pcbhashlookup(tb, s6addr, sport, d6addr, + dport); + if (in6p == NULL) { + in6p = in6_pcblookup_listen(tb, d6addr, dport); + if (in6p == NULL) + return NULL; + } + so = in6p->inp_socket; + return so; +} diff --git a/sys/rump/net/lib/libnpf/Makefile b/sys/rump/net/lib/libnpf/Makefile index dfea0c1f9390b..2c23801b48704 100644 --- a/sys/rump/net/lib/libnpf/Makefile +++ b/sys/rump/net/lib/libnpf/Makefile @@ -17,7 +17,7 @@ SRCS= npf.c npf_alg.c npf_conf.c npf_ctl.c npf_handler.c SRCS+= npf_bpf.c npf_if.c npf_inet.c npf_mbuf.c npf_nat.c SRCS+= npf_params.c npf_ruleset.c npf_rproc.c SRCS+= npf_conn.c npf_conndb.c npf_connkey.c npf_portmap.c -SRCS+= npf_state.c npf_state_tcp.c npf_tableset.c +SRCS+= npf_state.c npf_state_tcp.c npf_tableset.c npf_socket.c SRCS+= lpm.c npf_sendpkt.c npf_worker.c npf_os.c npf_ifaddr.c SRCS+= nvlist.c nvpair.c nv_kern_netbsd.c dnvlist.c diff --git a/usr.sbin/npf/npfctl/npf.conf.5 b/usr.sbin/npf/npfctl/npf.conf.5 index 63a7f1d94b6f4..f8413b29e2298 100644 --- a/usr.sbin/npf/npfctl/npf.conf.5 +++ b/usr.sbin/npf/npfctl/npf.conf.5 @@ -311,6 +311,45 @@ syntax, for example: .Pp Fragments are not selectable since NPF always reassembles packets before further processing. +.Ss User/group ID filtering +.Pp +NPF allows filtering by user or group identity. Packet filtering by user or group +controls data packet flows based on the user or group identity of the process +that generated the traffic, or is waiting to receive traffic, +rather than just traditional parameters like IP address, port number, and protocol. +.Pp +There are many situations where this is useful: +.Bl -bullet -hang +.It Finer-grained access control +One can allow specific destinations to be accessed only by certain users +or groups. +.It Application level security +Two processes are using a specific port, but only one should be allowed to +access packets originating from a particular host. +.It Improves isolation in multi-tenant systems +Prevent an untrusted user from making any network connections +.It Security hardening and containment +A user application that has been exploited can be prevented from making +network connections to a command-and-control server. +.It Compliance and policy enforcement +Can restrict access to particular networks to network administrators only. +.El +This filtering process can be achieved by passing the user or group ID on the rule. +.Pp +.Dl pass out from all user jack group < 1000 +.Pp +The above rule only allows sockets of processes owned by user jack +and belonging to a group with an id value of less than 1000. +.Pp +.Dl block in from all user > 100 group wheel +.Pp +The above rule prevents all listening sockets bound by processes owned by any user +with the id value greater than 100 and belonging to the wheel group. +.Pp +A rule can have either a user ID or group ID set. If both are set, both must +agree to be a match to the socket involved in communication. +Numbers or names can be used for the identification of the user or group as they +still resolve to a numeric ID of the user or group. .Ss Stateful NPF supports stateful packet inspection which can be used to bypass unnecessary rule processing as well as to complement NAT. @@ -620,11 +659,19 @@ proto-opts = "flags" tcp-flags [ "/" tcp-flag-mask ] | "icmp-type" type [ "code" icmp-code ] proto = "proto" protocol [ proto-opts ] -filt-opts = "from" filt-addr [ port-opts ] "to" filt-addr [ port-opts ] +filt-opts = "from" filt-addr [ port-opts ] "to" filt-addr [ port-opts ] user_id group_id filt-addr = [ "!" ] [ interface | addr-mask | table-id | "any" ] port-opts = "port" ( port-num | port-from "-" port-to | var-name ) addr-mask = addr [ "/" mask ] + +user_id = "user" id_items +group_id = "group" id_items + +id_items = [id] | [op_unary id] | [id op_binary id] + +op_unary = ["="] | ["!="] | ["<="] | [">="] | [">"] | ["<"] +op_binary = ["<>"] | ["><"] .Ed .\" ----- .Sh FILES diff --git a/usr.sbin/npf/npfctl/npf_build.c b/usr.sbin/npf/npfctl/npf_build.c index 2a171fed3dd75..55c7b81e0d5ec 100644 --- a/usr.sbin/npf/npfctl/npf_build.c +++ b/usr.sbin/npf/npfctl/npf_build.c @@ -715,6 +715,14 @@ npfctl_build_rule(uint32_t attr, const char *ifname, sa_family_t family, npfctl_build_code(rl, family, popts, fopts); } + if (fopts->uid.op != NPF_OP_NONE) { + npf_rule_setrid(rl, fopts->uid, "r_user"); + } + + if (fopts->gid.op != NPF_OP_NONE) { + npf_rule_setrid(rl, fopts->gid, "r_group"); + } + if (rproc) { npf_rule_setproc(rl, rproc); } diff --git a/usr.sbin/npf/npfctl/npf_data.c b/usr.sbin/npf/npfctl/npf_data.c index e21bedccc40cb..06abf7c058dff 100644 --- a/usr.sbin/npf/npfctl/npf_data.c +++ b/usr.sbin/npf/npfctl/npf_data.c @@ -52,6 +52,8 @@ __RCSID("$NetBSD: npf_data.c,v 1.30 2019/01/19 21:19:32 rmind Exp $"); #include #include #include +#include +#include #include "npfctl.h" @@ -267,6 +269,50 @@ npfctl_parse_table_id(const char *name) return npfvar_create_element(NPFVAR_TABLE, &tid, sizeof(u_int)); } +int +npfctl_parse_user(const char *user, uint32_t *uid) +{ + if (!strcmp(user, "unknown")) + *uid = UID_MAX; + else { + struct passwd *pw; + + if ((pw = getpwnam(user)) == NULL) { + return -1; + } + *uid = pw->pw_uid; + } + return 0; +} + +int +npfctl_parse_group(const char *group, uint32_t *gid) +{ + if (!strcmp(group, "unknown")) + *gid = GID_MAX; + else { + struct group *grp; + + if ((grp = getgrnam(group)) == NULL) { + return -1; + } + *gid = grp->gr_gid; + } + return 0; +} + +/* + * this function is called for both gid and uid init in parser + * both uid and gid are both uint32_t + */ +void +npfctl_init_rid(rid_t *rid, uint32_t id1, uint32_t id2, uint8_t op) +{ + rid->id[0] = id1; + rid->id[1] = id2; + rid->op = op; +} + /* * npfctl_parse_port_range: create a port-range variable. Note that the * passed port numbers should be in host byte order. diff --git a/usr.sbin/npf/npfctl/npf_parse.y b/usr.sbin/npf/npfctl/npf_parse.y index 169c74beb28cd..3b0e54326e730 100644 --- a/usr.sbin/npf/npfctl/npf_parse.y +++ b/usr.sbin/npf/npfctl/npf_parse.y @@ -169,6 +169,7 @@ yyerror(const char *fmt, ...) %token TO %token TREE %token TYPE +%token USER %token ICMP %token ICMP6 @@ -189,7 +190,8 @@ yyerror(const char *fmt, ...) %type block_or_pass rule_dir group_dir block_opts %type maybe_not opt_stateful icmp_type table_type %type map_sd map_algo map_flags map_type -%type param_val +%type param_val op_unary op_binary +%type uid gid %type static_ifaddrs filt_addr_element %type filt_port filt_port_list port_range icmp_type_and_code %type filt_addr addr_and_mask tcp_flags tcp_flags_and_mask @@ -197,6 +199,8 @@ yyerror(const char *fmt, ...) %type element list_elems list_trail list value filt_addr_list %type opt_proto proto proto_elems %type mapseg +%type uids uid_item uid_list user_id +%type gids gid_item gid_list group_id %type filt_opts all_or_filt_opts %type rawproto %type group_opts @@ -204,12 +208,15 @@ yyerror(const char *fmt, ...) %union { char * str; unsigned long num; + uint32_t rid; double fpnum; npfvar_t * var; addr_port_t addrport; filt_opts_t filtopts; opt_proto_t optproto; rule_group_t rulegroup; + struct r_id uid; + struct r_id gid; } %% @@ -678,7 +685,7 @@ opt_proto ; all_or_filt_opts - : ALL + : ALL user_id group_id { $$.fo_finvert = false; $$.fo_from.ap_netaddr = NULL; @@ -686,6 +693,8 @@ all_or_filt_opts $$.fo_tinvert = false; $$.fo_to.ap_netaddr = NULL; $$.fo_to.ap_portrange = NULL; + $$.uid = $2; + $$.gid = $3; } | filt_opts { $$ = $1; } ; @@ -710,6 +719,7 @@ block_opts filt_opts : FROM maybe_not filt_addr filt_port TO maybe_not filt_addr filt_port + user_id group_id { $$.fo_finvert = $2; $$.fo_from.ap_netaddr = $3; @@ -717,8 +727,10 @@ filt_opts $$.fo_tinvert = $6; $$.fo_to.ap_netaddr = $7; $$.fo_to.ap_portrange = $8; + $$.uid = $9; + $$.gid = $10; } - | FROM maybe_not filt_addr filt_port + | FROM maybe_not filt_addr filt_port user_id group_id { $$.fo_finvert = $2; $$.fo_from.ap_netaddr = $3; @@ -726,8 +738,10 @@ filt_opts $$.fo_tinvert = false; $$.fo_to.ap_netaddr = NULL; $$.fo_to.ap_portrange = NULL; + $$.uid = $5; + $$.gid = $6; } - | TO maybe_not filt_addr filt_port + | TO maybe_not filt_addr filt_port user_id group_id { $$.fo_finvert = false; $$.fo_from.ap_netaddr = NULL; @@ -735,6 +749,8 @@ filt_opts $$.fo_tinvert = $2; $$.fo_to.ap_netaddr = $3; $$.fo_to.ap_portrange = $4; + $$.uid = $5; + $$.gid = $6; } ; @@ -993,6 +1009,108 @@ ifref } ; +user_id + : /* empty */ { $$.op = NPF_OP_NONE; } + | USER uids { $$ = $2; } + ; + +uids + : uid_item { $$ = $1; } + ; + +uid_item + : uid + { + npfctl_init_rid(&$$, $1, $1, NPF_OP_EQ); + } + | op_unary uid + { + npfctl_init_rid(&$$, $2, $2, $1); + } + | uid op_binary uid + { + npfctl_init_rid(&$$, $1, $3, $2); + } + ; + +uid + : NUM + { + if ($1 >= UID_MAX) { + yyerror("illegal uid value %lu", $1); + } + $$ = $1; + } + | IDENTIFIER + { + if (npfctl_parse_user($1, &$$) == -1) { + yyerror("unknown user %s", $1); + } + } + | VAR_ID + { + npf_var_rid($1, npfctl_parse_user, &$$, "user"); + } + ; + +group_id + : /* empty */ { $$.op = NPF_OP_NONE; } + | GROUP gids { $$ = $2; } + ; + +gids + : gid_item { $$ = $1; } + ; + +gid_item + : gid + { + npfctl_init_rid(&$$, $1, $1, NPF_OP_EQ); + } + | op_unary gid + { + npfctl_init_rid(&$$, $2, $2, $1); + } + | gid op_binary gid + { + npfctl_init_rid(&$$, $1, $3, $2); + } + ; + + gid + : NUM + { + if ($1 >= GID_MAX) { + yyerror("illegal gid value %lu", $1); + } + $$ = $1; + } + | IDENTIFIER + { + if (npfctl_parse_group($1, &$$) == -1) { + yyerror("unknown group %s", $1); + } + } + | VAR_ID + { + npf_var_rid($1, npfctl_parse_group, &$$, "group"); + } + ; + +op_unary + : EQ { $$ = NPF_OP_EQ; } + | EXCL_MARK EQ { $$ = NPF_OP_NE; } + | LT EQ { $$ = NPF_OP_LE; } + | LT { $$ = NPF_OP_LT; } + | GT EQ { $$ = NPF_OP_GE; } + | GT { $$ = NPF_OP_GT; } + ; + +op_binary + : XRG { $$ = NPF_OP_XRG; } + | IRG { $$ = NPF_OP_IRG; } + ; + number : HEX { $$ = $1; } | NUM { $$ = $1; } diff --git a/usr.sbin/npf/npfctl/npf_scan.l b/usr.sbin/npf/npfctl/npf_scan.l index 7dd6b70c8234e..caa0d9f149bb3 100644 --- a/usr.sbin/npf/npfctl/npf_scan.l +++ b/usr.sbin/npf/npfctl/npf_scan.l @@ -173,7 +173,7 @@ flags return FLAGS; icmp-type return ICMPTYPE; code return CODE; any return ANY; - +user return USER; "/" return SLASH; "{" return CURLY_OPEN; "}" return CURLY_CLOSE; @@ -182,6 +182,10 @@ any return ANY; "," return COMMA; "=" return EQ; "!" return EXCL_MARK; +"<" return LT; +">" return GT; +"<>" return XRG; +"><" return IRG; "0x"{HEXDIG} { char *endp, *buf = ecalloc(1, yyleng + 1); diff --git a/usr.sbin/npf/npfctl/npf_show.c b/usr.sbin/npf/npfctl/npf_show.c index 2bb701239ecac..aca445c9620dd 100644 --- a/usr.sbin/npf/npfctl/npf_show.c +++ b/usr.sbin/npf/npfctl/npf_show.c @@ -510,12 +510,39 @@ npfctl_print_filter(npf_conf_info_t *ctx, nl_rule_t *rl) return seenf; } +static char * +print_guid(char *buf, struct r_id id, int size) +{ + if (id.op == NPF_OP_XRG) { + snprintf(buf, size, "%u <> %u", id.id[0], id.id[1]); + } else if (id.op == NPF_OP_IRG) { + snprintf(buf, size, "%u >< %u", id.id[0], id.id[1]); + } else if (id.op == NPF_OP_EQ ) { + snprintf(buf, size, "%u", id.id[0]); + } else if (id.op == NPF_OP_NE) { + snprintf(buf, size, "!= %u", id.id[0]); + } else if (id.op == NPF_OP_LE) { + snprintf(buf, size, "<= %u", id.id[0]); + } else if (id.op == NPF_OP_LT) { + snprintf(buf, size, "< %u", id.id[0]); + } else if (id.op == NPF_OP_GE) { + snprintf(buf, size, ">= %u", id.id[0]); + } else if (id.op == NPF_OP_GT) { + snprintf(buf, size, "> %u", id.id[0]); + } else { + return NULL; + } + return buf; +} +#define BUF_SIZE 20 static void npfctl_print_rule(npf_conf_info_t *ctx, nl_rule_t *rl, unsigned level) { const uint32_t attr = npf_rule_getattr(rl); const char *rproc, *ifname, *name; bool dyn_ruleset; + struct r_id rid; + char buf[BUF_SIZE]; /* Rule attributes/flags. */ for (unsigned i = 0; i < __arraycount(attr_keyword_map); i++) { @@ -548,6 +575,14 @@ npfctl_print_rule(npf_conf_info_t *ctx, nl_rule_t *rl, unsigned level) ctx->fpos += fprintf(ctx->fp, "all "); } + if (!npf_rule_getrid(&rid, rl, "r_user")) { + ctx->fpos += fprintf(ctx->fp, "user %s ", print_guid(buf, rid, BUF_SIZE)); + } + + if (!npf_rule_getrid(&rid, rl, "r_group")) { + ctx->fpos += fprintf(ctx->fp, "group %s ", print_guid(buf, rid, BUF_SIZE)); + } + /* Rule procedure. */ if ((rproc = npf_rule_getproc(rl)) != NULL) { ctx->fpos += fprintf(ctx->fp, "apply \"%s\" ", rproc); diff --git a/usr.sbin/npf/npfctl/npf_var.c b/usr.sbin/npf/npfctl/npf_var.c index 57858c3ce1191..e19b710d272eb 100644 --- a/usr.sbin/npf/npfctl/npf_var.c +++ b/usr.sbin/npf/npfctl/npf_var.c @@ -183,6 +183,45 @@ npfvar_expand_string(const npfvar_t *vp) return npfvar_get_data(vp, NPFVAR_STRING, 0); } +uint32_t +npfvar_expand_number(const npfvar_t *vp) +{ + uint32_t *number; + if (npfvar_get_count(vp) != 1) { + yyerror("variable '%s' has multiple elements", vp->v_key); + } + number = (uint32_t *)npfvar_get_data(vp, NPFVAR_NUM, 0); + return *number; +} + +void +npf_var_rid(char *var_id, rid_parser parser, uint32_t *rid, const char *ridt) +{ + npfvar_t *vp = npfvar_lookup(var_id); + int type = npfvar_get_type(vp, 0); + char *rid_type; + + switch (type) { + case NPFVAR_IDENTIFIER: + case NPFVAR_STRING: + rid_type = npfvar_expand_string(vp); + if (parser(rid_type, rid) == -1) { + yyerror("unknown %s %s", var_id, ridt); + } + break; + case NPFVAR_NUM: + *rid = npfvar_expand_number(vp); + break; + case -1: + yyerror("undefined variable '%s'", var_id); + break; + default: + yyerror("wrong variable '%s' type '%s' for %s id", + var_id, npfvar_type(type), ridt); + break; + } +} + size_t npfvar_get_count(const npfvar_t *vp) { diff --git a/usr.sbin/npf/npfctl/npf_var.h b/usr.sbin/npf/npfctl/npf_var.h index cf92e7d427358..35fcec67ff7fc 100644 --- a/usr.sbin/npf/npfctl/npf_var.h +++ b/usr.sbin/npf/npfctl/npf_var.h @@ -69,6 +69,7 @@ static const char *npfvar_types[ ] = { struct npfvar; typedef struct npfvar npfvar_t; +typedef int (*rid_parser)(const char *, uint32_t *); npfvar_t * npfvar_create(void); npfvar_t * npfvar_create_element(unsigned, const void *, size_t); @@ -82,7 +83,9 @@ void npfvar_destroy(npfvar_t *); char * npfvar_expand_string(const npfvar_t *); size_t npfvar_get_count(const npfvar_t *); +uint32_t npfvar_expand_number(const npfvar_t *); int npfvar_get_type(const npfvar_t *, size_t); void * npfvar_get_data(const npfvar_t *, unsigned, size_t); +void npf_var_rid(char *, rid_parser, uint32_t *, const char *); #endif diff --git a/usr.sbin/npf/npfctl/npfctl.h b/usr.sbin/npf/npfctl/npfctl.h index 4a17517488079..f106c91dc60ed 100644 --- a/usr.sbin/npf/npfctl/npfctl.h +++ b/usr.sbin/npf/npfctl/npfctl.h @@ -78,6 +78,8 @@ typedef struct filt_opts { addr_port_t fo_to; bool fo_finvert; bool fo_tinvert; + struct r_id uid; + struct r_id gid; } filt_opts_t; typedef struct opt_proto { @@ -139,6 +141,10 @@ npfvar_t * npfctl_parse_port_range(in_port_t, in_port_t); npfvar_t * npfctl_parse_port_range_variable(const char *, npfvar_t *); npfvar_t * npfctl_parse_fam_addr_mask(const char *, const char *, unsigned long *); + +int npfctl_parse_user(const char *, uint32_t *); +int npfctl_parse_group(const char *, uint32_t *); +void npfctl_init_rid(rid_t *, uint32_t, uint32_t, uint8_t); bool npfctl_parse_cidr(char *, fam_addr_mask_t *, int *); uint16_t npfctl_npt66_calcadj(npf_netmask_t, const npf_addr_t *, const npf_addr_t *); diff --git a/usr.sbin/npf/npftest/Makefile b/usr.sbin/npf/npftest/Makefile index 83cfc6f7d8b9c..e86220853c789 100644 --- a/usr.sbin/npf/npftest/Makefile +++ b/usr.sbin/npf/npftest/Makefile @@ -18,7 +18,7 @@ DPADD+= ${LIBNPFTEST}/libnpftest.a LDADD+= -L${LIBNPFTEST} -lnpftest LDADD+= -lrump -lrumpvfs_nofifofs -lrumpvfs -lrumpuser -LDADD+= -lrumpnet -lrumpnet_net -lrumpdev_bpf +LDADD+= -lrumpnet -lrumpnet_net -lrumpdev_bpf -lrumpnet_netinet .if ${RUMP_SANITIZE:Uno} != "no" LDADD+= -fsanitize=${RUMP_SANITIZE} diff --git a/usr.sbin/npf/npftest/libnpftest/Makefile b/usr.sbin/npf/npftest/libnpftest/Makefile index 943a459f589b6..4c2aa76de8889 100644 --- a/usr.sbin/npf/npftest/libnpftest/Makefile +++ b/usr.sbin/npf/npftest/libnpftest/Makefile @@ -18,6 +18,7 @@ SRCS+= npf_gc_test.c SRCS+= npf_state_test.c SRCS+= npf_rule_test.c SRCS+= npf_nat_test.c +SRCS+= npf_rid_test.c SRCS+= npf_perf_test.c diff --git a/usr.sbin/npf/npftest/libnpftest/npf_rid_test.c b/usr.sbin/npf/npftest/libnpftest/npf_rid_test.c new file mode 100644 index 0000000000000..df0da0ef061b4 --- /dev/null +++ b/usr.sbin/npf/npftest/libnpftest/npf_rid_test.c @@ -0,0 +1,273 @@ +/* +* NPF socket User/group id tests. +* +* Public Domain. +*/ + +#ifdef _KERNEL +#include +#include +__KERNEL_RCSID(0, "$NetBSD: npf_rid_test.c,v 1.22 2025/05/31 09:49:01 joe Exp $"); +#endif + +#include "npf_impl.h" +#include "npf_test.h" + +#include +#include +#include +#include +#include +#include + +#define RESULT_PASS 0 +#define RESULT_BLOCK ENETUNREACH + +/* this port number suitable for testing */ +#define REMOTE_PORT 65500 +#define LOCAL_PORT 65000 +#define LOCAL_IP "127.0.0.1" +#define REMOTE_IP LOCAL_IP + +static const struct test_case { + int af; + const char * src; + uint16_t sport; + const char * dst; + uint16_t dport; + uint32_t uid; + uint32_t gid; + const char * ifname; + int di; + int ret; + int stateful_ret; +} test_cases[] = { + { + /* pass in final from $local_ip4 user $Kojo = 1001 group $wheel = 20 */ + .af = AF_INET, + .src = "10.1.1.4", .sport = 9000, + .dst = LOCAL_IP, .dport = LOCAL_PORT, + .ifname = IFNAME_EXT, .di = PFIL_IN, + .uid = 1001, .gid = 20, /* matches so pass it */ + .ret = RESULT_PASS, .stateful_ret = RESULT_PASS + }, + { + /* connect on different UID and block */ + .af = AF_INET, + .src = "10.1.1.4", .sport = 9000, + .dst = LOCAL_IP, .dport = LOCAL_PORT, + .ifname = IFNAME_EXT, .di = PFIL_IN, + .uid = 1001, .gid = 10, /* mismatch gid so block it */ + .ret = RESULT_BLOCK, .stateful_ret = RESULT_BLOCK + }, + { + .af = AF_INET, + .src = "10.1.1.4", .sport = 9000, + .dst = LOCAL_IP, .dport = LOCAL_PORT, + .ifname = IFNAME_EXT, .di = PFIL_IN, + .uid = 100, .gid = 20, /* mismatch uid so block it */ + .ret = RESULT_BLOCK, .stateful_ret = RESULT_BLOCK + }, + + + /* block out final to 127.0.0.1 user > $Kojo( > 1001) group 1 >< $wheel( IRG 1 >< 20) */ + { + .af = AF_INET, + .src = LOCAL_IP, .sport = LOCAL_PORT, + .dst = REMOTE_IP, .dport = REMOTE_PORT, + .ifname = IFNAME_EXT, .di = PFIL_OUT, + .uid = 1005, .gid = 14, /* matches so blocks it */ + .ret = RESULT_BLOCK, .stateful_ret = RESULT_BLOCK + }, + { + .af = AF_INET, + .src = LOCAL_IP, .sport = LOCAL_PORT, + .dst = REMOTE_IP, .dport = REMOTE_PORT, + .ifname = IFNAME_EXT, .di = PFIL_OUT, + .uid = 1005, .gid = 30, /* mismatch gid so pass it */ + .ret = RESULT_PASS, .stateful_ret = RESULT_PASS + }, + { + .af = AF_INET, + .src = LOCAL_IP, .sport = LOCAL_PORT, + .dst = REMOTE_IP, .dport = REMOTE_PORT, + .ifname = IFNAME_EXT, .di = PFIL_OUT, + .uid = 100, .gid = 15, /* mismatch uid so pass it */ + .ret = RESULT_PASS, .stateful_ret = RESULT_PASS + }, + { + .af = AF_INET, + .src = LOCAL_IP, .sport = LOCAL_PORT, + .dst = REMOTE_IP, .dport = REMOTE_PORT, + .ifname = IFNAME_EXT, .di = PFIL_OUT, + .uid = 1010, .gid = 11, /* matches so blocks it */ + .ret = RESULT_BLOCK, .stateful_ret = RESULT_BLOCK + }, +}; + +static int +run_raw_testcase(unsigned i, bool verbose) +{ + const struct test_case *t = &test_cases[i]; + npf_t *npf = npf_getkernctx(); + npf_cache_t *npc; + struct mbuf *m; + npf_rule_t *rl; + int slock, error; + + m = mbuf_get_pkt(t->af, IPPROTO_UDP, t->src, t->dst, t->sport, t->dport); + npc = get_cached_pkt(m, t->ifname); + + slock = npf_config_read_enter(npf); + rl = npf_ruleset_inspect(npc, npf_config_ruleset(npf), t->di, NPF_LAYER_3); + if (rl) { + npf_match_info_t mi; + int id_match; + + id_match = npf_rule_match_rid(rl, npc, t->di); + error = npf_rule_conclude(rl, &mi); + if (verbose) + printf("id match is ...%d\n", id_match); + if (id_match != -1 && !id_match) { + error = npf_rule_reverse(npc, &mi, error); + } + + } else { + error = ENOENT; + } + npf_config_read_exit(npf, slock); + + put_cached_pkt(npc); + return error; +} + +static int +run_handler_testcase(unsigned i) +{ + const struct test_case *t = &test_cases[i]; + ifnet_t *ifp = npf_test_getif(t->ifname); + npf_t *npf = npf_getkernctx(); + struct mbuf *m; + int error; + + m = mbuf_get_pkt(t->af, IPPROTO_UDP, t->src, t->dst, t->sport, t->dport); + error = npfk_packet_handler(npf, &m, ifp, t->di); + if (m) { + m_freem(m); + } + return error; +} + +/* + * we create our specific server socket here which listens on + * loopback address and port 65000. easier to test pcb lookup here since + * it will be loaded into the protocol table. + */ +static struct socket * +test_socket(int dir, uid_t uid, gid_t gid) +{ + struct sockaddr_in server; + struct lwp *cur = curlwp; + void *p, *rp; + + memset(&Server, 0, sizeof(server)); + + server.sin_len = sizeof(server); + server.sin_family = AF_INET; + p = &server.sin_addr.s_addr; + npf_inet_pton(AF_INET, LOCAL_IP, p); /* we bind to 127.0.0.1 */ + server.sin_port = htons(LOCAL_PORT); + + struct socket *so; + int error = socreate(AF_INET, &so, SOCK_DGRAM, 0, cur, NULL); + if (error) { + printf("socket creation failed: error is %d\n", error); + return NULL; + } + + solock(so); + + kauth_cred_t cred = kauth_cred_alloc(); + kauth_cred_seteuid(cred, uid); + kauth_cred_setegid(cred, gid); + + kauth_cred_t old = so->so_cred; + so->so_cred = kauth_cred_dup(cred); + kauth_cred_free(old); + + sounlock(so); + + if ((error = sobind(so, (struct sockaddr *)&server, cur)) != 0) { + printf("bind failed %d\n", error); + return NULL; + } + + if (dir == PFIL_OUT) { + /* connect to an additional remote address to set the 4 tuple addr-port state */ + struct sockaddr_in remote; + memset(&Remote, 0, sizeof(remote)); + + remote.sin_len = sizeof(remote); + remote.sin_family = AF_INET; + rp = &remote.sin_addr.s_addr; + npf_inet_pton(AF_INET, REMOTE_IP, rp); /* we connect to 127.0.0.1 */ + remote.sin_port = htons(REMOTE_PORT); + + solock(so); + if ((error = soconnect(so, (struct sockaddr *)&remote, cur)) != 0) { + printf("connect failed :%d\n", error); + return NULL; + } + sounlock(so); + } + + return so; +} + +static bool +test_static(bool verbose) +{ + for (size_t i = 0; i < __arraycount(test_cases); i++) { + const struct test_case *t = &test_cases[i]; + int error, serror; + struct socket *so; + + so = test_socket(t->di, t->uid, t->gid); + if (so == NULL) { + printf("socket:\n"); + return false; + } + + if (npf_test_getif(t->ifname) == NULL) { + printf("Interface %s is not configured.\n", t->ifname); + return false; + } + + error = run_raw_testcase(i, verbose); + serror = run_handler_testcase(i); + + if (verbose) { + printf("rule test %d:\texpected %d (stateful) and %d\n" + "\t\t-> returned %d and %d\n", + i + 1, t->stateful_ret, t->ret, serror, error); + } + CHECK_TRUE(error == t->ret); + CHECK_TRUE(serror == t->stateful_ret) + + soclose(so); + } + return true; +} + +bool +npf_guid_test(bool verbose) +{ + soinit1(); + + bool ok; + + ok = test_static(verbose); + CHECK_TRUE(ok); + + return true; +} diff --git a/usr.sbin/npf/npftest/libnpftest/npf_rule_test.c b/usr.sbin/npf/npftest/libnpftest/npf_rule_test.c index a89e99cf65aac..ff3b05feb2668 100644 --- a/usr.sbin/npf/npftest/libnpftest/npf_rule_test.c +++ b/usr.sbin/npf/npftest/libnpftest/npf_rule_test.c @@ -190,7 +190,6 @@ static const struct test_case { .ifname = IFNAME_INT, .di = PFIL_OUT, .stateful_ret = RESULT_BLOCK, .ret = RESULT_BLOCK }, - }; static int diff --git a/usr.sbin/npf/npftest/libnpftest/npf_test.h b/usr.sbin/npf/npftest/libnpftest/npf_test.h index 3ca7ed99bd0ad..0983f2996bd7b 100644 --- a/usr.sbin/npf/npftest/libnpftest/npf_test.h +++ b/usr.sbin/npf/npftest/libnpftest/npf_test.h @@ -121,6 +121,7 @@ bool npf_table_test(bool, void *, size_t); bool npf_state_test(bool); bool npf_rule_test(bool); +bool npf_guid_test(bool); bool npf_nat_test(bool); bool npf_gc_test(bool); diff --git a/usr.sbin/npf/npftest/npftest.c b/usr.sbin/npf/npftest/npftest.c index ac717e7c6b5c4..9788e3b0550d7 100644 --- a/usr.sbin/npf/npftest/npftest.c +++ b/usr.sbin/npf/npftest/npftest.c @@ -66,7 +66,8 @@ describe_tests(void) "state\tstate handling and processing\n" "gc\tconnection G/C\n" "rule\trule processing\n" - "nat\tNAT rule processing\n"); + "nat\tNAT rule processing\n" + "guid\tUser/group filtering\n"); exit(EXIT_SUCCESS); } @@ -323,6 +324,12 @@ main(int argc, char **argv) tname_matched = true; } + if (!testname || strcmp("guid", testname) == 0) { + ok = rumpns_npf_guid_test(verbose); + fail |= result("guid", ok); + tname_matched = true; + } + if (!testname || strcmp("nat", testname) == 0) { srandom(1); ok = rumpns_npf_nat_test(verbose); diff --git a/usr.sbin/npf/npftest/npftest.conf b/usr.sbin/npf/npftest/npftest.conf index 83ef247bb48d6..d502db20ad4a9 100644 --- a/usr.sbin/npf/npftest/npftest.conf +++ b/usr.sbin/npf/npftest/npftest.conf @@ -18,6 +18,8 @@ $local_ip1 = 10.1.1.1 $local_ip2 = 10.1.1.2 $local_ip3 = 10.1.1.3 $local_ip4 = 10.1.1.4 +$Kojo = 1001 +$wheel = 20 $local_net = { 10.1.1.0/24 } $ports = { 8000, 9000 } @@ -44,6 +46,8 @@ map ruleset "map:some-daemon" on $ext_if group "ext" on $ext_if { pass out final from $local_ip3 pass in final to $pub_ip3 + pass in final from $local_ip4 user $Kojo group $wheel + block out final to 127.0.0.1 user > $Kojo group 1 >< $wheel pass out final from $net6_inner pass in final to $net6_outer @@ -67,6 +71,7 @@ group "int" on $int_if { pass in final family inet6 proto udp from $net6_pr55403 pass in final family inet6 proto udp from ! $net6_pr55403 to $net6_pr55403 + } group default { diff --git a/usr.sbin/npf/npftest/npftest.h b/usr.sbin/npf/npftest/npftest.h index fd67cfb3c8f3a..7729b3622596d 100644 --- a/usr.sbin/npf/npftest/npftest.h +++ b/usr.sbin/npf/npftest/npftest.h @@ -27,6 +27,7 @@ #define rumpns_npf_gc_test npf_gc_test #define rumpns_npf_test_conc npf_test_conc #define rumpns_npf_test_statetrack npf_test_statetrack +#define rumpns_npf_guid_test npf_guid_test #endif #include "npf.h" @@ -49,6 +50,7 @@ bool rumpns_npf_table_test(bool, void *, size_t); bool rumpns_npf_state_test(bool); bool rumpns_npf_rule_test(bool); +bool rumpns_npf_guid_test(bool); bool rumpns_npf_nat_test(bool); bool rumpns_npf_gc_test(bool);