Skip to content

Commit

Permalink
Merge branch 'usbnet-ipheth-prevent-oob-reads-of-ndp16'
Browse files Browse the repository at this point in the history
Foster Snowhill says:

====================
usbnet: ipheth: prevent OoB reads of NDP16

iOS devices support two types of tethering over USB: regular, where the
internet connection is shared from the phone to the attached computer,
and reverse, where the internet connection is shared from the attached
computer to the phone.

The `ipheth` driver is responsible for regular tethering only. With this
tethering type, iOS devices support two encapsulation modes on RX:
legacy and NCM.

In "NCM mode", the iOS device encapsulates RX (phone->computer) traffic
in NCM Transfer Blocks (similarly to CDC NCM). However, unlike reverse
tethering, regular tethering is not compliant with the CDC NCM spec:

* Does not have the required CDC NCM descriptors
* TX (computer->phone) is not NCM-encapsulated at all

Thus `ipheth` implements a very limited subset of the spec with the sole
purpose of parsing RX URBs. This driver does not aim to be
a CDC NCM-compliant implementation and, in fact, can't be one because of
the points above.

For a complete spec-compliant CDC NCM implementation, there is already
the `cdc_ncm` driver. This driver is used for reverse tethering on iOS
devices. This patch series does not in any way change `cdc_ncm`.

In the first iteration of the NCM mode implementation in `ipheth`,
there were a few potential out of bounds reads when processing malformed
URBs received from a connected device:

* Only the start of NDP16 (wNdpIndex) was checked to fit in the URB
  buffer.
* Datagram length check as part of DPEs could overflow.
* DPEs could be read past the end of NDP16 and even end of URB buffer
  if a trailer DPE wasn't encountered.

The above is not expected to happen in normal device operation.

To address the above issues for iOS devices in NCM mode, rely on
and check for a specific fixed format of incoming URBs expected from
an iOS device:

* 12-byte NTH16
* 96-byte NDP16, allowing up to 22 DPEs (up to 21 datagrams + trailer)

On iOS, NDP16 directly follows NTH16, and its length is constant
regardless of the DPE count.

As the regular tethering implementation of iOS devices isn't compliant
with CDC NCM, it's not possible to use the `cdc_ncm` driver to handle
this functionality. Furthermore, while the logic required to properly
parse URBs with NCM-encapsulated frames is already part of said driver,
I haven't found a nice way to reuse the existing code without messing
with the `cdc_ncm` driver itself.

I didn't want to reimplement more of the spec than I absolutely had to,
because that work had already been done in `cdc_ncm`. Instead, to limit
the scope, I chose to rely on the specific URB format of iOS devices
that hasn't changed since the NCM mode was introduced there.

I tested each individual patch in the v5 series with iPhone 15 Pro Max,
iOS 18.2.1: compiled cleanly, ran iperf3 between phone and computer,
observed no errors in either kernel log or interface statistics.

v4 was Reviewed-by Jakub Kicinski <kuba@kernel.org>. Compared to v4,
v5 has no code changes. The two differences are:

* Patch "usbnet: ipheth: break up NCM header size computation"
  moved later in the series, closer to a subsequent commit that makes
  use of the change.
* In patch "usbnet: ipheth: refactor NCM datagram loop", removed
  a stray paragraph in commit msg.

Above items are also noted in the changelogs of respective patches.
====================

Link: https://patch.msgid.link/20250125235409.3106594-1-forst@pen.gy
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
  • Loading branch information
Paolo Abeni committed Jan 28, 2025
2 parents 19ae40f + be154b5 commit e091043
Showing 1 changed file with 45 additions and 24 deletions.
69 changes: 45 additions & 24 deletions drivers/net/usb/ipheth.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,18 @@
#define IPHETH_USBINTF_PROTO 1

#define IPHETH_IP_ALIGN 2 /* padding at front of URB */
#define IPHETH_NCM_HEADER_SIZE (12 + 96) /* NCMH + NCM0 */
/* On iOS devices, NCM headers in RX have a fixed size regardless of DPE count:
* - NTH16 (NCMH): 12 bytes, as per CDC NCM 1.0 spec
* - NDP16 (NCM0): 96 bytes, of which
* - NDP16 fixed header: 8 bytes
* - maximum of 22 DPEs (21 datagrams + trailer), 4 bytes each
*/
#define IPHETH_NDP16_MAX_DPE 22
#define IPHETH_NDP16_HEADER_SIZE (sizeof(struct usb_cdc_ncm_ndp16) + \
IPHETH_NDP16_MAX_DPE * \
sizeof(struct usb_cdc_ncm_dpe16))
#define IPHETH_NCM_HEADER_SIZE (sizeof(struct usb_cdc_ncm_nth16) + \
IPHETH_NDP16_HEADER_SIZE)
#define IPHETH_TX_BUF_SIZE ETH_FRAME_LEN
#define IPHETH_RX_BUF_SIZE_LEGACY (IPHETH_IP_ALIGN + ETH_FRAME_LEN)
#define IPHETH_RX_BUF_SIZE_NCM 65536
Expand Down Expand Up @@ -207,15 +218,23 @@ static int ipheth_rcvbulk_callback_legacy(struct urb *urb)
return ipheth_consume_skb(buf, len, dev);
}

