nft_set_elem_destroy() callers need to hold the transaction mutex for maps holding chain or object references. This helper is also called from the nft_dynset error path, without mutex. nft_dynset doesn't support verdict or objref maps, however. Signed-off-by: Florian Westphal <fw@xxxxxxxxx> --- net/netfilter/nf_tables_api.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index bac5847a5499..d6448d6e9a18 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -6404,7 +6404,19 @@ static void nft_set_elem_expr_destroy(const struct nft_ctx *ctx, __nft_set_elem_expr_destroy(ctx, expr); } -/* Drop references and destroy. Called from gc, dynset and abort path. */ +/** + * nft_set_elem_destroy - free a set element instantly + * @set: the set the element was supposed to be added to + * @elem: the private element pointer to be free'd + * @destroy_expr: true if embedded expression was initialised before + * + * Immediately releases an element without going through any synchronization. + * This function can only be used for error unwinding BEFORE the element was + * added to the set, else concurrent data path access may result in + * use-after-free. + * For datapath error unwinding, jumps-to-chain or objref are + * not supported. + */ void nft_set_elem_destroy(const struct nft_set *set, const struct nft_elem_priv *elem_priv, bool destroy_expr) @@ -6415,6 +6427,13 @@ void nft_set_elem_destroy(const struct nft_set *set, .family = set->table->family, }; + /* We can only do error unwind for vmaps or objref types + * if the caller is holding the transaction mutex. + */ + if (set->dtype == NFT_DATA_VERDICT || + nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) + WARN_ON_ONCE(!lockdep_commit_lock_is_held(read_pnet(&set->net))); + nft_data_release(nft_set_ext_key(ext), NFT_DATA_VALUE); if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) nft_data_release(nft_set_ext_data(ext), set->dtype); -- 2.43.0