* [PATCH net-next v2 1/5] net/sched: wrap open coded Qdics class filter counter
2023-07-28 15:35 [PATCH net-next v2 0/5] net/sched: improve class lifetime handling Pedro Tammela
@ 2023-07-28 15:35 ` Pedro Tammela
2023-07-28 15:35 ` [PATCH net-next v2 2/5] net/sched: sch_drr: warn about class in use while deleting Pedro Tammela
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Pedro Tammela @ 2023-07-28 15:35 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, jhs, xiyou.wangcong, jiri,
Pedro Tammela
The 'filter_cnt' counter is used to control a Qdisc class lifetime.
Each filter referecing this class by its id will eventually
increment/decrement this counter in their respective
'add/update/delete' routines.
As these operations are always serialized under rtnl lock, we don't
need an atomic type like 'refcount_t'.
It also means that we lose the overflow/underflow checks already
present in refcount_t, which are valuable to hunt down bugs
where the unsigned counter wraps around as it aids automated tools
like syzkaller to scream in such situations.
Wrap the open coded increment/decrement into helper functions and
add overflow checks to the operations.
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: Pedro Tammela <pctammela@mojatatu.com>
---
include/net/sch_generic.h | 26 ++++++++++++++++++++++++++
net/sched/sch_drr.c | 9 ++++-----
net/sched/sch_hfsc.c | 8 ++++----
net/sched/sch_htb.c | 8 +++-----
net/sched/sch_qfq.c | 10 ++++------
5 files changed, 41 insertions(+), 20 deletions(-)
diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h
index 15be2d96b06d..f232512505f8 100644
--- a/include/net/sch_generic.h
+++ b/include/net/sch_generic.h
@@ -599,6 +599,7 @@ get_default_qdisc_ops(const struct net_device *dev, int ntx)
struct Qdisc_class_common {
u32 classid;
+ unsigned int filter_cnt;
struct hlist_node hnode;
};
@@ -633,6 +634,31 @@ qdisc_class_find(const struct Qdisc_class_hash *hash, u32 id)
return NULL;
}
+static inline bool qdisc_class_in_use(const struct Qdisc_class_common *cl)
+{
+ return cl->filter_cnt > 0;
+}
+
+static inline void qdisc_class_get(struct Qdisc_class_common *cl)
+{
+ unsigned int res;
+
+ if (check_add_overflow(cl->filter_cnt, 1, &res))
+ WARN(1, "Qdisc class overflow");
+
+ cl->filter_cnt = res;
+}
+
+static inline void qdisc_class_put(struct Qdisc_class_common *cl)
+{
+ unsigned int res;
+
+ if (check_sub_overflow(cl->filter_cnt, 1, &res))
+ WARN(1, "Qdisc class underflow");
+
+ cl->filter_cnt = res;
+}
+
static inline int tc_classid_to_hwtc(struct net_device *dev, u32 classid)
{
u32 hwtc = TC_H_MIN(classid) - TC_H_MIN_PRIORITY;
diff --git a/net/sched/sch_drr.c b/net/sched/sch_drr.c
index e35a4e90f4e6..1b22b3b741c9 100644
--- a/net/sched/sch_drr.c
+++ b/net/sched/sch_drr.c
@@ -17,7 +17,6 @@
struct drr_class {
struct Qdisc_class_common common;
- unsigned int filter_cnt;
struct gnet_stats_basic_sync bstats;
struct gnet_stats_queue qstats;
@@ -150,7 +149,7 @@ static int drr_delete_class(struct Qdisc *sch, unsigned long arg,
struct drr_sched *q = qdisc_priv(sch);
struct drr_class *cl = (struct drr_class *)arg;
- if (cl->filter_cnt > 0)
+ if (qdisc_class_in_use(&cl->common))
return -EBUSY;
sch_tree_lock(sch);
@@ -187,8 +186,8 @@ static unsigned long drr_bind_tcf(struct Qdisc *sch, unsigned long parent,
{
struct drr_class *cl = drr_find_class(sch, classid);
- if (cl != NULL)
- cl->filter_cnt++;
+ if (cl)
+ qdisc_class_get(&cl->common);
return (unsigned long)cl;
}
@@ -197,7 +196,7 @@ static void drr_unbind_tcf(struct Qdisc *sch, unsigned long arg)
{
struct drr_class *cl = (struct drr_class *)arg;
- cl->filter_cnt--;
+ qdisc_class_put(&cl->common);
}
static int drr_graft_class(struct Qdisc *sch, unsigned long arg,
diff --git a/net/sched/sch_hfsc.c b/net/sched/sch_hfsc.c
index 70b0c5873d32..896cb401fdb9 100644
--- a/net/sched/sch_hfsc.c
+++ b/net/sched/sch_hfsc.c
@@ -116,7 +116,6 @@ struct hfsc_class {
struct net_rate_estimator __rcu *rate_est;
struct tcf_proto __rcu *filter_list; /* filter list */
struct tcf_block *block;
- unsigned int filter_cnt; /* filter count */
unsigned int level; /* class level in hierarchy */
struct hfsc_sched *sched; /* scheduler data */
@@ -1094,7 +1093,8 @@ hfsc_delete_class(struct Qdisc *sch, unsigned long arg,
struct hfsc_sched *q = qdisc_priv(sch);
struct hfsc_class *cl = (struct hfsc_class *)arg;
- if (cl->level > 0 || cl->filter_cnt > 0 || cl == &q->root)
+ if (cl->level > 0 || qdisc_class_in_use(&cl->cl_common) ||
+ cl == &q->root)
return -EBUSY;
sch_tree_lock(sch);
@@ -1223,7 +1223,7 @@ hfsc_bind_tcf(struct Qdisc *sch, unsigned long parent, u32 classid)
if (cl != NULL) {
if (p != NULL && p->level <= cl->level)
return 0;
- cl->filter_cnt++;
+ qdisc_class_get(&cl->cl_common);
}
return (unsigned long)cl;
@@ -1234,7 +1234,7 @@ hfsc_unbind_tcf(struct Qdisc *sch, unsigned long arg)
{
struct hfsc_class *cl = (struct hfsc_class *)arg;
- cl->filter_cnt--;
+ qdisc_class_put(&cl->cl_common);
}
static struct tcf_block *hfsc_tcf_block(struct Qdisc *sch, unsigned long arg,
diff --git a/net/sched/sch_htb.c b/net/sched/sch_htb.c
index 333800a7d4eb..05c8291865ae 100644
--- a/net/sched/sch_htb.c
+++ b/net/sched/sch_htb.c
@@ -102,7 +102,6 @@ struct htb_class {
struct tcf_proto __rcu *filter_list; /* class attached filters */
struct tcf_block *block;
- int filter_cnt;
int level; /* our level (see above) */
unsigned int children;
@@ -1710,7 +1709,7 @@ static int htb_delete(struct Qdisc *sch, unsigned long arg,
* tc subsys guarantee us that in htb_destroy it holds no class
* refs so that we can remove children safely there ?
*/
- if (cl->children || cl->filter_cnt)
+ if (cl->children || qdisc_class_in_use(&cl->common))
return -EBUSY;
if (!cl->level && htb_parent_last_child(cl))
@@ -2107,7 +2106,7 @@ static unsigned long htb_bind_filter(struct Qdisc *sch, unsigned long parent,
* be broken by class during destroy IIUC.
*/
if (cl)
- cl->filter_cnt++;
+ qdisc_class_get(&cl->common);
return (unsigned long)cl;
}
@@ -2115,8 +2114,7 @@ static void htb_unbind_filter(struct Qdisc *sch, unsigned long arg)
{
struct htb_class *cl = (struct htb_class *)arg;
- if (cl)
- cl->filter_cnt--;
+ qdisc_class_put(&cl->common);
}
static void htb_walk(struct Qdisc *sch, struct qdisc_walker *arg)
diff --git a/net/sched/sch_qfq.c b/net/sched/sch_qfq.c
index befaf74b33ca..7addc15f01b5 100644
--- a/net/sched/sch_qfq.c
+++ b/net/sched/sch_qfq.c
@@ -130,8 +130,6 @@ struct qfq_aggregate;
struct qfq_class {
struct Qdisc_class_common common;
- unsigned int filter_cnt;
-
struct gnet_stats_basic_sync bstats;
struct gnet_stats_queue qstats;
struct net_rate_estimator __rcu *rate_est;
@@ -545,7 +543,7 @@ static int qfq_delete_class(struct Qdisc *sch, unsigned long arg,
struct qfq_sched *q = qdisc_priv(sch);
struct qfq_class *cl = (struct qfq_class *)arg;
- if (cl->filter_cnt > 0)
+ if (qdisc_class_in_use(&cl->common))
return -EBUSY;
sch_tree_lock(sch);
@@ -580,8 +578,8 @@ static unsigned long qfq_bind_tcf(struct Qdisc *sch, unsigned long parent,
{
struct qfq_class *cl = qfq_find_class(sch, classid);
- if (cl != NULL)
- cl->filter_cnt++;
+ if (cl)
+ qdisc_class_get(&cl->common);
return (unsigned long)cl;
}
@@ -590,7 +588,7 @@ static void qfq_unbind_tcf(struct Qdisc *sch, unsigned long arg)
{
struct qfq_class *cl = (struct qfq_class *)arg;
- cl->filter_cnt--;
+ qdisc_class_put(&cl->common);
}
static int qfq_graft_class(struct Qdisc *sch, unsigned long arg,
--
2.39.2
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH net-next v2 2/5] net/sched: sch_drr: warn about class in use while deleting
2023-07-28 15:35 [PATCH net-next v2 0/5] net/sched: improve class lifetime handling Pedro Tammela
2023-07-28 15:35 ` [PATCH net-next v2 1/5] net/sched: wrap open coded Qdics class filter counter Pedro Tammela
@ 2023-07-28 15:35 ` Pedro Tammela
2023-07-28 15:35 ` [PATCH net-next v2 3/5] net/sched: sch_hfsc: " Pedro Tammela
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Pedro Tammela @ 2023-07-28 15:35 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, jhs, xiyou.wangcong, jiri,
Pedro Tammela
Add extack to warn that delete was rejected because
the class is still in use
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: Pedro Tammela <pctammela@mojatatu.com>
---
net/sched/sch_drr.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/net/sched/sch_drr.c b/net/sched/sch_drr.c
index 1b22b3b741c9..19901e77cd3b 100644
--- a/net/sched/sch_drr.c
+++ b/net/sched/sch_drr.c
@@ -149,8 +149,10 @@ static int drr_delete_class(struct Qdisc *sch, unsigned long arg,
struct drr_sched *q = qdisc_priv(sch);
struct drr_class *cl = (struct drr_class *)arg;
- if (qdisc_class_in_use(&cl->common))
+ if (qdisc_class_in_use(&cl->common)) {
+ NL_SET_ERR_MSG(extack, "DRR class is in use");
return -EBUSY;
+ }
sch_tree_lock(sch);
--
2.39.2
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH net-next v2 3/5] net/sched: sch_hfsc: warn about class in use while deleting
2023-07-28 15:35 [PATCH net-next v2 0/5] net/sched: improve class lifetime handling Pedro Tammela
2023-07-28 15:35 ` [PATCH net-next v2 1/5] net/sched: wrap open coded Qdics class filter counter Pedro Tammela
2023-07-28 15:35 ` [PATCH net-next v2 2/5] net/sched: sch_drr: warn about class in use while deleting Pedro Tammela
@ 2023-07-28 15:35 ` Pedro Tammela
2023-07-28 15:35 ` [PATCH net-next v2 4/5] net/sched: sch_htb: " Pedro Tammela
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Pedro Tammela @ 2023-07-28 15:35 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, jhs, xiyou.wangcong, jiri,
Pedro Tammela
Add extack to warn that delete was rejected because
the class is still in use
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: Pedro Tammela <pctammela@mojatatu.com>
---
net/sched/sch_hfsc.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/net/sched/sch_hfsc.c b/net/sched/sch_hfsc.c
index 896cb401fdb9..98805303218d 100644
--- a/net/sched/sch_hfsc.c
+++ b/net/sched/sch_hfsc.c
@@ -1094,8 +1094,10 @@ hfsc_delete_class(struct Qdisc *sch, unsigned long arg,
struct hfsc_class *cl = (struct hfsc_class *)arg;
if (cl->level > 0 || qdisc_class_in_use(&cl->cl_common) ||
- cl == &q->root)
+ cl == &q->root) {
+ NL_SET_ERR_MSG(extack, "HFSC class in use");
return -EBUSY;
+ }
sch_tree_lock(sch);
--
2.39.2
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH net-next v2 4/5] net/sched: sch_htb: warn about class in use while deleting
2023-07-28 15:35 [PATCH net-next v2 0/5] net/sched: improve class lifetime handling Pedro Tammela
` (2 preceding siblings ...)
2023-07-28 15:35 ` [PATCH net-next v2 3/5] net/sched: sch_hfsc: " Pedro Tammela
@ 2023-07-28 15:35 ` Pedro Tammela
2023-07-28 15:35 ` [PATCH net-next v2 5/5] net/sched: sch_qfq: " Pedro Tammela
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Pedro Tammela @ 2023-07-28 15:35 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, jhs, xiyou.wangcong, jiri,
Pedro Tammela
Add extack to warn that delete was rejected because
the class is still in use
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: Pedro Tammela <pctammela@mojatatu.com>
---
net/sched/sch_htb.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/net/sched/sch_htb.c b/net/sched/sch_htb.c
index 05c8291865ae..0d947414e616 100644
--- a/net/sched/sch_htb.c
+++ b/net/sched/sch_htb.c
@@ -1709,8 +1709,10 @@ static int htb_delete(struct Qdisc *sch, unsigned long arg,
* tc subsys guarantee us that in htb_destroy it holds no class
* refs so that we can remove children safely there ?
*/
- if (cl->children || qdisc_class_in_use(&cl->common))
+ if (cl->children || qdisc_class_in_use(&cl->common)) {
+ NL_SET_ERR_MSG(extack, "HTB class in use");
return -EBUSY;
+ }
if (!cl->level && htb_parent_last_child(cl))
last_child = 1;
--
2.39.2
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH net-next v2 5/5] net/sched: sch_qfq: warn about class in use while deleting
2023-07-28 15:35 [PATCH net-next v2 0/5] net/sched: improve class lifetime handling Pedro Tammela
` (3 preceding siblings ...)
2023-07-28 15:35 ` [PATCH net-next v2 4/5] net/sched: sch_htb: " Pedro Tammela
@ 2023-07-28 15:35 ` Pedro Tammela
2023-07-31 7:31 ` [PATCH net-next v2 0/5] net/sched: improve class lifetime handling Simon Horman
2023-08-01 9:10 ` patchwork-bot+netdevbpf
6 siblings, 0 replies; 8+ messages in thread
From: Pedro Tammela @ 2023-07-28 15:35 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, jhs, xiyou.wangcong, jiri,
Pedro Tammela
Add extack to warn that delete was rejected because
the class is still in use
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: Pedro Tammela <pctammela@mojatatu.com>
---
net/sched/sch_qfq.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/net/sched/sch_qfq.c b/net/sched/sch_qfq.c
index 7addc15f01b5..1a25752f1a9a 100644
--- a/net/sched/sch_qfq.c
+++ b/net/sched/sch_qfq.c
@@ -543,8 +543,10 @@ static int qfq_delete_class(struct Qdisc *sch, unsigned long arg,
struct qfq_sched *q = qdisc_priv(sch);
struct qfq_class *cl = (struct qfq_class *)arg;
- if (qdisc_class_in_use(&cl->common))
+ if (qdisc_class_in_use(&cl->common)) {
+ NL_SET_ERR_MSG_MOD(extack, "QFQ class in use");
return -EBUSY;
+ }
sch_tree_lock(sch);
--
2.39.2
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH net-next v2 0/5] net/sched: improve class lifetime handling
2023-07-28 15:35 [PATCH net-next v2 0/5] net/sched: improve class lifetime handling Pedro Tammela
` (4 preceding siblings ...)
2023-07-28 15:35 ` [PATCH net-next v2 5/5] net/sched: sch_qfq: " Pedro Tammela
@ 2023-07-31 7:31 ` Simon Horman
2023-08-01 9:10 ` patchwork-bot+netdevbpf
6 siblings, 0 replies; 8+ messages in thread
From: Simon Horman @ 2023-07-31 7:31 UTC (permalink / raw)
To: Pedro Tammela
Cc: netdev, davem, edumazet, kuba, pabeni, jhs, xiyou.wangcong, jiri
On Fri, Jul 28, 2023 at 12:35:32PM -0300, Pedro Tammela wrote:
> Valis says[0]:
> ============
> Three classifiers (cls_fw, cls_u32 and cls_route) always copy
> tcf_result struct into the new instance of the filter on update.
>
> This causes a problem when updating a filter bound to a class,
> as tcf_unbind_filter() is always called on the old instance in the
> success path, decreasing filter_cnt of the still referenced class
> and allowing it to be deleted, leading to a use-after-free.
> ============
>
> Turns out these could have been spotted easily with proper warnings.
> Improve the current class lifetime with wrappers that check for
> overflow/underflow.
>
> While at it add an extack for when a class in use is deleted.
>
> [0] https://lore.kernel.org/all/20230721174856.3045-1-sec@valis.email/
>
> v1 -> v2:
> - Add ack tag from Jamal
> - Move definitions to sch_generic.h as suggested by Cong
Reviewed-by: Simon Horman <horms@kernel.org>
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH net-next v2 0/5] net/sched: improve class lifetime handling
2023-07-28 15:35 [PATCH net-next v2 0/5] net/sched: improve class lifetime handling Pedro Tammela
` (5 preceding siblings ...)
2023-07-31 7:31 ` [PATCH net-next v2 0/5] net/sched: improve class lifetime handling Simon Horman
@ 2023-08-01 9:10 ` patchwork-bot+netdevbpf
6 siblings, 0 replies; 8+ messages in thread
From: patchwork-bot+netdevbpf @ 2023-08-01 9:10 UTC (permalink / raw)
To: Pedro Tammela
Cc: netdev, davem, edumazet, kuba, pabeni, jhs, xiyou.wangcong, jiri
Hello:
This series was applied to netdev/net-next.git (main)
by Paolo Abeni <pabeni@redhat.com>:
On Fri, 28 Jul 2023 12:35:32 -0300 you wrote:
> Valis says[0]:
> ============
> Three classifiers (cls_fw, cls_u32 and cls_route) always copy
> tcf_result struct into the new instance of the filter on update.
>
> This causes a problem when updating a filter bound to a class,
> as tcf_unbind_filter() is always called on the old instance in the
> success path, decreasing filter_cnt of the still referenced class
> and allowing it to be deleted, leading to a use-after-free.
> ============
>
> [...]
Here is the summary with links:
- [net-next,v2,1/5] net/sched: wrap open coded Qdics class filter counter
https://git.kernel.org/netdev/net-next/c/8798481b667f
- [net-next,v2,2/5] net/sched: sch_drr: warn about class in use while deleting
https://git.kernel.org/netdev/net-next/c/daf8d9181b9b
- [net-next,v2,3/5] net/sched: sch_hfsc: warn about class in use while deleting
https://git.kernel.org/netdev/net-next/c/8e4553ef3ed5
- [net-next,v2,4/5] net/sched: sch_htb: warn about class in use while deleting
https://git.kernel.org/netdev/net-next/c/7118f56e04d4
- [net-next,v2,5/5] net/sched: sch_qfq: warn about class in use while deleting
https://git.kernel.org/netdev/net-next/c/e20e75017c5a
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 8+ messages in thread