diff options
author | Thomas Haller <thaller@redhat.com> | 2023-09-12 09:30:54 +0200 |
---|---|---|
committer | Pablo Neira Ayuso <pablo@netfilter.org> | 2023-09-14 14:45:14 +0200 |
commit | 8519ab031d8022999603a69ee9f18e8cfb06645d (patch) | |
tree | c6574e21984de79f9ff92e7b9360feb400c07239 /src/evaluate.c | |
parent | 65d3e81ce8b98d0bde24f4d8c392c252981c7d8b (diff) |
datatype: fix leak and cleanup reference counting for struct datatype
Test `./tests/shell/run-tests.sh -V tests/shell/testcases/maps/nat_addr_port`
fails:
==118== 195 (112 direct, 83 indirect) bytes in 1 blocks are definitely lost in loss record 3 of 3
==118== at 0x484682C: calloc (vg_replace_malloc.c:1554)
==118== by 0x48A39DD: xmalloc (utils.c:37)
==118== by 0x48A39DD: xzalloc (utils.c:76)
==118== by 0x487BDFD: datatype_alloc (datatype.c:1205)
==118== by 0x487BDFD: concat_type_alloc (datatype.c:1288)
==118== by 0x488229D: stmt_evaluate_nat_map (evaluate.c:3786)
==118== by 0x488229D: stmt_evaluate_nat (evaluate.c:3892)
==118== by 0x488229D: stmt_evaluate (evaluate.c:4450)
==118== by 0x488328E: rule_evaluate (evaluate.c:4956)
==118== by 0x48ADC71: nft_evaluate (libnftables.c:552)
==118== by 0x48AEC29: nft_run_cmd_from_buffer (libnftables.c:595)
==118== by 0x402983: main (main.c:534)
I think the reference handling for datatype is wrong. It was introduced
by commit 01a13882bb59 ('src: add reference counter for dynamic
datatypes').
We don't notice it most of the time, because instances are statically
allocated, where datatype_get()/datatype_free() is a NOP.
Fix and rework.
- Commit 01a13882bb59 comments "The reference counter of any newly
allocated datatype is set to zero". That seems not workable.
Previously, functions like datatype_clone() would have returned the
refcnt set to zero. Some callers would then then set the refcnt to one, but
some wouldn't (set_datatype_alloc()). Calling datatype_free() with a
refcnt of zero will overflow to UINT_MAX and leak:
if (--dtype->refcnt > 0)
return;
While there could be schemes with such asymmetric counting that juggle the
appropriate number of datatype_get() and datatype_free() calls, this is
confusing and error prone. The common pattern is that every
alloc/clone/get/ref is paired with exactly one unref/free.
Let datatype_clone() return references with refcnt set 1 and in
general be always clear about where we transfer ownership (take a
reference) and where we need to release it.
- set_datatype_alloc() needs to consistently return ownership to the
reference. Previously, some code paths would and others wouldn't.
- Replace
datatype_set(key, set_datatype_alloc(dtype, key->byteorder))
with a __datatype_set() with takes ownership.
Fixes: 01a13882bb59 ('src: add reference counter for dynamic datatypes')
Signed-off-by: Thomas Haller <thaller@redhat.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Diffstat (limited to 'src/evaluate.c')
-rw-r--r-- | src/evaluate.c | 66 |
1 files changed, 40 insertions, 26 deletions
diff --git a/src/evaluate.c b/src/evaluate.c index b0c6919f..1b7e0b37 100644 --- a/src/evaluate.c +++ b/src/evaluate.c @@ -82,7 +82,7 @@ static void key_fix_dtype_byteorder(struct expr *key) if (dtype->byteorder == key->byteorder) return; - datatype_set(key, set_datatype_alloc(dtype, key->byteorder)); + __datatype_set(key, set_datatype_alloc(dtype, key->byteorder)); } static int set_evaluate(struct eval_ctx *ctx, struct set *set); @@ -1521,8 +1521,7 @@ static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr) clone = datatype_clone(i->dtype); clone->size = i->len; clone->byteorder = i->byteorder; - clone->refcnt = 1; - i->dtype = clone; + __datatype_set(i, clone); } if (dtype == NULL && i->dtype->size == 0) @@ -1550,7 +1549,7 @@ static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr) } (*expr)->flags |= flags; - datatype_set(*expr, concat_type_alloc(ntype)); + __datatype_set(*expr, concat_type_alloc(ntype)); (*expr)->len = size; if (off > 0) @@ -1888,7 +1887,6 @@ static int expr_evaluate_map(struct eval_ctx *ctx, struct expr **expr) { struct expr *map = *expr, *mappings; struct expr_ctx ectx = ctx->ectx; - const struct datatype *dtype; struct expr *key, *data; if (map->map->etype == EXPR_CT && @@ -1930,12 +1928,16 @@ static int expr_evaluate_map(struct eval_ctx *ctx, struct expr **expr) ctx->ectx.len, NULL); } - dtype = set_datatype_alloc(ectx.dtype, ectx.byteorder); - if (dtype->type == TYPE_VERDICT) + if (ectx.dtype->type == TYPE_VERDICT) { data = verdict_expr_alloc(&netlink_location, 0, NULL); - else + } else { + const struct datatype *dtype; + + dtype = set_datatype_alloc(ectx.dtype, ectx.byteorder); data = constant_expr_alloc(&netlink_location, dtype, dtype->byteorder, ectx.len, NULL); + datatype_free(dtype); + } mappings = implicit_set_declaration(ctx, "__map%d", key, data, @@ -3765,8 +3767,10 @@ static int stmt_evaluate_nat_map(struct eval_ctx *ctx, struct stmt *stmt) { struct proto_ctx *pctx = eval_proto_ctx(ctx); struct expr *one, *two, *data, *tmp; - const struct datatype *dtype; - int addr_type, err; + const struct datatype *dtype = NULL; + const struct datatype *dtype2; + int addr_type; + int err; if (stmt->nat.family == NFPROTO_INET) expr_family_infer(pctx, stmt->nat.addr, &stmt->nat.family); @@ -3786,18 +3790,23 @@ static int stmt_evaluate_nat_map(struct eval_ctx *ctx, struct stmt *stmt) dtype = concat_type_alloc((addr_type << TYPE_BITS) | TYPE_INET_SERVICE); expr_set_context(&ctx->ectx, dtype, dtype->size); - if (expr_evaluate(ctx, &stmt->nat.addr)) - return -1; + if (expr_evaluate(ctx, &stmt->nat.addr)) { + err = -1; + goto out; + } if (pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc == NULL && !nat_evaluate_addr_has_th_expr(stmt->nat.addr)) { - return stmt_binary_error(ctx, stmt->nat.addr, stmt, + err = stmt_binary_error(ctx, stmt->nat.addr, stmt, "transport protocol mapping is only " "valid after transport protocol match"); + goto out; } - if (stmt->nat.addr->etype != EXPR_MAP) - return 0; + if (stmt->nat.addr->etype != EXPR_MAP) { + err = 0; + goto out; + } data = stmt->nat.addr->mappings->set->data; if (data->flags & EXPR_F_INTERVAL) @@ -3805,36 +3814,42 @@ static int stmt_evaluate_nat_map(struct eval_ctx *ctx, struct stmt *stmt) datatype_set(data, dtype); - if (expr_ops(data)->type != EXPR_CONCAT) - return __stmt_evaluate_arg(ctx, stmt, dtype, dtype->size, + if (expr_ops(data)->type != EXPR_CONCAT) { + err = __stmt_evaluate_arg(ctx, stmt, dtype, dtype->size, BYTEORDER_BIG_ENDIAN, &stmt->nat.addr); + goto out; + } one = list_first_entry(&data->expressions, struct expr, list); two = list_entry(one->list.next, struct expr, list); - if (one == two || !list_is_last(&two->list, &data->expressions)) - return __stmt_evaluate_arg(ctx, stmt, dtype, dtype->size, + if (one == two || !list_is_last(&two->list, &data->expressions)) { + err = __stmt_evaluate_arg(ctx, stmt, dtype, dtype->size, BYTEORDER_BIG_ENDIAN, &stmt->nat.addr); + goto out; + } - dtype = get_addr_dtype(stmt->nat.family); + dtype2 = get_addr_dtype(stmt->nat.family); tmp = one; - err = __stmt_evaluate_arg(ctx, stmt, dtype, dtype->size, + err = __stmt_evaluate_arg(ctx, stmt, dtype2, dtype2->size, BYTEORDER_BIG_ENDIAN, &tmp); if (err < 0) - return err; + goto out; if (tmp != one) BUG("Internal error: Unexpected alteration of l3 expression"); tmp = two; err = nat_evaluate_transport(ctx, stmt, &tmp); if (err < 0) - return err; + goto out; if (tmp != two) BUG("Internal error: Unexpected alteration of l4 expression"); +out: + datatype_free(dtype); return err; } @@ -4549,8 +4564,7 @@ static int set_expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr) dtype = datatype_clone(i->dtype); dtype->size = i->len; dtype->byteorder = i->byteorder; - dtype->refcnt = 1; - i->dtype = dtype; + __datatype_set(i, dtype); } if (i->dtype->size == 0 && i->len == 0) @@ -4573,7 +4587,7 @@ static int set_expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr) } (*expr)->flags |= flags; - datatype_set(*expr, concat_type_alloc(ntype)); + __datatype_set(*expr, concat_type_alloc(ntype)); (*expr)->len = size; expr_set_context(&ctx->ectx, (*expr)->dtype, (*expr)->len); |