ccm_tx_work_expired() uses interval_to_us() to convert the configured
exp_interval enum into a microsecond delay, then passes it to
queue_delayed_work() to schedule the next iteration. The ccm_tx_dwork
callback re-arms the same delayed_work struct at the end of each
invocation, forming a repeating timer.
interval_to_us() returns 0 for BR_CFM_CCM_INTERVAL_NONE and any
out-of-range enum value. When this 0 is passed to queue_delayed_work()
as the delay, the work item fires immediately and re-arms itself with
zero delay again, creating an infinite tight loop. Each iteration
allocates an skb via ccm_frame_build() and queues it for transmission.
The skbs pile up faster than the network stack can free them because the
worker never yields the CPU, rapidly exhausting all kernel memory until
OOM deadlock panic.
Since CC config and CCM TX are independent netlink commands that can be
issued in any order, there is no single configuration entry point where
rejecting interval=0 would cover all cases.
Fix this by checking the interval at the start of ccm_tx_work_expired()
and stopping transmission immediately if it is zero. Set period to 0 so
that br_cfm_cc_ccm_tx() correctly sees transmission as stopped and can
restart it later if a valid interval is configured. This also avoids
transmitting a CCM frame with an invalid interval value.
Fixes: a806ad8ee2aa ("bridge: cfm: Kernel space implementation of CFM. CCM
frame TX added.")
Reported-by: Weiming Shi <[email protected]>
Signed-off-by: Xiang Mei <[email protected]>
---
net/bridge/br_cfm.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/net/bridge/br_cfm.c b/net/bridge/br_cfm.c
index 118c7ea48c35..688c51250630 100644
--- a/net/bridge/br_cfm.c
+++ b/net/bridge/br_cfm.c
@@ -274,6 +274,13 @@ static void ccm_tx_work_expired(struct work_struct *work)
del_work = to_delayed_work(work);
mep = container_of(del_work, struct br_cfm_mep, ccm_tx_dwork);
+ interval_us = interval_to_us(mep->cc_config.exp_interval);
+ if (!interval_us) {
+ /* No valid interval - stop transmission */
+ mep->cc_ccm_tx_info.period = 0;
+ return;
+ }
+
if (time_before_eq(mep->ccm_tx_end, jiffies)) {
/* Transmission period has ended */
mep->cc_ccm_tx_info.period = 0;
@@ -284,7 +291,6 @@ static void ccm_tx_work_expired(struct work_struct *work)
if (skb)
ccm_frame_tx(skb);
- interval_us = interval_to_us(mep->cc_config.exp_interval);
queue_delayed_work(system_percpu_wq, &mep->ccm_tx_dwork,
usecs_to_jiffies(interval_us));
}
--
2.43.0