Thread (4 messages) 4 messages, 3 authors, 6d ago

Re: [PATCH net v2] net: qualcomm: rmnet: validate MAP frame length before ingress parsing

From: Xiang Mei <hidden>
Date: 2026-06-30 17:44:02
Also in: lkml

Thanks so much for your refactor. It's better! I took it for v2 with
two small changes:

- Dropped the goto err1/err0; the helper has no cleanup, so it just returns
  0 or packet_len directly. (err0 was the success path anyway.)

- The no-agg caller leaks the skb when validation fails, since
  __rmnet_map_ingress_handler() is the one that owns and frees it:

if (rmnet_map_validate_packet_len(skb, port))
__rmnet_map_ingress_handler(skb, port);
else
kfree_skb(skb);

I also bounds-check the MAP header up front (skb->len < sizeof(*maph)),
since the no-agg path can get a frame shorter than the header before
maph->pkt_len is read.

v2 sent with you as Suggested-by. Tested with the original repro: OOB gone
on the no-agg path, valid frames and the deagg path unaffected.

Xiang

On Tue, Jun 30, 2026 at 10:41 AM Xiang Mei [off-list ref] wrote:
quoted hunk ↗ jump to hunk
When ingress deaggregation is disabled, rmnet_map_ingress_handler() passes
the skb straight to __rmnet_map_ingress_handler(), skipping the length
validation that rmnet_map_deaggregate() performs on the aggregated path.
The parser then dereferences the MAP header and csum header/trailer based on
the on-wire pkt_len without checking skb->len, so a short frame is read out
of bounds:

  BUG: KASAN: slab-out-of-bounds in rmnet_map_checksum_downlink_packet
  Read of size 1 at addr ffff88801118ed00 by task exploit/147
  Call Trace:
   ...
   rmnet_map_checksum_downlink_packet (drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c:413)
   __rmnet_map_ingress_handler (drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c:96)
   rmnet_rx_handler (drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c:129)
   __netif_receive_skb_core.constprop.0 (net/core/dev.c:6089)
   netif_receive_skb (net/core/dev.c:6460)
   tun_get_user (drivers/net/tun.c:1955)
   tun_chr_write_iter (drivers/net/tun.c:2001)
   vfs_write (fs/read_write.c:688)
   ksys_write (fs/read_write.c:740)
   do_syscall_64 (arch/x86/entry/syscall_64.c:94)
   ...

Factor that validation out of rmnet_map_deaggregate() into
rmnet_map_validate_packet_len() and run it on the no-aggregation path too.
The MAP header is bounds-checked first, since this path can receive a frame
shorter than the header.

Fixes: ceed73a2cf4a ("drivers: net: ethernet: qualcomm: rmnet: Initial implementation")
Reported-by: Weiming Shi <redacted>
Suggested-by: Subash Abhinov Kasiviswanathan <subash.a.kasiviswanathan@oss.qualcomm.com>
Signed-off-by: Xiang Mei <redacted>
---
v2: Validate on the no-aggregation path by reusing the deaggregation
    length checks (factored into rmnet_map_validate_packet_len()) instead
    of adding separate pskb_may_pull() guards in __rmnet_map_ingress_handler().

 .../ethernet/qualcomm/rmnet/rmnet_handlers.c  |  5 +-
 .../net/ethernet/qualcomm/rmnet/rmnet_map.h   |  1 +
 .../ethernet/qualcomm/rmnet/rmnet_map_data.c  | 72 ++++++++++---------
 3 files changed, 45 insertions(+), 33 deletions(-)
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c
index 9f3479500f85..d055a2628d8c 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c
@@ -126,7 +126,10 @@ rmnet_map_ingress_handler(struct sk_buff *skb,

                consume_skb(skb);
        } else {
-               __rmnet_map_ingress_handler(skb, port);
+               if (rmnet_map_validate_packet_len(skb, port))
+                       __rmnet_map_ingress_handler(skb, port);
+               else
+                       kfree_skb(skb);
        }
 }
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h
index b70284095568..60ca8b780c88 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h
@@ -59,5 +59,6 @@ void rmnet_map_tx_aggregate_init(struct rmnet_port *port);
 void rmnet_map_tx_aggregate_exit(struct rmnet_port *port);
 void rmnet_map_update_ul_agg_config(struct rmnet_port *port, u32 size,
                                    u32 count, u32 time);
