mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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


             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®