Blame SOURCES/0014-proto-track-full-stack-of-seen-l2-protocols-not-just.patch

b59ec1
From 72f0ca53bcc6586b921ff70dac9f3a911e4ec170 Mon Sep 17 00:00:00 2001
b59ec1
From: Phil Sutter <psutter@redhat.com>
b59ec1
Date: Thu, 9 Feb 2023 10:27:57 +0100
b59ec1
Subject: [PATCH] proto: track full stack of seen l2 protocols, not just
b59ec1
 cumulative offset
b59ec1
b59ec1
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2094887
b59ec1
Upstream Status: nftables commit 0d9daa0407212
b59ec1
b59ec1
commit 0d9daa0407212c8cc89b3ea8aee031ddf0109b08
b59ec1
Author: Florian Westphal <fw@strlen.de>
b59ec1
Date:   Mon Jul 25 14:32:13 2022 +0200
b59ec1
b59ec1
    proto: track full stack of seen l2 protocols, not just cumulative offset
b59ec1
b59ec1
    For input, a cumulative size counter of all pushed l2 headers is enough,
b59ec1
    because we have the full expression tree available to us.
b59ec1
b59ec1
    For delinearization we need to track all seen l2 headers, else we lose
b59ec1
    information that we might need at a later time.
b59ec1
b59ec1
    Consider:
b59ec1
b59ec1
    rule netdev nt nc set update ether saddr . vlan id
b59ec1
b59ec1
    during delinearization, the vlan proto_desc replaces the ethernet one,
b59ec1
    and by the time we try to split the concatenation apart we will search
b59ec1
    the ether saddr offset vs. the templates for proto_vlan.
b59ec1
b59ec1
    This replaces the offset with an array that stores the protocol
b59ec1
    descriptions seen.
b59ec1
b59ec1
    Then, if the payload offset is larger than our description, search the
b59ec1
    l2 stack and adjust the offset until we're within the expected offset
b59ec1
    boundary.
b59ec1
b59ec1
    Reported-by: Eric Garver <eric@garver.life>
b59ec1
    Signed-off-by: Florian Westphal <fw@strlen.de>
b59ec1
b59ec1
Signed-off-by: Phil Sutter <psutter@redhat.com>
b59ec1
---
b59ec1
 include/proto.h           |  3 +-
b59ec1
 src/evaluate.c            | 15 +++++++--
b59ec1
 src/netlink_delinearize.c |  5 ---
b59ec1
 src/payload.c             | 67 ++++++++++++++++++++++++++++++++-------
b59ec1
 src/proto.c               |  2 --
b59ec1
 5 files changed, 71 insertions(+), 21 deletions(-)
b59ec1
b59ec1
diff --git a/include/proto.h b/include/proto.h
b59ec1
index a04240a..35e760c 100644
b59ec1
--- a/include/proto.h
b59ec1
+++ b/include/proto.h
b59ec1
@@ -193,13 +193,14 @@ struct proto_ctx {
b59ec1
 	struct {
b59ec1
 		struct location			location;
b59ec1
 		const struct proto_desc		*desc;
b59ec1
-		unsigned int			offset;
b59ec1
 		struct {
b59ec1
 			struct location		location;
b59ec1
 			const struct proto_desc	*desc;
b59ec1
 		} protos[PROTO_CTX_NUM_PROTOS];
b59ec1
 		unsigned int			num_protos;
b59ec1
 	} protocol[PROTO_BASE_MAX + 1];
b59ec1
+	const struct proto_desc *stacked_ll[PROTO_CTX_NUM_PROTOS];
b59ec1
+	uint8_t stacked_ll_count;
b59ec1
 };
b59ec1
 
