From f256fd5a8ca0fa251b12a202efea8be8a26ae25e Mon Sep 17 00:00:00 2001 From: Emmankoko Date: Sat, 29 Mar 2025 16:16:41 +0000 Subject: [PATCH] Userland: npf rule parser for user and group id --- lib/libnpf/npf.c | 10 ++ lib/libnpf/npf.expsym | 1 + lib/libnpf/npf.h | 1 + sys/net/npf/npf.h | 22 ++++ usr.sbin/npf/npfctl/npf_build.c | 8 ++ usr.sbin/npf/npfctl/npf_data.c | 48 +++++++++ usr.sbin/npf/npfctl/npf_parse.y | 176 +++++++++++++++++++++++++++++++- usr.sbin/npf/npfctl/npf_scan.l | 6 +- usr.sbin/npf/npfctl/npf_var.c | 11 ++ usr.sbin/npf/npfctl/npf_var.h | 1 + usr.sbin/npf/npfctl/npfctl.h | 6 ++ 11 files changed, 285 insertions(+), 5 deletions(-) diff --git a/lib/libnpf/npf.c b/lib/libnpf/npf.c index 80116a34799a3..e066e8c04a3e0 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) { diff --git a/lib/libnpf/npf.expsym b/lib/libnpf/npf.expsym index 2fd9d4feff750..dfc9f4a320116 100644 --- a/lib/libnpf/npf.expsym +++ b/lib/libnpf/npf.expsym @@ -75,6 +75,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..ce16ac54345c8 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 *); diff --git a/sys/net/npf/npf.h b/sys/net/npf/npf.h index d9d5f77ea0d51..e9636744857c6 100644 --- a/sys/net/npf/npf.h +++ b/sys/net/npf/npf.h @@ -59,6 +59,15 @@ 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 uint8_t npf_netmask_t; #define NPF_MAX_NETMASK (128) @@ -372,6 +381,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/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..3c4daed798eef 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,52 @@ 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(usergroup, "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 + */ +struct r_id +npfctl_init_rid(uint32_t id1, uint32_t id2, uint8_t op) +{ + struct r_id rid; + rid.id[0] = id1; + rid.id[1] = id2; + rid.op = op; + return rid; +} + /* * 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..89473bd5bf3ff 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,158 @@ 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); + YYERROR; + } + $$ = $1; + } + | IDENTIFIER + { + if (npfctl_parse_user($1, &$$) == -1) { + yyerror("unknown user %s", $1); + YYERROR; + } + } + | VAR_ID + { + npfvar_t *vp = npfvar_lookup($1); + int type = npfvar_get_type(vp, 0); + char *usr; + + switch (type) { + case NPFVAR_IDENTIFIER: + case NPFVAR_STRING: + usr = npfvar_expand_string(vp); + if (npfctl_parse_user(usr, &$$) == -1) { + yyerror("unknown user %s", $1); + YYERROR; + } + break; + case NPFVAR_NUM: + $$ = npfvar_expand_number(vp); + break; + case -1: + yyerror("undefined variable '%s'", $1); + break; + default: + yyerror("wrong variable '%s' type '%s' for user id", + $1, npfvar_type(type)); + break; + } + } + ; + +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); + YYERROR; + } + $$ = $1; + } + | IDENTIFIER + { + if (npfctl_parse_usergroup($1, &$$) == -1) { + yyerror("unknown group %s", $1); + YYERROR; + } + } + | VAR_ID + { + npfvar_t *vp = npfvar_lookup($1); + int type = npfvar_get_type(vp, 0); + char *user_group; + + switch (type) { + case NPFVAR_IDENTIFIER: + case NPFVAR_STRING: + user_group = npfvar_expand_string(vp); + if (npfctl_parse_usergroup(user_group, &$$) == -1) { + yyerror("unknown group %s", $1); + YYERROR; + } + break; + case NPFVAR_NUM: + $$ = npfvar_expand_number(vp); + break; + case -1: + yyerror("undefined variable '%s'", $1); + break; + default: + yyerror("wrong variable '%s' type '%s' for group id", + $1, npfvar_type(type)); + break; + } + } + ; + +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_var.c b/usr.sbin/npf/npfctl/npf_var.c index 57858c3ce1191..ef220958a8918 100644 --- a/usr.sbin/npf/npfctl/npf_var.c +++ b/usr.sbin/npf/npfctl/npf_var.c @@ -183,6 +183,17 @@ 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; +} + 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..3bb4f2ade23d0 100644 --- a/usr.sbin/npf/npfctl/npf_var.h +++ b/usr.sbin/npf/npfctl/npf_var.h @@ -82,6 +82,7 @@ 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); diff --git a/usr.sbin/npf/npfctl/npfctl.h b/usr.sbin/npf/npfctl/npfctl.h index 4a17517488079..7fbd7baa4c454 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 *); +struct r_id npfctl_init_rid(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 *);