/* In "NCM mode", the iOS device encapsulates RX (phone->computer) traffic
* in NCM Transfer Blocks (similarly to CDC NCM). However, unlike reverse
* tethering (handled by the `cdc_ncm` driver), regular tethering is not
* compliant with the CDC NCM spec, as the device is missing the necessary
* descriptors, and TX (computer->phone) traffic is not encapsulated
* at all. Thus `ipheth` implements a very limited subset of the spec with
* the sole purpose of parsing RX URBs.
*/
static int ipheth_rcvbulk_callback_ncm(struct urb *urb)
{
struct usb_cdc_ncm_nth16 *ncmh;
struct usb_cdc_ncm_ndp16 *ncm0;
struct usb_cdc_ncm_dpe16 *dpe;
struct ipheth_device *dev;
u16 dg_idx, dg_len;
int retval = -EINVAL;
char *buf;
int len;

dev = urb->context;

Expand All @@ -226,40 +245,42 @@ static int ipheth_rcvbulk_callback_ncm(struct urb *urb)

ncmh = urb->transfer_buffer;
if (ncmh->dwSignature != cpu_to_le32(USB_CDC_NCM_NTH16_SIGN) ||
le16_to_cpu(ncmh->wNdpIndex) >= urb->actual_length) {
dev->net->stats.rx_errors++;
return retval;
}
/* On iOS, NDP16 directly follows NTH16 */
ncmh->wNdpIndex != cpu_to_le16(sizeof(struct usb_cdc_ncm_nth16)))
goto rx_error;

ncm0 = urb->transfer_buffer + le16_to_cpu(ncmh->wNdpIndex);
if (ncm0->dwSignature != cpu_to_le32(USB_CDC_NCM_NDP16_NOCRC_SIGN) ||
le16_to_cpu(ncmh->wHeaderLength) + le16_to_cpu(ncm0->wLength) >=
urb->actual_length) {
dev->net->stats.rx_errors++;
return retval;
}
ncm0 = urb->transfer_buffer + sizeof(struct usb_cdc_ncm_nth16);
if (ncm0->dwSignature != cpu_to_le32(USB_CDC_NCM_NDP16_NOCRC_SIGN))
goto rx_error;

dpe = ncm0->dpe16;
while (le16_to_cpu(dpe->wDatagramIndex) != 0 &&
le16_to_cpu(dpe->wDatagramLength) != 0) {
if (le16_to_cpu(dpe->wDatagramIndex) >= urb->actual_length ||
le16_to_cpu(dpe->wDatagramIndex) +
le16_to_cpu(dpe->wDatagramLength) > urb->actual_length) {
for (int dpe_i = 0; dpe_i < IPHETH_NDP16_MAX_DPE; ++dpe_i, ++dpe) {
dg_idx = le16_to_cpu(dpe->wDatagramIndex);
dg_len = le16_to_cpu(dpe->wDatagramLength);

/* Null DPE must be present after last datagram pointer entry
* (3.3.1 USB CDC NCM spec v1.0)
*/
if (dg_idx == 0 && dg_len == 0)
return 0;

if (dg_idx < IPHETH_NCM_HEADER_SIZE ||
dg_idx >= urb->actual_length ||
dg_len > urb->actual_length - dg_idx) {
dev->net->stats.rx_length_errors++;
return retval;
}

buf = urb->transfer_buffer + le16_to_cpu(dpe->wDatagramIndex);
len = le16_to_cpu(dpe->wDatagramLength);
buf = urb->transfer_buffer + dg_idx;

retval = ipheth_consume_skb(buf, len, dev);
retval = ipheth_consume_skb(buf, dg_len, dev);
if (retval != 0)
return retval;

dpe++;
}

return 0;
rx_error:
dev->net->stats.rx_errors++;
return retval;
}

static void ipheth_rcvbulk_callback(struct urb *urb)
Expand Down

0 comments on commit e091043

Please sign in to comment.