From: Nguyen Ngoc Thang <ngocthang2710.1999@gmail.com>
To: Chas Williams <3chas3@gmail.com>
Cc: "David S . Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Simon Horman <horms@kernel.org>,
netdev@vger.kernel.org, linux-atm-general@lists.sourceforge.net,
linux-kernel@vger.kernel.org
Subject: [PATCH] net/atm: fix shift-out-of-bounds in __vcc_connect()/find_ci()
Date: Sun, 27 Sep 2026 19:04:10 +0700 [thread overview]
Message-ID: <20260927120410.44906-1-ngocthang2710.1999@gmail.com> (raw)
dev->ci_range.vpi_bits and vci_bits are documented (1..8 and 1..16)
values a driver assigns after atm_dev_register(), with ATM_CI_MAX (-1)
meaning "no restriction". find_ci() and __vcc_connect() use them
directly as shift counts (1 << bits, x >> bits) without ever handling
either the -1 sentinel or the all-zero value the field holds between
atm_dev_register() making the device visible and the driver actually
setting ci_range.
usbatm_atm_init() has exactly that window: it calls atm_dev_register()
and only afterwards sets ci_range.vpi_bits/vci_bits. A bind() on
AF_ATMPVC/AF_ATMSVC racing in during that window sees a vci_bits (or
vpi_bits) value outside the documented range, and the plain shift is
undefined behaviour, reported by UBSAN as a negative shift exponent.
Add ci_range_bound(), which turns a vpi_bits/vci_bits value into the
exclusive upper bound of the VPI/VCI space, treating ATM_CI_MAX and
any other value outside the documented range as "use the maximum
range" instead of shifting by it. Use it at every site that currently
shifts by ci_range.vpi_bits/vci_bits. The __vcc_connect() bounds check
also gains an explicit vpi/vci < 0 check, since it used to rely on the
implementation-defined sign-extension of the old ">>" for rejecting
values other than the ATM_VPI/VCI_ANY/UNSPEC sentinels.
Reported-by: syzbot+f6ac161ee9699270b8dd@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=f6ac161ee9699270b8dd
Signed-off-by: Nguyen Ngoc Thang <ngocthang2710.1999@gmail.com>
---
No C reproducer was attached to the syzbot dashboard for this report,
so I converted the syz reproducer to C myself with syz-prog2c at the
syzkaller revision syzbot used, and ran it in QEMU (KVM, x86_64)
against syzbot's own kernel/config/disk image. It enumerates a fake
USB device over raw-gadget matching idVendor 0x0572 / idProduct 0xcafe
(binds drivers/usb/atm/cxacru.c), then races a
setsockopt(SO_ATMQOS)+bind(AF_ATMPVC) against it:
usb 1-1: New USB device found, idVendor=0572, idProduct=cafe
------------[ cut here ]------------
UBSAN: shift-out-of-bounds in net/atm/common.c:382:32
shift exponent -1 is negative
__vcc_connect+0x14b4/0x19c0
vcc_connect+0x328/0x8f0
pvc_bind+0x272/0x380
cxacru 1-1:1.0: send of cm 0x91 failed (-71)
The "send of cm 0x91 failed" line right after the crash confirms the
bind() really does win the race against cxacru's own device bring-up.
This reproduced on every run against the unpatched kernel; with this
patch applied I ran the same reproducer 6 times back to back and saw
no UBSAN report in any of them. checkpatch.pl is clean on the diff.
net/atm/common.c | 31 ++++++++++++++++++++++++-------
1 file changed, 24 insertions(+), 7 deletions(-)
diff --git a/net/atm/common.c b/net/atm/common.c
index 81195727fa18..bb6bc26eb599 100644
--- a/net/atm/common.c
+++ b/net/atm/common.c
@@ -327,6 +327,21 @@ static int check_ci(const struct atm_vcc *vcc, short vpi, int vci)
return 0;
}
+/*
+ * ci_range.vpi_bits/vci_bits are documented as 1..8 / 1..16, with
+ * ATM_CI_MAX (-1) meaning "no restriction". Neither that sentinel nor
+ * the all-zero value the field holds before a driver's probe has set
+ * it (see the register-before-ci_range-is-set race in usbatm_atm_init())
+ * is a valid shift count, so compute the exclusive upper bound instead
+ * of shifting a VPI/VCI by a possibly negative or out-of-range amount.
+ */
+static int ci_range_bound(signed char bits, int max_bits)
+{
+ if (bits <= 0 || bits > max_bits)
+ return 1 << max_bits;
+ return 1 << bits;
+}
+
static int find_ci(const struct atm_vcc *vcc, short *vpi, int *vci)
{
static short p; /* poor man's per-device cache */
@@ -342,12 +357,13 @@ static int find_ci(const struct atm_vcc *vcc, short *vpi, int *vci)
/* last scan may have left values out of bounds for current device */
if (*vpi != ATM_VPI_ANY)
p = *vpi;
- else if (p >= 1 << vcc->dev->ci_range.vpi_bits)
+ else if (p >= ci_range_bound(vcc->dev->ci_range.vpi_bits, 8))
p = 0;
if (*vci != ATM_VCI_ANY)
c = *vci;
- else if (c < ATM_NOT_RSV_VCI || c >= 1 << vcc->dev->ci_range.vci_bits)
- c = ATM_NOT_RSV_VCI;
+ else if (c < ATM_NOT_RSV_VCI ||
+ c >= ci_range_bound(vcc->dev->ci_range.vci_bits, 16))
+ c = ATM_NOT_RSV_VCI;
old_p = p;
old_c = c;
do {
@@ -358,13 +374,13 @@ static int find_ci(const struct atm_vcc *vcc, short *vpi, int *vci)
}
if (*vci == ATM_VCI_ANY) {
c++;
- if (c >= 1 << vcc->dev->ci_range.vci_bits)
+ if (c >= ci_range_bound(vcc->dev->ci_range.vci_bits, 16))
c = ATM_NOT_RSV_VCI;
}
if ((c == ATM_NOT_RSV_VCI || *vci != ATM_VCI_ANY) &&
*vpi == ATM_VPI_ANY) {
p++;
- if (p >= 1 << vcc->dev->ci_range.vpi_bits)
+ if (p >= ci_range_bound(vcc->dev->ci_range.vpi_bits, 8))
p = 0;
}
} while (old_p != p || old_c != c);
@@ -378,8 +394,9 @@ static int __vcc_connect(struct atm_vcc *vcc, struct atm_dev *dev, short vpi,
int error;
if ((vpi != ATM_VPI_UNSPEC && vpi != ATM_VPI_ANY &&
- vpi >> dev->ci_range.vpi_bits) || (vci != ATM_VCI_UNSPEC &&
- vci != ATM_VCI_ANY && vci >> dev->ci_range.vci_bits))
+ (vpi < 0 || vpi >= ci_range_bound(dev->ci_range.vpi_bits, 8))) ||
+ (vci != ATM_VCI_UNSPEC && vci != ATM_VCI_ANY &&
+ (vci < 0 || vci >= ci_range_bound(dev->ci_range.vci_bits, 16))))
return -EINVAL;
if (vci > 0 && vci < ATM_NOT_RSV_VCI && !capable(CAP_NET_BIND_SERVICE))
return -EPERM;
--
2.43.0
next reply other threads:[~2026-09-27 12:04 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-27 12:04 Nguyen Ngoc Thang [this message]
2026-09-30 18:05 ` netdev-bot+sashiko
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260927120410.44906-1-ngocthang2710.1999@gmail.com \
--to=ngocthang2710.1999@gmail.com \
--cc=3chas3@gmail.com \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-atm-general@lists.sourceforge.net \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®