Message ID | 20231205172554.3570602-1-vladbu@nvidia.com (mailing list archive) |
---|---|
State | Accepted |
Commit | 125f1c7f26ffcdbf96177abe75b70c1a6ceb17bc |
Delegated to: | Netdev Maintainers |
Headers | show |
Series | [net] net/sched: act_ct: Take per-cb reference to tcf_ct_flow_table | expand |
On Tue, Dec 05, 2023 at 06:25:54PM +0100, Vlad Buslov wrote: > The referenced change added custom cleanup code to act_ct to delete any > callbacks registered on the parent block when deleting the > tcf_ct_flow_table instance. However, the underlying issue is that the > drivers don't obtain the reference to the tcf_ct_flow_table instance when > registering callbacks which means that not only driver callbacks may still > be on the table when deleting it but also that the driver can still have > pointers to its internal nf_flowtable and can use it concurrently which > results either warning in netfilter[0] or use-after-free. > > Fix the issue by taking a reference to the underlying struct > tcf_ct_flow_table instance when registering the callback and release the > reference when unregistering. Expose new API required for such reference > counting by adding two new callbacks to nf_flowtable_type and implementing > them for act_ct flowtable_ct type. This fixes the issue by extending the > lifetime of nf_flowtable until all users have unregistered. This definitely looks like a much better fix to me, thanks Vlad. We will do some checking our side just to confirm that it doesn't break anything with the nfp driver, but I don't expect that it should.
On Tue, 5 Dec 2023 18:25:54 +0100 Vlad Buslov wrote: > The referenced change added custom cleanup code to act_ct to delete any > callbacks registered on the parent block when deleting the > tcf_ct_flow_table instance. However, the underlying issue is that the > drivers don't obtain the reference to the tcf_ct_flow_table instance when > registering callbacks which means that not only driver callbacks may still > be on the table when deleting it but also that the driver can still have > pointers to its internal nf_flowtable and can use it concurrently which > results either warning in netfilter[0] or use-after-free. > > Fix the issue by taking a reference to the underlying struct > tcf_ct_flow_table instance when registering the callback and release the > reference when unregistering. Expose new API required for such reference > counting by adding two new callbacks to nf_flowtable_type and implementing > them for act_ct flowtable_ct type. This fixes the issue by extending the > lifetime of nf_flowtable until all users have unregistered. Some acks would be good here - Pablo, Jamal?
On Fri, Dec 08, 2023 at 03:40:35PM -0800, Jakub Kicinski wrote: > On Tue, 5 Dec 2023 18:25:54 +0100 Vlad Buslov wrote: > > The referenced change added custom cleanup code to act_ct to delete any > > callbacks registered on the parent block when deleting the > > tcf_ct_flow_table instance. However, the underlying issue is that the > > drivers don't obtain the reference to the tcf_ct_flow_table instance when > > registering callbacks which means that not only driver callbacks may still > > be on the table when deleting it but also that the driver can still have > > pointers to its internal nf_flowtable and can use it concurrently which > > results either warning in netfilter[0] or use-after-free. > > > > Fix the issue by taking a reference to the underlying struct > > tcf_ct_flow_table instance when registering the callback and release the > > reference when unregistering. Expose new API required for such reference > > counting by adding two new callbacks to nf_flowtable_type and implementing > > them for act_ct flowtable_ct type. This fixes the issue by extending the > > lifetime of nf_flowtable until all users have unregistered. > > Some acks would be good here - Pablo, Jamal? Acked-by: Pablo Neira Ayuso <pablo@netfilter.org> I'd prefer driver does not access nf_flowtable directly, I already mentioned this in the past.
Hello: This patch was applied to netdev/net.git (main) by David S. Miller <davem@davemloft.net>: On Tue, 5 Dec 2023 18:25:54 +0100 you wrote: > The referenced change added custom cleanup code to act_ct to delete any > callbacks registered on the parent block when deleting the > tcf_ct_flow_table instance. However, the underlying issue is that the > drivers don't obtain the reference to the tcf_ct_flow_table instance when > registering callbacks which means that not only driver callbacks may still > be on the table when deleting it but also that the driver can still have > pointers to its internal nf_flowtable and can use it concurrently which > results either warning in netfilter[0] or use-after-free. > > [...] Here is the summary with links: - [net] net/sched: act_ct: Take per-cb reference to tcf_ct_flow_table https://git.kernel.org/netdev/net/c/125f1c7f26ff You are awesome, thank you!
diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h index fe1507c1db82..692d5955911c 100644 --- a/include/net/netfilter/nf_flow_table.h +++ b/include/net/netfilter/nf_flow_table.h @@ -62,6 +62,8 @@ struct nf_flowtable_type { enum flow_offload_tuple_dir dir, struct nf_flow_rule *flow_rule); void (*free)(struct nf_flowtable *ft); + void (*get)(struct nf_flowtable *ft); + void (*put)(struct nf_flowtable *ft); nf_hookfn *hook; struct module *owner; }; @@ -240,6 +242,11 @@ nf_flow_table_offload_add_cb(struct nf_flowtable *flow_table, } list_add_tail(&block_cb->list, &block->cb_list); + up_write(&flow_table->flow_block_lock); + + if (flow_table->type->get) + flow_table->type->get(flow_table); + return 0; unlock: up_write(&flow_table->flow_block_lock); @@ -262,6 +269,9 @@ nf_flow_table_offload_del_cb(struct nf_flowtable *flow_table, WARN_ON(true); } up_write(&flow_table->flow_block_lock); + + if (flow_table->type->put) + flow_table->type->put(flow_table); } void flow_offload_route_init(struct flow_offload *flow, diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index b3f4a503ee2b..f69c47945175 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -286,9 +286,31 @@ static bool tcf_ct_flow_is_outdated(const struct flow_offload *flow) !test_bit(NF_FLOW_HW_ESTABLISHED, &flow->flags); } +static void tcf_ct_flow_table_get_ref(struct tcf_ct_flow_table *ct_ft); + +static void tcf_ct_nf_get(struct nf_flowtable *ft) +{ + struct tcf_ct_flow_table *ct_ft = + container_of(ft, struct tcf_ct_flow_table, nf_ft); + + tcf_ct_flow_table_get_ref(ct_ft); +} + +static void tcf_ct_flow_table_put(struct tcf_ct_flow_table *ct_ft); + +static void tcf_ct_nf_put(struct nf_flowtable *ft) +{ + struct tcf_ct_flow_table *ct_ft = + container_of(ft, struct tcf_ct_flow_table, nf_ft); + + tcf_ct_flow_table_put(ct_ft); +} + static struct nf_flowtable_type flowtable_ct = { .gc = tcf_ct_flow_is_outdated, .action = tcf_ct_flow_table_fill_actions, + .get = tcf_ct_nf_get, + .put = tcf_ct_nf_put, .owner = THIS_MODULE, }; @@ -337,9 +359,13 @@ static int tcf_ct_flow_table_get(struct net *net, struct tcf_ct_params *params) return err; } +static void tcf_ct_flow_table_get_ref(struct tcf_ct_flow_table *ct_ft) +{ + refcount_inc(&ct_ft->ref); +} + static void tcf_ct_flow_table_cleanup_work(struct work_struct *work) { - struct flow_block_cb *block_cb, *tmp_cb; struct tcf_ct_flow_table *ct_ft; struct flow_block *block; @@ -347,13 +373,9 @@ static void tcf_ct_flow_table_cleanup_work(struct work_struct *work) rwork); nf_flow_table_free(&ct_ft->nf_ft); - /* Remove any remaining callbacks before cleanup */ block = &ct_ft->nf_ft.flow_block; down_write(&ct_ft->nf_ft.flow_block_lock); - list_for_each_entry_safe(block_cb, tmp_cb, &block->cb_list, list) { - list_del(&block_cb->list); - flow_block_cb_free(block_cb); - } + WARN_ON(!list_empty(&block->cb_list)); up_write(&ct_ft->nf_ft.flow_block_lock); kfree(ct_ft);