diff mbox series

[RFC,nf-next,2/4] netfilter: nf_tables: allow loads only when register is initialized

Message ID 20240627135330.17039-3-fw@strlen.de
State RFC
Headers show
Series nf_tables: remove explicit register zeroing | expand

Commit Message

Florian Westphal June 27, 2024, 1:53 p.m. UTC
Reject rules where a load occurs from a register that has not seen a store
early in the same rule.

commit 4c905f6740a3 ("netfilter: nf_tables: initialize registers in
nft_do_chain()")
had to add a unconditional memset to the nftables register space to avoid
leaking stack information to userspace.

This memset shows up in benchmarks.  After this change, this commit can
be reverted again.

Note that this breaks userspace compatibility, because theoretically
you can do

  rule 1: reg2 := meta load iif, reg2  == 1 jump ...
  rule 2: reg2 == 2 jump ...   // read access with no store in this rule

... after this change this is rejected.

Neither nftables nor iptables-nft generate such rules, each rule is
always standalone.

This resuts in a small increase of nft_ctx structure by sizeof(long).

To cope with hypothetical rulesets like the example above we could emit
on-demand "reg[x] = 0" store when generating the datapath blob in
nf_tables_commit_chain_prepare().  Followup patch adds this.

Signed-off-by: Florian Westphal <fw@strlen.de>
---
 include/net/netfilter/nf_tables.h |  1 +
 net/netfilter/nf_tables_api.c     | 38 +++++++++++++++++++++++++++----
 2 files changed, 35 insertions(+), 4 deletions(-)

Comments

Pablo Neira Ayuso July 1, 2024, 8:31 p.m. UTC | #1
On Thu, Jun 27, 2024 at 03:53:22PM +0200, Florian Westphal wrote:
> @@ -11105,8 +11107,8 @@ static int nft_validate_register_load(enum nft_registers reg, unsigned int len)
>  int nft_parse_register_load(const struct nft_ctx *ctx,
>  			    const struct nlattr *attr, u8 *sreg, u32 len)
>  {
> -	u32 reg;
> -	int err;
> +	int err, invalid_reg;
> +	u32 reg, next_register;
>  
>  	err = nft_parse_register(attr, &reg);
>  	if (err < 0)
> @@ -11116,11 +11118,36 @@ int nft_parse_register_load(const struct nft_ctx *ctx,
>  	if (err < 0)
>  		return err;
>  
> +	next_register = DIV_ROUND_UP(len, NFT_REG32_SIZE) + reg;
> +
> +	/* Can't happen: nft_validate_register_load() should have failed */
> +	if (WARN_ON_ONCE(next_register > NFT_REG32_NUM))
> +		return -EINVAL;
> +
> +	/* find first register that did not see an earlier store. */
> +	invalid_reg = find_next_zero_bit(ctx->reg_inited, NFT_REG32_NUM, reg);

Is this assuming that register allocation from userspace is done secuencially?

> +	/* invalid register within the range that we're loading from? */
> +	if (invalid_reg < next_register)
> +		return -ENODATA;
> +
>  	*sreg = reg;
>  	return 0;
>  }
>  EXPORT_SYMBOL_GPL(nft_parse_register_load);
Florian Westphal July 1, 2024, 10:16 p.m. UTC | #2
Pablo Neira Ayuso <pablo@netfilter.org> wrote:
> > +	next_register = DIV_ROUND_UP(len, NFT_REG32_SIZE) + reg;
> > +
> > +	/* Can't happen: nft_validate_register_load() should have failed */
> > +	if (WARN_ON_ONCE(next_register > NFT_REG32_NUM))
> > +		return -EINVAL;
> > +
> > +	/* find first register that did not see an earlier store. */
> > +	invalid_reg = find_next_zero_bit(ctx->reg_inited, NFT_REG32_NUM, reg);
> 
> Is this assuming that register allocation from userspace is done secuencially?

No, that would be a bug.

Each set bit represents a register, if the bit is 1, the register
saw a store.

The above is the load check: load is from register "reg", and we
check the first reg that did not see a store (is 0), starting from
reg.  The result (register with undefined content) needs to be larger
than next_register, which is the register coming after the current
access (can be NFT_REG32_NUM, in that case no furhter registers exist
and access is ok).

> > +	/* invalid register within the range that we're loading from? */
> > +	if (invalid_reg < next_register)
> > +		return -ENODATA;
> > +

This means that in range the relevant access range
[reg,next_reg) the is at least on register that did not see a store.
diff mbox series

Patch

diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h
index 9a71fc20598b..3f06ec7dc500 100644
--- a/include/net/netfilter/nf_tables.h
+++ b/include/net/netfilter/nf_tables.h
@@ -221,6 +221,7 @@  struct nft_ctx {
 	u8				family;
 	u8				level;
 	bool				report;
+	DECLARE_BITMAP(reg_inited, NFT_REG32_NUM);
 };
 
 enum nft_data_desc_flags {
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index 7437b38269a5..c59a32ab9145 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -146,6 +146,8 @@  static void nft_ctx_init(struct nft_ctx *ctx,
 	ctx->report	= nlmsg_report(nlh);
 	ctx->flags	= nlh->nlmsg_flags;
 	ctx->seq	= nlh->nlmsg_seq;
+
+	bitmap_zero(ctx->reg_inited, NFT_REG32_NUM);
 }
 
 static struct nft_trans *nft_trans_alloc_gfp(const struct nft_ctx *ctx,
@@ -11105,8 +11107,8 @@  static int nft_validate_register_load(enum nft_registers reg, unsigned int len)
 int nft_parse_register_load(const struct nft_ctx *ctx,
 			    const struct nlattr *attr, u8 *sreg, u32 len)
 {
-	u32 reg;
-	int err;
+	int err, invalid_reg;
+	u32 reg, next_register;
 
 	err = nft_parse_register(attr, &reg);
 	if (err < 0)
@@ -11116,11 +11118,36 @@  int nft_parse_register_load(const struct nft_ctx *ctx,
 	if (err < 0)
 		return err;
 
+	next_register = DIV_ROUND_UP(len, NFT_REG32_SIZE) + reg;
+
+	/* Can't happen: nft_validate_register_load() should have failed */
+	if (WARN_ON_ONCE(next_register > NFT_REG32_NUM))
+		return -EINVAL;
+
+	/* find first register that did not see an earlier store. */
+	invalid_reg = find_next_zero_bit(ctx->reg_inited, NFT_REG32_NUM, reg);
+
+	/* invalid register within the range that we're loading from? */
+	if (invalid_reg < next_register)
+		return -ENODATA;
+
 	*sreg = reg;
 	return 0;
 }
 EXPORT_SYMBOL_GPL(nft_parse_register_load);
 
+static void nft_saw_register_store(const struct nft_ctx *__ctx,
+				   int reg, unsigned int len)
+{
+	unsigned int registers = DIV_ROUND_UP(len, NFT_REG32_SIZE);
+	struct nft_ctx *ctx = (struct nft_ctx *)__ctx;
+
+	if (WARN_ON_ONCE(len == 0 || reg < 0))
+		return;
+
+	bitmap_set(ctx->reg_inited, reg, registers);
+}
+
 static int nft_validate_register_store(const struct nft_ctx *ctx,
 				       enum nft_registers reg,
 				       const struct nft_data *data,
@@ -11142,7 +11169,7 @@  static int nft_validate_register_store(const struct nft_ctx *ctx,
 				return err;
 		}
 
-		return 0;
+		break;
 	default:
 		if (reg < NFT_REG_1 * NFT_REG_SIZE / NFT_REG32_SIZE)
 			return -EINVAL;
@@ -11154,8 +11181,11 @@  static int nft_validate_register_store(const struct nft_ctx *ctx,
 
 		if (data != NULL && type != NFT_DATA_VALUE)
 			return -EINVAL;
-		return 0;
+		break;
 	}
+
+	nft_saw_register_store(ctx, reg, len);
+	return 0;
 }
 
 int nft_parse_register_store(const struct nft_ctx *ctx,