+u32 rmnet_map_validate_packet_len(struct sk_buff *skb, struct rmnet_port *port);

 #endif /* _RMNET_MAP_H_ */
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c
index 8b4640c5d61e..305ae15ae8f3 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c
@@ -333,54 +333,62 @@ struct rmnet_map_header *rmnet_map_add_map_header(struct sk_buff *skb,
        return map_header;
 }

-/* Deaggregates a single packet
- * A whole new buffer is allocated for each portion of an aggregated frame.
- * Caller should keep calling deaggregate() on the source skb until 0 is
- * returned, indicating that there are no more packets to deaggregate. Caller
- * is responsible for freeing the original skb.
- */
-struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb,
-                                     struct rmnet_port *port)
+u32 rmnet_map_validate_packet_len(struct sk_buff *skb, struct rmnet_port *port)
 {
        struct rmnet_map_v5_csum_header *next_hdr = NULL;
        struct rmnet_map_header *maph;
        void *data = skb->data;
-       struct sk_buff *skbn;
-       u8 nexthdr_type;
        u32 packet_len;

-       if (skb->len == 0)
-               return NULL;
+       if (skb->len < sizeof(*maph))
+               return 0;

        maph = (struct rmnet_map_header *)skb->data;
+
+       /* Some hardware can send us empty frames. Catch them */
+       if (!maph->pkt_len)
+               return 0;
+
        packet_len = ntohs(maph->pkt_len) + sizeof(*maph);

        if (port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV4) {
                packet_len += sizeof(struct rmnet_map_dl_csum_trailer);
-       } else if (port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV5) {
-               if (!(maph->flags & MAP_CMD_FLAG)) {
-                       packet_len += sizeof(*next_hdr);
-                       if (maph->flags & MAP_NEXT_HEADER_FLAG)
-                               next_hdr = data + sizeof(*maph);
-                       else
-                               /* Mapv5 data pkt without csum hdr is invalid */
-                               return NULL;
-               }
+       } else if ((port->data_format & RMNET_FLAGS_INGRESS_MAP_CKSUMV5) &&
+                  !(maph->flags & MAP_CMD_FLAG)) {
+               /* Mapv5 data pkt without csum hdr is invalid */
+               if (!(maph->flags & MAP_NEXT_HEADER_FLAG))
+                       return 0;
+
+               packet_len += sizeof(*next_hdr);
+               next_hdr = data + sizeof(*maph);
        }

-       if (((int)skb->len - (int)packet_len) < 0)
-               return NULL;
+       if (skb->len < packet_len)
+               return 0;

-       /* Some hardware can send us empty frames. Catch them */
-       if (!maph->pkt_len)
-               return NULL;
+       if (next_hdr &&
+           u8_get_bits(next_hdr->header_info, MAPV5_HDRINFO_HDR_TYPE_FMASK) !=
+           RMNET_MAP_HEADER_TYPE_CSUM_OFFLOAD)
+               return 0;

-       if (next_hdr) {
-               nexthdr_type = u8_get_bits(next_hdr->header_info,
-                                          MAPV5_HDRINFO_HDR_TYPE_FMASK);
-               if (nexthdr_type != RMNET_MAP_HEADER_TYPE_CSUM_OFFLOAD)
-                       return NULL;
-       }
+       return packet_len;
+}
+
+/* Deaggregates a single packet
+ * A whole new buffer is allocated for each portion of an aggregated frame.
+ * Caller should keep calling deaggregate() on the source skb until 0 is
+ * returned, indicating that there are no more packets to deaggregate. Caller
+ * is responsible for freeing the original skb.
+ */
+struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb,
+                                     struct rmnet_port *port)
+{
+       struct sk_buff *skbn;
+       u32 packet_len;
+
+       packet_len = rmnet_map_validate_packet_len(skb, port);
+       if (!packet_len)
+               return NULL;

        skbn = alloc_skb(packet_len + RMNET_MAP_DEAGGR_SPACING, GFP_ATOMIC);
        if (!skbn)
--
2.43.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help