diff options
Diffstat (limited to 'sys/netpfil/ipfw/ip_fw_sockopt.c')
-rw-r--r-- | sys/netpfil/ipfw/ip_fw_sockopt.c | 431 |
1 files changed, 353 insertions, 78 deletions
diff --git a/sys/netpfil/ipfw/ip_fw_sockopt.c b/sys/netpfil/ipfw/ip_fw_sockopt.c index 857b465..c63a303 100644 --- a/sys/netpfil/ipfw/ip_fw_sockopt.c +++ b/sys/netpfil/ipfw/ip_fw_sockopt.c @@ -81,6 +81,8 @@ static int check_ipfw_rule1(struct ip_fw_rule *rule, int size, struct rule_check_info *ci); static int check_ipfw_rule0(struct ip_fw_rule0 *rule, int size, struct rule_check_info *ci); +static int rewrite_rule_uidx(struct ip_fw_chain *chain, + struct rule_check_info *ci); #define NAMEDOBJ_HASH_SIZE 32 @@ -98,10 +100,11 @@ struct namedobj_instance { }; #define BLOCK_ITEMS (8 * sizeof(u_long)) /* Number of items for ffsl() */ -static uint32_t objhash_hash_name(struct namedobj_instance *ni, void *key, - uint32_t kopt); +static uint32_t objhash_hash_name(struct namedobj_instance *ni, + const void *key, uint32_t kopt); static uint32_t objhash_hash_idx(struct namedobj_instance *ni, uint32_t val); -static int objhash_cmp_name(struct named_object *no, void *name, uint32_t set); +static int objhash_cmp_name(struct named_object *no, const void *name, + uint32_t set); MALLOC_DEFINE(M_IPFW, "IpFw/IpAcct", "IpFw/IpAcct chain's"); @@ -153,10 +156,17 @@ static struct ipfw_sopt_handler scodes[] = { static int set_legacy_obj_kidx(struct ip_fw_chain *ch, struct ip_fw_rule0 *rule); -struct opcode_obj_rewrite *ipfw_find_op_rw(uint16_t opcode); +static struct opcode_obj_rewrite *find_op_rw(ipfw_insn *cmd, + uint16_t *puidx, uint8_t *ptype); static int mark_object_kidx(struct ip_fw_chain *ch, struct ip_fw *rule, uint32_t *bmask); +static int ref_rule_objects(struct ip_fw_chain *ch, struct ip_fw *rule, + struct rule_check_info *ci, struct obj_idx *oib, struct tid_info *ti); +static int ref_opcode_object(struct ip_fw_chain *ch, ipfw_insn *cmd, + struct tid_info *ti, struct obj_idx *pidx, int *unresolved); static void unref_rule_objects(struct ip_fw_chain *chain, struct ip_fw *rule); +static void unref_oib_objects(struct ip_fw_chain *ch, ipfw_insn *cmd, + struct obj_idx *oib, struct obj_idx *end); static int export_objhash_ntlv(struct namedobj_instance *ni, uint16_t kidx, struct sockopt_data *sd); @@ -675,7 +685,7 @@ commit_rules(struct ip_fw_chain *chain, struct rule_check_info *rci, int count) * We need to find (and create non-existing) * kernel objects, and reference existing ones. */ - error = ipfw_rewrite_rule_uidx(chain, ci); + error = rewrite_rule_uidx(chain, ci); if (error != 0) { /* @@ -1483,6 +1493,35 @@ check_ipfw_rule_body(ipfw_insn *cmd, int cmd_len, struct rule_check_info *ci) goto bad_size; break; + case O_EXTERNAL_ACTION: + if (cmd->arg1 == 0 || + cmdlen != F_INSN_SIZE(ipfw_insn)) { + printf("ipfw: invalid external " + "action opcode\n"); + return (EINVAL); + } + ci->object_opcodes++; + /* Do we have O_EXTERNAL_INSTANCE opcode? */ + if (l != cmdlen) { + l -= cmdlen; + cmd += cmdlen; + cmdlen = F_LEN(cmd); + if (cmd->opcode != O_EXTERNAL_INSTANCE) { + printf("ipfw: invalid opcode " + "next to external action %u\n", + cmd->opcode); + return (EINVAL); + } + if (cmd->arg1 == 0 || + cmdlen != F_INSN_SIZE(ipfw_insn)) { + printf("ipfw: invalid external " + "action instance opcode\n"); + return (EINVAL); + } + ci->object_opcodes++; + } + goto check_action; + case O_FIB: if (cmdlen != F_INSN_SIZE(ipfw_insn)) goto bad_size; @@ -2000,11 +2039,10 @@ static int mark_object_kidx(struct ip_fw_chain *ch, struct ip_fw *rule, uint32_t *bmask) { - int cmdlen, l, count; + struct opcode_obj_rewrite *rw; ipfw_insn *cmd; + int bidx, cmdlen, l, count; uint16_t kidx; - struct opcode_obj_rewrite *rw; - int bidx; uint8_t subtype; l = rule->cmd_len; @@ -2014,15 +2052,15 @@ mark_object_kidx(struct ip_fw_chain *ch, struct ip_fw *rule, for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); - rw = ipfw_find_op_rw(cmd->opcode); + rw = find_op_rw(cmd, &kidx, &subtype); if (rw == NULL) continue; - if (rw->classifier(cmd, &kidx, &subtype) != 0) - continue; - bidx = kidx / 32; - /* Maintain separate bitmasks for table and non-table objects */ + /* + * Maintain separate bitmasks for table and + * non-table objects. + */ if (rw->etlv != IPFW_TLV_TBL_NAME) bidx += IPFW_TABLES_MAX / 32; @@ -2194,7 +2232,7 @@ create_objects_compat(struct ip_fw_chain *ch, ipfw_insn *cmd, ti->type = p->type; ti->atype = 0; - rw = ipfw_find_op_rw((cmd + p->off)->opcode); + rw = find_op_rw(cmd + p->off, NULL, NULL); KASSERT(rw != NULL, ("Unable to find handler for op %d", (cmd + p->off)->opcode)); @@ -2229,14 +2267,14 @@ create_objects_compat(struct ip_fw_chain *ch, ipfw_insn *cmd, static int set_legacy_obj_kidx(struct ip_fw_chain *ch, struct ip_fw_rule0 *rule) { - int cmdlen, error, l; - ipfw_insn *cmd; - uint16_t kidx, uidx; - struct named_object *no; struct opcode_obj_rewrite *rw; - uint8_t subtype; + struct named_object *no; + ipfw_insn *cmd; char *end; long val; + int cmdlen, error, l; + uint16_t kidx, uidx; + uint8_t subtype; error = 0; @@ -2246,12 +2284,9 @@ set_legacy_obj_kidx(struct ip_fw_chain *ch, struct ip_fw_rule0 *rule) for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); - rw = ipfw_find_op_rw(cmd->opcode); - if (rw == NULL) - continue; - /* Check if is index in given opcode */ - if (rw->classifier(cmd, &kidx, &subtype) != 0) + rw = find_op_rw(cmd, &kidx, &subtype); + if (rw == NULL) continue; /* Try to find referenced kernel object */ @@ -2286,7 +2321,7 @@ set_legacy_obj_kidx(struct ip_fw_chain *ch, struct ip_fw_rule0 *rule) * * Used to rollback partially converted rule on error. */ -void +static void unref_oib_objects(struct ip_fw_chain *ch, ipfw_insn *cmd, struct obj_idx *oib, struct obj_idx *end) { @@ -2300,7 +2335,7 @@ unref_oib_objects(struct ip_fw_chain *ch, ipfw_insn *cmd, struct obj_idx *oib, if (p->kidx == 0) continue; - rw = ipfw_find_op_rw((cmd + p->off)->opcode); + rw = find_op_rw(cmd + p->off, NULL, NULL); KASSERT(rw != NULL, ("Unable to find handler for op %d", (cmd + p->off)->opcode)); @@ -2318,11 +2353,11 @@ unref_oib_objects(struct ip_fw_chain *ch, ipfw_insn *cmd, struct obj_idx *oib, static void unref_rule_objects(struct ip_fw_chain *ch, struct ip_fw *rule) { - int cmdlen, l; - ipfw_insn *cmd; + struct opcode_obj_rewrite *rw; struct named_object *no; + ipfw_insn *cmd; + int cmdlen, l; uint16_t kidx; - struct opcode_obj_rewrite *rw; uint8_t subtype; IPFW_UH_WLOCK_ASSERT(ch); @@ -2333,12 +2368,9 @@ unref_rule_objects(struct ip_fw_chain *ch, struct ip_fw *rule) for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); - rw = ipfw_find_op_rw(cmd->opcode); + rw = find_op_rw(cmd, &kidx, &subtype); if (rw == NULL) continue; - if (rw->classifier(cmd, &kidx, &subtype) != 0) - continue; - no = rw->find_bykidx(ch, kidx); KASSERT(no != NULL, ("table id %d not found", kidx)); @@ -2360,31 +2392,23 @@ unref_rule_objects(struct ip_fw_chain *ch, struct ip_fw *rule) * Find and reference object (if any) stored in instruction @cmd. * * Saves object info in @pidx, sets - * - @found to 1 if object was found and references * - @unresolved to 1 if object should exists but not found * * Returns non-zero value in case of error. */ -int +static int ref_opcode_object(struct ip_fw_chain *ch, ipfw_insn *cmd, struct tid_info *ti, - struct obj_idx *pidx, int *found, int *unresolved) + struct obj_idx *pidx, int *unresolved) { struct named_object *no; struct opcode_obj_rewrite *rw; int error; - *found = 0; - *unresolved = 0; - /* Check if this opcode is candidate for rewrite */ - rw = ipfw_find_op_rw(cmd->opcode); + rw = find_op_rw(cmd, &ti->uidx, &ti->type); if (rw == NULL) return (0); - /* Check if we need to rewrite this opcode */ - if (rw->classifier(cmd, &ti->uidx, &ti->type) != 0) - return (0); - /* Need to rewrite. Save necessary fields */ pidx->uidx = ti->uidx; pidx->type = ti->type; @@ -2394,19 +2418,146 @@ ref_opcode_object(struct ip_fw_chain *ch, ipfw_insn *cmd, struct tid_info *ti, if (error != 0) return (error); if (no == NULL) { + /* + * Report about unresolved object for automaic + * creation. + */ *unresolved = 1; return (0); } - /* Found. bump refcount */ - *found = 1; + /* Found. Bump refcount and update kidx. */ no->refcnt++; - pidx->kidx = no->kidx; - + rw->update(cmd, no->kidx); return (0); } /* + * Finds and bumps refcount for objects referenced by given @rule. + * Auto-creates non-existing tables. + * Fills in @oib array with userland/kernel indexes. + * + * Returns 0 on success. + */ +static int +ref_rule_objects(struct ip_fw_chain *ch, struct ip_fw *rule, + struct rule_check_info *ci, struct obj_idx *oib, struct tid_info *ti) +{ + struct obj_idx *pidx; + ipfw_insn *cmd; + int cmdlen, error, l, unresolved; + + pidx = oib; + l = rule->cmd_len; + cmd = rule->cmd; + cmdlen = 0; + error = 0; + + IPFW_UH_WLOCK(ch); + + /* Increase refcount on each existing referenced table. */ + for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { + cmdlen = F_LEN(cmd); + unresolved = 0; + + error = ref_opcode_object(ch, cmd, ti, pidx, &unresolved); + if (error != 0) + break; + /* + * Compability stuff for old clients: + * prepare to automaitcally create non-existing objects. + */ + if (unresolved != 0) { + pidx->off = rule->cmd_len - l; + pidx++; + } + } + + if (error != 0) { + /* Unref everything we have already done */ + unref_oib_objects(ch, rule->cmd, oib, pidx); + IPFW_UH_WUNLOCK(ch); + return (error); + } + IPFW_UH_WUNLOCK(ch); + + /* Perform auto-creation for non-existing objects */ + if (pidx != oib) + error = create_objects_compat(ch, rule->cmd, oib, pidx, ti); + + /* Calculate real number of dynamic objects */ + ci->object_opcodes = (uint16_t)(pidx - oib); + + return (error); +} + +/* + * Checks is opcode is referencing table of appropriate type. + * Adds reference count for found table if true. + * Rewrites user-supplied opcode values with kernel ones. + * + * Returns 0 on success and appropriate error code otherwise. + */ +static int +rewrite_rule_uidx(struct ip_fw_chain *chain, struct rule_check_info *ci) +{ + int error; + ipfw_insn *cmd; + uint8_t type; + struct obj_idx *p, *pidx_first, *pidx_last; + struct tid_info ti; + + /* + * Prepare an array for storing opcode indices. + * Use stack allocation by default. + */ + if (ci->object_opcodes <= (sizeof(ci->obuf)/sizeof(ci->obuf[0]))) { + /* Stack */ + pidx_first = ci->obuf; + } else + pidx_first = malloc( + ci->object_opcodes * sizeof(struct obj_idx), + M_IPFW, M_WAITOK | M_ZERO); + + error = 0; + type = 0; + memset(&ti, 0, sizeof(ti)); + + /* + * Use default set for looking up tables (old way) or + * use set rule is assigned to (new way). + */ + ti.set = (V_fw_tables_sets != 0) ? ci->krule->set : 0; + if (ci->ctlv != NULL) { + ti.tlvs = (void *)(ci->ctlv + 1); + ti.tlen = ci->ctlv->head.length - sizeof(ipfw_obj_ctlv); + } + + /* Reference all used tables and other objects */ + error = ref_rule_objects(chain, ci->krule, ci, pidx_first, &ti); + if (error != 0) + goto free; + /* + * Note that ref_rule_objects() might have updated ci->object_opcodes + * to reflect actual number of object opcodes. + */ + + /* Perform rewrite of remaining opcodes */ + p = pidx_first; + pidx_last = pidx_first + ci->object_opcodes; + for (p = pidx_first; p < pidx_last; p++) { + cmd = ci->krule->cmd + p->off; + update_opcode_kidx(cmd, p->kidx); + } + +free: + if (pidx_first != ci->obuf) + free(pidx_first, M_IPFW); + + return (error); +} + +/* * Adds one or more rules to ipfw @chain. * Data layout (version 0)(current): * Request: @@ -2646,7 +2797,7 @@ dump_soptcodes(struct ip_fw_chain *chain, ip_fw3_opheader *op3, for (n = 1; n <= count; n++) { i = (ipfw_sopt_info *)ipfw_get_sopt_space(sd, sizeof(*i)); - KASSERT(i != 0, ("previously checked buffer is not enough")); + KASSERT(i != NULL, ("previously checked buffer is not enough")); sh = &ctl3_handlers[n]; i->opcode = sh->opcode; i->version = sh->version; @@ -2680,35 +2831,73 @@ compare_opcodes(const void *_a, const void *_b) } /* - * Finds opcode object rewriter based on @code. - * - * Returns pointer to handler or NULL. + * XXX: Rewrite bsearch() */ -struct opcode_obj_rewrite * -ipfw_find_op_rw(uint16_t opcode) +static int +find_op_rw_range(uint16_t op, struct opcode_obj_rewrite **plo, + struct opcode_obj_rewrite **phi) { - struct opcode_obj_rewrite *rw, h; + struct opcode_obj_rewrite *ctl3_max, *lo, *hi, h, *rw; memset(&h, 0, sizeof(h)); - h.opcode = opcode; + h.opcode = op; rw = (struct opcode_obj_rewrite *)bsearch(&h, ctl3_rewriters, ctl3_rsize, sizeof(h), compare_opcodes); + if (rw == NULL) + return (1); + + /* Find the first element matching the same opcode */ + lo = rw; + for ( ; lo > ctl3_rewriters && (lo - 1)->opcode == op; lo--) + ; - return (rw); + /* Find the last element matching the same opcode */ + hi = rw; + ctl3_max = ctl3_rewriters + ctl3_rsize; + for ( ; (hi + 1) < ctl3_max && (hi + 1)->opcode == op; hi++) + ; + + *plo = lo; + *phi = hi; + + return (0); } +/* + * Finds opcode object rewriter based on @code. + * + * Returns pointer to handler or NULL. + */ +static struct opcode_obj_rewrite * +find_op_rw(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype) +{ + struct opcode_obj_rewrite *rw, *lo, *hi; + uint16_t uidx; + uint8_t subtype; + + if (find_op_rw_range(cmd->opcode, &lo, &hi) != 0) + return (NULL); + + for (rw = lo; rw <= hi; rw++) { + if (rw->classifier(cmd, &uidx, &subtype) == 0) { + if (puidx != NULL) + *puidx = uidx; + if (ptype != NULL) + *ptype = subtype; + return (rw); + } + } + + return (NULL); +} int classify_opcode_kidx(ipfw_insn *cmd, uint16_t *puidx) { - struct opcode_obj_rewrite *rw; - uint8_t subtype; - rw = ipfw_find_op_rw(cmd->opcode); - if (rw == NULL) + if (find_op_rw(cmd, puidx, NULL) == 0) return (1); - - return (rw->classifier(cmd, puidx, &subtype)); + return (0); } void @@ -2716,7 +2905,7 @@ update_opcode_kidx(ipfw_insn *cmd, uint16_t idx) { struct opcode_obj_rewrite *rw; - rw = ipfw_find_op_rw(cmd->opcode); + rw = find_op_rw(cmd, NULL, NULL); KASSERT(rw != NULL, ("No handler to update opcode %d", cmd->opcode)); rw->update(cmd, idx); } @@ -2784,20 +2973,26 @@ int ipfw_del_obj_rewriter(struct opcode_obj_rewrite *rw, size_t count) { size_t sz; - struct opcode_obj_rewrite *tmp, *h; + struct opcode_obj_rewrite *ctl3_max, *ktmp, *lo, *hi; int i; CTL3_LOCK(); for (i = 0; i < count; i++) { - tmp = &rw[i]; - h = ipfw_find_op_rw(tmp->opcode); - if (h == NULL) + if (find_op_rw_range(rw[i].opcode, &lo, &hi) != 0) continue; - sz = (ctl3_rewriters + ctl3_rsize - (h + 1)) * sizeof(*h); - memmove(h, h + 1, sz); - ctl3_rsize--; + for (ktmp = lo; ktmp <= hi; ktmp++) { + if (ktmp->classifier != rw[i].classifier) + continue; + + ctl3_max = ctl3_rewriters + ctl3_rsize; + sz = (ctl3_max - (ktmp + 1)) * sizeof(*ktmp); + memmove(ktmp, ktmp + 1, sz); + ctl3_rsize--; + break; + } + } if (ctl3_rsize == 0) { @@ -2828,7 +3023,7 @@ export_objhash_ntlv_internal(struct namedobj_instance *ni, /* * Lists all service objects. * Data layout (v0)(current): - * Request: [ ipfw_obj_lheader ] size = ipfw_cfg_lheader.size + * Request: [ ipfw_obj_lheader ] size = ipfw_obj_lheader.size * Reply: [ ipfw_obj_lheader [ ipfw_obj_ntlv x N ] (optional) ] * Returns 0 on success */ @@ -3843,17 +4038,17 @@ ipfw_objhash_set_funcs(struct namedobj_instance *ni, objhash_hash_f *hash_f, } static uint32_t -objhash_hash_name(struct namedobj_instance *ni, void *name, uint32_t set) +objhash_hash_name(struct namedobj_instance *ni, const void *name, uint32_t set) { - return (fnv_32_str((char *)name, FNV1_32_INIT)); + return (fnv_32_str((const char *)name, FNV1_32_INIT)); } static int -objhash_cmp_name(struct named_object *no, void *name, uint32_t set) +objhash_cmp_name(struct named_object *no, const void *name, uint32_t set) { - if ((strcmp(no->name, (char *)name) == 0) && (no->set == set)) + if ((strcmp(no->name, (const char *)name) == 0) && (no->set == set)) return (0); return (1); @@ -3886,11 +4081,90 @@ ipfw_objhash_lookup_name(struct namedobj_instance *ni, uint32_t set, char *name) } /* + * Find named object by @uid. + * Check @tlvs for valid data inside. + * + * Returns pointer to found TLV or NULL. + */ +static ipfw_obj_ntlv * +find_name_tlv_type(void *tlvs, int len, uint16_t uidx, uint32_t etlv) +{ + ipfw_obj_ntlv *ntlv; + uintptr_t pa, pe; + int l; + + pa = (uintptr_t)tlvs; + pe = pa + len; + l = 0; + for (; pa < pe; pa += l) { + ntlv = (ipfw_obj_ntlv *)pa; + l = ntlv->head.length; + + if (l != sizeof(*ntlv)) + return (NULL); + + if (ntlv->idx != uidx) + continue; + /* + * When userland has specified zero TLV type, do + * not compare it with eltv. In some cases userland + * doesn't know what type should it have. Use only + * uidx and name for search named_object. + */ + if (ntlv->head.type != 0 && + ntlv->head.type != (uint16_t)etlv) + continue; + + if (ipfw_check_object_name_generic(ntlv->name) != 0) + return (NULL); + + return (ntlv); + } + + return (NULL); +} + +/* + * Finds object config based on either legacy index + * or name in ntlv. + * Note @ti structure contains unchecked data from userland. + * + * Returns 0 in success and fills in @pno with found config + */ +int +ipfw_objhash_find_type(struct namedobj_instance *ni, struct tid_info *ti, + uint32_t etlv, struct named_object **pno) +{ + char *name; + ipfw_obj_ntlv *ntlv; + uint32_t set; + + if (ti->tlvs == NULL) + return (EINVAL); + + ntlv = find_name_tlv_type(ti->tlvs, ti->tlen, ti->uidx, etlv); + if (ntlv == NULL) + return (EINVAL); + name = ntlv->name; + + /* + * Use set provided by @ti instead of @ntlv one. + * This is needed due to different sets behavior + * controlled by V_fw_tables_sets. + */ + set = ti->set; + *pno = ipfw_objhash_lookup_name(ni, set, name); + if (*pno == NULL) + return (ESRCH); + return (0); +} + +/* * Find named object by name, considering also its TLV type. */ struct named_object * ipfw_objhash_lookup_name_type(struct namedobj_instance *ni, uint32_t set, - uint32_t type, char *name) + uint32_t type, const char *name) { struct named_object *no; uint32_t hash; @@ -3898,7 +4172,8 @@ ipfw_objhash_lookup_name_type(struct namedobj_instance *ni, uint32_t set, hash = ni->hash_f(ni, name, set) % ni->nn_size; TAILQ_FOREACH(no, &ni->names[hash], nn_next) { - if (ni->cmp_f(no, name, set) == 0 && no->etlv == type) + if (ni->cmp_f(no, name, set) == 0 && + no->etlv == (uint16_t)type) return (no); } |