Message ID | 20240627135330.17039-3-fw@strlen.de |
---|---|
State | RFC |
Headers | show |
Series | nf_tables: remove explicit register zeroing | expand |
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, ®); > 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);
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 --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, ®); 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,
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(-)