b59ec1
 extern void proto_ctx_init(struct proto_ctx *ctx, unsigned int family,
b59ec1
diff --git a/src/evaluate.c b/src/evaluate.c
b59ec1
index 82bf131..9246064 100644
b59ec1
--- a/src/evaluate.c
b59ec1
+++ b/src/evaluate.c
b59ec1
@@ -678,7 +678,13 @@ static int resolve_protocol_conflict(struct eval_ctx *ctx,
b59ec1
 	    conflict_resolution_gen_dependency(ctx, link, payload, &nstmt) < 0)
b59ec1
 		return 1;
b59ec1
 
b59ec1
-	payload->payload.offset += ctx->pctx.protocol[base].offset;
b59ec1
+	if (base == PROTO_BASE_LL_HDR) {
b59ec1
+		unsigned int i;
b59ec1
+
b59ec1
+		for (i = 0; i < ctx->pctx.stacked_ll_count; i++)
b59ec1
+			payload->payload.offset += ctx->pctx.stacked_ll[i]->length;
b59ec1
+	}
b59ec1
+
b59ec1
 	rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt);
b59ec1
 
b59ec1
 	return 0;
b59ec1
@@ -727,7 +733,12 @@ static int __expr_evaluate_payload(struct eval_ctx *ctx, struct expr *expr)
b59ec1
 	if (desc == payload->payload.desc) {
b59ec1
 		const struct proto_hdr_template *tmpl;
b59ec1
 
b59ec1
-		payload->payload.offset += ctx->pctx.protocol[base].offset;
b59ec1
+		if (desc->base == PROTO_BASE_LL_HDR) {
b59ec1
+			unsigned int i;
b59ec1
+
b59ec1
+			for (i = 0; i < ctx->pctx.stacked_ll_count; i++)
b59ec1
+				payload->payload.offset += ctx->pctx.stacked_ll[i]->length;
b59ec1
+		}
b59ec1
 check_icmp:
b59ec1
 		if (desc != &proto_icmp && desc != &proto_icmp6)
b59ec1
 			return 0;
b59ec1
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
b59ec1
index cba419d..0b5519d 100644
b59ec1
--- a/src/netlink_delinearize.c
b59ec1
+++ b/src/netlink_delinearize.c
b59ec1
@@ -1976,11 +1976,6 @@ static void payload_match_postprocess(struct rule_pp_ctx *ctx,
b59ec1
 				      struct expr *expr,
b59ec1
 				      struct expr *payload)
b59ec1
 {
b59ec1
-	enum proto_bases base = payload->payload.base;
b59ec1
-
b59ec1
-	assert(payload->payload.offset >= ctx->pctx.protocol[base].offset);
b59ec1
-	payload->payload.offset -= ctx->pctx.protocol[base].offset;
b59ec1
-
b59ec1
 	switch (expr->op) {
b59ec1
 	case OP_EQ:
b59ec1
 	case OP_NEQ:
b59ec1
diff --git a/src/payload.c b/src/payload.c
b59ec1
index 66418cd..2c0d0ac 100644
b59ec1
--- a/src/payload.c
b59ec1
+++ b/src/payload.c
b59ec1
@@ -116,8 +116,13 @@ static void payload_expr_pctx_update(struct proto_ctx *ctx,
b59ec1
 	if (desc->base == base->base) {
b59ec1
 		assert(base->length > 0);
b59ec1
 
b59ec1
-		if (!left->payload.is_raw)
b59ec1
-			ctx->protocol[base->base].offset += base->length;
b59ec1
+		if (!left->payload.is_raw) {
b59ec1
+			if (desc->base == PROTO_BASE_LL_HDR &&
b59ec1
+			    ctx->stacked_ll_count < PROTO_CTX_NUM_PROTOS) {
b59ec1
+				ctx->stacked_ll[ctx->stacked_ll_count] = base;
b59ec1
+				ctx->stacked_ll_count++;
b59ec1
+			}
b59ec1
+		}
b59ec1
 	}
b59ec1
 	proto_ctx_update(ctx, desc->base, loc, desc);
b59ec1
 }
b59ec1
@@ -869,6 +874,38 @@ void exthdr_dependency_kill(struct payload_dep_ctx *ctx, struct expr *expr,
b59ec1
 	}
b59ec1
 }
b59ec1
 
b59ec1
+static const struct proto_desc *get_stacked_desc(const struct proto_ctx *ctx,
b59ec1
+						 const struct proto_desc *top,
b59ec1
+						 const struct expr *e,
b59ec1
+						 unsigned int *skip)
b59ec1
+{
b59ec1
+	unsigned int i, total, payload_offset = e->payload.offset;
b59ec1
+
b59ec1
+	assert(e->etype == EXPR_PAYLOAD);
b59ec1
+
b59ec1
+	if (e->payload.base != PROTO_BASE_LL_HDR ||
b59ec1
+	    payload_offset < top->length) {
b59ec1
+		*skip = 0;
b59ec1
+		return top;
b59ec1
+	}
b59ec1
+
b59ec1
+	for (i = 0, total = 0; i < ctx->stacked_ll_count; i++) {
b59ec1
+		const struct proto_desc *stacked;
b59ec1
+
b59ec1
+		stacked = ctx->stacked_ll[i];
b59ec1
+		if (payload_offset < stacked->length) {
b59ec1
+			*skip = total;
b59ec1
+			return stacked;
b59ec1
+		}
b59ec1
+
b59ec1
+		payload_offset -= stacked->length;
b59ec1
+		total += stacked->length;
b59ec1
+	}
b59ec1
+
b59ec1
+	*skip = total;
b59ec1
+	return top;
b59ec1
+}
b59ec1
+
b59ec1
 /**
b59ec1
  * payload_expr_complete - fill in type information of a raw payload expr
b59ec1
  *
b59ec1
@@ -880,9 +917,10 @@ void exthdr_dependency_kill(struct payload_dep_ctx *ctx, struct expr *expr,
b59ec1
  */
b59ec1
 void payload_expr_complete(struct expr *expr, const struct proto_ctx *ctx)
b59ec1
 {
b59ec1
+	unsigned int payload_offset = expr->payload.offset;
b59ec1
 	const struct proto_desc *desc;
b59ec1
 	const struct proto_hdr_template *tmpl;
b59ec1
-	unsigned int i;
b59ec1
+	unsigned int i, total;
b59ec1
 
b59ec1
 	assert(expr->etype == EXPR_PAYLOAD);
b59ec1
 
b59ec1
@@ -891,9 +929,12 @@ void payload_expr_complete(struct expr *expr, const struct proto_ctx *ctx)
b59ec1
 		return;
b59ec1
 	assert(desc->base == expr->payload.base);
b59ec1
 
b59ec1
+	desc = get_stacked_desc(ctx, desc, expr, &total);
b59ec1
+	payload_offset -= total;
b59ec1
+
b59ec1
 	for (i = 0; i < array_size(desc->templates); i++) {
b59ec1
 		tmpl = &desc->templates[i];
b59ec1
-		if (tmpl->offset != expr->payload.offset ||
b59ec1
+		if (tmpl->offset != payload_offset ||
b59ec1
 		    tmpl->len    != expr->len)
b59ec1
 			continue;
b59ec1
 
b59ec1
@@ -950,6 +991,7 @@ bool payload_expr_trim(struct expr *expr, struct expr *mask,
b59ec1
 	unsigned int payload_len = expr->len;
b59ec1
 	const struct proto_desc *desc;
b59ec1
 	unsigned int off, i, len = 0;
b59ec1
+	unsigned int total;
b59ec1
 
b59ec1
 	assert(expr->etype == EXPR_PAYLOAD);
b59ec1
 
b59ec1
@@ -959,10 +1001,8 @@ bool payload_expr_trim(struct expr *expr, struct expr *mask,
b59ec1
 
b59ec1
 	assert(desc->base == expr->payload.base);
b59ec1
 
b59ec1
-	if (ctx->protocol[expr->payload.base].offset) {
b59ec1
-		assert(payload_offset >= ctx->protocol[expr->payload.base].offset);
b59ec1
-		payload_offset -= ctx->protocol[expr->payload.base].offset;
b59ec1
-	}
b59ec1
+	desc = get_stacked_desc(ctx, desc, expr, &total);
b59ec1
+	payload_offset -= total;
b59ec1
 
b59ec1
 	off = round_up(mask->len, BITS_PER_BYTE) - mask_len;
b59ec1
 	payload_offset += off;
b59ec1
@@ -1009,10 +1049,11 @@ bool payload_expr_trim(struct expr *expr, struct expr *mask,
b59ec1
 void payload_expr_expand(struct list_head *list, struct expr *expr,
b59ec1
 			 const struct proto_ctx *ctx)
b59ec1
 {
b59ec1
+	unsigned int payload_offset = expr->payload.offset;
b59ec1
 	const struct proto_hdr_template *tmpl;
b59ec1
 	const struct proto_desc *desc;
b59ec1
+	unsigned int i, total;
b59ec1
 	struct expr *new;
b59ec1
-	unsigned int i;
b59ec1
 
b59ec1
 	assert(expr->etype == EXPR_PAYLOAD);
b59ec1
 
b59ec1
@@ -1021,13 +1062,16 @@ void payload_expr_expand(struct list_head *list, struct expr *expr,
b59ec1
 		goto raw;
b59ec1
 	assert(desc->base == expr->payload.base);
b59ec1
 
b59ec1
+	desc = get_stacked_desc(ctx, desc, expr, &total);
b59ec1
+	payload_offset -= total;
b59ec1
+
b59ec1
 	for (i = 1; i < array_size(desc->templates); i++) {
b59ec1
 		tmpl = &desc->templates[i];
b59ec1
 
b59ec1
 		if (tmpl->len == 0)
b59ec1
 			break;
b59ec1
 
b59ec1
-		if (tmpl->offset != expr->payload.offset)
b59ec1
+		if (tmpl->offset != payload_offset)
b59ec1
 			continue;
b59ec1
 
b59ec1
 		if (tmpl->icmp_dep && ctx->th_dep.icmp.type &&
b59ec1
@@ -1039,6 +1083,7 @@ void payload_expr_expand(struct list_head *list, struct expr *expr,
b59ec1
 			list_add_tail(&new->list, list);
b59ec1
 			expr->len	     -= tmpl->len;
b59ec1
 			expr->payload.offset += tmpl->len;
b59ec1
+			payload_offset       += tmpl->len;
b59ec1
 			if (expr->len == 0)
b59ec1
 				return;
b59ec1
 		} else if (expr->len > 0) {
b59ec1
@@ -1051,7 +1096,7 @@ void payload_expr_expand(struct list_head *list, struct expr *expr,
b59ec1
 	}
b59ec1
 raw:
b59ec1
 	new = payload_expr_alloc(&expr->location, NULL, 0);
b59ec1
-	payload_init_raw(new, expr->payload.base, expr->payload.offset,
b59ec1
+	payload_init_raw(new, expr->payload.base, payload_offset,
b59ec1
 			 expr->len);
b59ec1
 	list_add_tail(&new->list, list);
b59ec1
 }
b59ec1
diff --git a/src/proto.c b/src/proto.c
b59ec1
index a013a00..2663f21 100644
b59ec1
--- a/src/proto.c
b59ec1
+++ b/src/proto.c
b59ec1
@@ -160,8 +160,6 @@ static void proto_ctx_debug(const struct proto_ctx *ctx, enum proto_bases base,
b59ec1
 			 proto_base_names[i],
b59ec1
 			 ctx->protocol[i].desc ? ctx->protocol[i].desc->name :
b59ec1
 						 "none");
b59ec1
-		if (ctx->protocol[i].offset)
b59ec1
-			pr_debug(" (offset: %u)", ctx->protocol[i].offset);
b59ec1
 		if (i == base)
b59ec1
 			pr_debug(" <-");
b59ec1
 		pr_debug("\n");
b59ec1
-- 
b59ec1
2.39.1
b59ec1