net/bonding: division by zero in ALB Tx with no active member
https://bugs.dpdk.org/show_bug.cgi?id=2077
Bug ID: 2077
Summary: net/bonding: division by zero in ALB Tx with no active
member
Product: DPDK
Version: 26.11
Hardware: All
OS: All
Status: UNCONFIRMED
Severity: normal
Priority: Normal
Component: ethdev
Assignee: [email protected]
Reporter: [email protected]Target Milestone: ---
Created attachment 425
--> https://bugs.dpdk.org/attachment.cgi?id=425&action=edit
AI proposed patch
============================================================================
ANT-2026-EDSAQEWD [NOT SECURITY: ordinary bug]
net/bonding: division by zero in ALB Tx with no active member
Location: drivers/net/bonding/rte_eth_bond_alb.c:26 in calculate_member()
Commit: 9a45341f18a1ab47af7123e63d942b90c1b79f38
============================================================================
Transmitting an ARP reply on a mode 6 (ALB) bonding port while none of its members is active crashes the whole DPDK process with SIGFPE, taking down every port and queue it serves. Every other bonding mode simply returns 0 from the Tx burst in that state. The trigger is an ARP reply whose sender hardware address is the bond's own MAC, sent from start-up until the first member link comes up, or at any time all member links are down.
bond_ethdev_tx_burst_alb() in drivers/net/bonding/rte_eth_bond_pmd.c is the only Tx burst function without the active_member_count check that round robin, active backup, TLB, balance, 802.3ad and broadcast all have. It passes ARP frames to bond_mode_alb_arp_xmit(). For a REPLY whose arp_sha equals the bond's MAC and whose arp_sip/arp_tip pair has no client table entry yet, that function calls calculate_member() in rte_eth_bond_alb.c, which computes (last_member + 1) % active_member_count with a count of 0.
In the documented use of mode 6 the frame is the application's own reply, for example a gratuitous ARP sent at start-up before the link poll has activated a member. The attached reproducer hands one such reply to a mode 6 bond in testpmd (io forwarding, two net_af_packet members with links down). It stops with a division-by-zero FPE in calculate_member in about two seconds, and a plain gcc build is killed by SIGFPE the same way.
The division appears to date back to the commit that added mode 6 (06fe78b98ccd). A patch is attached that adds the missing check to the ALB Tx path, makes calculate_member() tolerate a count of 0, and adds a unit test.
## Details
This is an ordinary bug, not a security issue: it needs a mode 6 bond with no active member, a state no outside party can bring about and in which the bond carries no traffic anyway, and an ARP reply with the bond's own MAC as sender handed to the bond's transmit function, which in the documented use of mode 6 is a reply the application writes itself.
bond_ethdev_tx_burst_alb() processes a burst even when the bonding port has no
active member, and its ARP path computes an index modulo active_member_count. Entry point: rte_eth_tx_burst() on a mode 6 (ALB) bonding port. Tested at commit9a45341f18a1; the default build is affected.
Every other Tx burst function in drivers/net/bonding/rte_eth_bond_pmd.c returns
before touching the burst when there is no active member: round robin (:635), active backup (:687), TLB (:975), balance (:1260), 802.3ad (:1289), broadcast (:1380). The ALB function has no such test and goes from its declarations straight into the classification loop, drivers/net/bonding/rte_eth_bond_pmd.c:1063:
```c
int i, j;
/* Search tx buffer for ARP packets and forward them to alb */
for (i = 0; i < nb_pkts; i++) {
eth_h = rte_pktmbuf_mtod(bufs[i], struct rte_ether_hdr *);
ether_type = eth_h->ether_type;
offset = get_vlan_offset(eth_h, ðer_type);
if (ether_type == rte_cpu_to_be_16(RTE_ETHER_TYPE_ARP)) {
member_idx = bond_mode_alb_arp_xmit(eth_h, offset,
internals);
```bond_mode_alb_arp_xmit() (drivers/net/bonding/rte_eth_bond_alb.c:128) decides
on two fields of the ARP payload. A frame whose arp_sha differs from the bond's
MAC is assigned to the current primary port (:147) and nothing is divided. A frame whose arp_sha equals the bond's MAC and whose opcode is REPLY (:158) is
looked up in the client table by arp_tip; unless an entry with the same arp_sip/arp_tip pair already exists (:159-:178), a member is assigned to
the client, rte_eth_bond_alb.c:187:
```c
client_info->member_idx = calculate_member(internals);
```rte_eth_bond_alb.c:21:
```c
static uint16_t
calculate_member(struct bond_dev_private *internals)
{
uint16_t idx;
idx = (internals->mode6.last_member + 1) %
internals->active_member_count;
```With active_member_count == 0 this is an integer division by zero: SIGFPE on x86, which terminates the process.
active_member_count is 0 in ordinary operation. bond_ethdev_start() does not
fill active_members[]; a member is added only by activate_member() in bond_ethdev_lsc_event_callback() once its link is reported up (rte_eth_bond_pmd.c:3111) and removed again when its link goes down (:3124).
So the count is 0 from start until the first member link comes up, and whenever all member links are down. The bonding port reports link down in that state, but
nothing stops an application from calling rte_eth_tx_burst() on it, and with the other modes it gets 0 back.
Shown with testpmd: port 0 is net_pcap0, port 3 is a mode 6 bond over two net_af_packet members whose links are down, forwarding mode io. show bonding config 3 prints Active Members: []. The capture holds one 60-byte
ARP reply with the bond's MAC 02:aa:bb:cc:dd:01 as sender hardware address:
``` 02 00 00 00 00 0a 02 00 00 00 00 0b 08 06 dst, src, ARP 00 01 08 00 06 04 00 02 Ethernet/IPv4, REPLY 02 aa bb cc dd 01 0a 00 00 01 arp_sha = bond MAC, arp_sip 02 00 00 00 00 0a 0a 00 00 02 arp_tha, arp_tip (18 bytes of padding) ```
After start, /src/build-asan/app/dpdk-testpmd stops with:
```
../drivers/net/bonding/rte_eth_bond_alb.c:26:43: runtime error: division by
zero
==739==ERROR: AddressSanitizer: FPE on unknown address 0x56543ac40ef4 (pc
0x56543ac40ef4 ... T3)
#0 0x56543ac40ef4 in calculate_member
/src/build-asan/../drivers/net/bonding/rte_eth_bond_alb.c:26:43
#1 0x56543ac4271e in bond_mode_alb_arp_xmit
/src/build-asan/../drivers/net/bonding/rte_eth_bond_alb.c:187:29
#2 0x56543ac988d6 in bond_ethdev_tx_burst_alb
/src/build-asan/../drivers/net/bonding/rte_eth_bond_pmd.c:1072:17
#3 0x565434f9b0b9 in rte_eth_tx_burst
/src/build-asan/../lib/ethdev/rte_ethdev.h:6565:12
#4 0x565434f98f92 in common_fwd_stream_transmit
/src/build-asan/../app/test-pmd/testpmd.h:945:10
#5 0x565434f98b2f in pkt_burst_io_forward
/src/build-asan/../app/test-pmd/iofwd.c:57:2
SUMMARY: AddressSanitizer: FPE
/src/build-asan/../drivers/net/bonding/rte_eth_bond_alb.c:26:43 in
calculate_member
```/src/build/app/dpdk-testpmd (gcc, debugoptimized) with the same arguments is killed by SIGFPE, and the kernel logs trap divide error for dpdk-worker1. Two controls behave normally: the same frame with both member links up is forwarded, and an ARP reply that differs only in arp_sha (the sender's own MAC) is handled without a crash while the links are down. The mac= devarg is not needed; without it the bond has the first member's MAC and a frame carrying that address crashes testpmd the same way.
## Impact
One ARP reply transmitted on a mode 6 bonding port that has no active member kills the DPDK process (SIGFPE in a plain build), and with it every port and queue that process serves, where every other mode drops the burst. Nothing is corrupted. The frame needs arp_sha equal to the bond's MAC and an arp_sip/arp_tip pair without a client table entry.
ALB mode is described for ARP replies sent by the local system (drivers/net/bonding/rte_eth_bond.h:78), so in practice the frame is the application's own reply. An application meets the crash when it sends one while all member links are down: a gratuitous ARP sent at start-up before the first link poll has activated a member, or a reply sent during a total link outage. An application that answers ARP requests itself receives no requests through a bond with no active member, so ordinary neighbour traffic does not lead there; replies already in flight when the last link drops normally go out before the 10 ms link poll deactivates the member.
The testpmd io set-up above, which transmits received ARP frames on the bond unchanged, is only a convenient way to hand such a frame to the bond's transmit function; it is not a working deployment. With the links up, mode 6 rewrites the
Ethernet source and ARP sender address of every forwarded ARP reply to the bond's
MAC (drivers/net/bonding/rte_eth_bond_alb.c:146-150), so the hosts behind such a
forwarder would map every neighbour's address to the bond.
## Reproducing
repro runs the prebuilt /src/build-asan/app/dpdk-testpmd once; nothing is built. It needs root: it moves itself into a private network namespace (unshare(CLONE_NEWNET)) and creates two veth interfaces there, bond_m0 and bond_m1, which disappear with the script.
* repro.d/make_arp_reply_pcap.py writes a capture with one 60-byte ARP reply whose sender hardware address is its second argument. repro passes the
bond's
MAC 02:aa:bb:cc:dd:01 (the layout is the one shown in Details).
* repro is the setup, the testpmd run and the check. The veth interfaces are never set up, so net_af_packet, which reports link state from IFF_RUNNING, reports both members' links down and the bond activates
neither:
the state of a bond whose member links are all down. The mac= devarg only
makes
the bond's address known to the script; without it the bond takes the first member's MAC and a frame carrying that one crashes the same way.
The testpmd run is
```
dpdk-testpmd -l 0-1 --no-huge -m 512 --no-pci \
--vdev
net_pcap0,rx_pcap=/tmp/arp_reply_with_bond_mac.pcap,tx_pcap=/dev/null \
--vdev net_af_packet0,iface=bond_m0 --vdev net_af_packet1,iface=bond_m1 \
--vdev
net_bonding0,mode=6,member=net_af_packet0,member=net_af_packet1,mac=02:aa:bb:cc:dd:01
\
-- -i --total-num-mbufs=4096 --portlist=0,3 --no-flush-rx
```with show bonding config 3, set fwd io, start, stop, quit on stdin:
io forwarding hands the captured frame (port 0) to rte_eth_tx_burst() on the bond (port 3). --no-flush-rx keeps testpmd from discarding the frame before forwarding starts; stdbuf -oL keeps testpmd's stdout when it dies.
The script prints the Active Members line of show bonding config 3, the UBSan division by zero line, testpmd's exit status and the first four lines of
the AddressSanitizer report, and prints BUG when the bond had no active member
and the report is an FPE with calculate_member as frame #0 and bond_ethdev_tx_burst_alb as frame #2. Expected output (about 2 seconds):
```
OBSERVED show bonding config 3: ['Active Members: []']
OBSERVED UBSan: ['../drivers/net/bonding/rte_eth_bond_alb.c:26:43: runtime
error: division by zero']
OBSERVED testpmd exit status 1
FPE on unknown address 0x55c0cbffbef4 (pc 0x55c0cbffbef4 bp 0x7bc7a99a49f0 sp
0x7bc7a99a4940 T3)
#0 0x55c0cbffbef4 in calculate_member
/src/build-asan/../drivers/net/bonding/rte_eth_bond_alb.c:26:43
#1 0x55c0cbffd71e in bond_mode_alb_arp_xmit
/src/build-asan/../drivers/net/bonding/rte_eth_bond_alb.c:187:29
#2 0x55c0cc0538d6 in bond_ethdev_tx_burst_alb
/src/build-asan/../drivers/net/bonding/rte_eth_bond_pmd.c:1072:17
BUG
```The sanitizer only names the line: /src/build/app/dpdk-testpmd (gcc, debugoptimized) with the same arguments is killed by SIGFPE. With the attached patch applied and testpmd rebuilt, the bond drops the frame, testpmd reaches Bye... and repro prints OK.
## Suggested fix
The attached patch makes two changes in the driver and adds a unit test.
bond_ethdev_tx_burst_alb() gets the test its six siblings have, before the classification loop:
```diff
--- a/drivers/net/bonding/rte_eth_bond_pmd.c
+++ b/drivers/net/bonding/rte_eth_bond_pmd.c
@@ -1062,6 +1062,9 @@ bond_ethdev_tx_burst_alb(void *queue, struct rte_mbuf
**bufs, uint16_t nb_pkts)
int i, j;
+ if (internals->active_member_count < 1)
+ return 0;
+
/* Search tx buffer for ARP packets and forward them to alb */
for (i = 0; i < nb_pkts; i++) {
eth_h = rte_pktmbuf_mtod(bufs[i], struct rte_ether_hdr *);
```A bond with no active member now returns 0 and leaves the burst to the caller, as every other mode does.
calculate_member() (drivers/net/bonding/rte_eth_bond_alb.c:21) now reads active_member_count once into a local and returns internals->current_primary_port when it is 0. The link status callback, which does not run on the forwarding lcore, can deactivate the last member between the
new test and the modulo; round robin, balance, 802.3ad and broadcast avoid this by working on a local copy of the count, and this change closes the same window for ALB. It also covers the second caller, bond_mode_alb_arp_recv() (rte_eth_bond_alb.c:115), which the receive side reaches only through that race with the link status callback.
test_alb_tx_burst_no_active_member in app/test/test_link_bonding.c (part of link_bonding_autotest) starts an ALB bond with all member links down and transmits an ARP reply with the bond's MAC as sender; it dies with SIGFPE on the
unpatched driver and passes with the fix.
With the patch applied and rebuilt, repro prints OK, forwarding with both links up is unchanged and the bonding unit test suite passes. The patch is a plain diff; its commit message should carryFixes: 06fe78b98ccd ("bond: add mode 6"), the commit that introduced the division with the mode itself, and Cc: [email protected].
Signature · 2 lines
You are receiving this mail because: You are the assignee for the bug.