Merge tag 'LA.UM.9.14.r1-22000-LAHAINA.QSSI12.0' of https://git.codelinaro.org/clo/la/platform/vendor/qcom/opensource/datarmnet into android13-5.4-lahaina

"LA.UM.9.14.r1-22000-LAHAINA.QSSI12.0"

* tag 'LA.UM.9.14.r1-22000-LAHAINA.QSSI12.0' of https://git.codelinaro.org/clo/la/platform/vendor/qcom/opensource/datarmnet:
  rmnet_core: Strengthen IPv6 fragment check in rmnet_frag_skip_exthdr()
  rmnet_core: Manually checksum csum_valid = 0 packets

Change-Id: Ic8d02b333ceb430f12209e5f8de32c42d617bd17
This commit is contained in:
Michael Bestas 2023-07-11 15:53:54 +03:00
commit e11e13749b
No known key found for this signature in database
GPG Key ID: CC95044519BE6669
2 changed files with 171 additions and 9 deletions

View File

@ -1,4 +1,5 @@
/* Copyright (c) 2013-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@ -322,12 +323,13 @@ int rmnet_frag_descriptor_add_frags_from(struct rmnet_frag_descriptor *to,
EXPORT_SYMBOL(rmnet_frag_descriptor_add_frags_from);
int rmnet_frag_ipv6_skip_exthdr(struct rmnet_frag_descriptor *frag_desc,
int start, u8 *nexthdrp, __be16 *fragp)
int start, u8 *nexthdrp, __be16 *frag_offp,
bool *frag_hdrp)
{
u8 nexthdr = *nexthdrp;
*fragp = 0;
*frag_offp = 0;
*frag_hdrp = false;
while (ipv6_ext_hdr(nexthdr)) {
struct ipv6_opt_hdr *hp, __hp;
int hdrlen;
@ -349,8 +351,9 @@ int rmnet_frag_ipv6_skip_exthdr(struct rmnet_frag_descriptor *frag_desc,
if (!fp)
return -EINVAL;
*fragp = *fp;
if (ntohs(*fragp) & ~0x7)
*frag_offp = *fp;
*frag_hdrp = true;
if (ntohs(*frag_offp) & ~0x7)
break;
hdrlen = 8;
} else if (nexthdr == NEXTHDR_AUTH) {
@ -1143,6 +1146,7 @@ rmnet_frag_segment_coal_data(struct rmnet_frag_descriptor *coal_desc,
struct ipv6hdr *ip6h, __ip6h;
int ip_len;
__be16 frag_off;
bool frag_hdr;
u8 protocol;
ip6h = rmnet_frag_header_ptr(coal_desc, 0, sizeof(*ip6h),
@ -1155,10 +1159,11 @@ rmnet_frag_segment_coal_data(struct rmnet_frag_descriptor *coal_desc,
ip_len = rmnet_frag_ipv6_skip_exthdr(coal_desc,
sizeof(*ip6h),
&protocol,
&frag_off);
&frag_off,
&frag_hdr);
coal_desc->trans_proto = protocol;
/* If we run into a problem, or this has a fragment header
/* If we run into a problem, or this is fragmented packet
* (which should technically not be possible, if the HW
* works as intended...), bail.
*/
@ -1167,6 +1172,14 @@ rmnet_frag_segment_coal_data(struct rmnet_frag_descriptor *coal_desc,
return;
}
if (frag_hdr) {
/* There is a fragment header, but this is not a
* fragmented packet. We can handle this, but it
* cannot be coalesced because of kernel limitations.
*/
gro = false;
}
coal_desc->ip_len = (u16)ip_len;
if (coal_desc->ip_len > sizeof(*ip6h)) {
/* Don't allow coalescing of any packets with IPv6
@ -1396,6 +1409,142 @@ rmnet_frag_data_check_coal_header(struct rmnet_frag_descriptor *frag_desc,
return 0;
}
static int rmnet_frag_checksum_pkt(struct rmnet_frag_descriptor *frag_desc)
{
struct rmnet_priv *priv = netdev_priv(frag_desc->dev);
struct rmnet_fragment *frag;
int offset = sizeof(struct rmnet_map_header) +
sizeof(struct rmnet_map_v5_csum_header);
u8 *version, __version;
__wsum csum;
u16 csum_len;
version = rmnet_frag_header_ptr(frag_desc, offset, sizeof(*version),
&__version);
if (!version)
return -EINVAL;
if ((*version & 0xF0) == 0x40) {
struct iphdr *iph;
u8 __iph[60]; /* Max IP header size (0xF * 4) */
/* We need to access the entire IP header including options
* to validate its checksum. Fortunately, the version byte
* also will tell us the length, so we only need to pull
* once ;)
*/
frag_desc->ip_len = (*version & 0xF) * 4;
iph = rmnet_frag_header_ptr(frag_desc, offset,
frag_desc->ip_len,
__iph);
if (!iph || ip_is_fragment(iph))
return -EINVAL;
/* Length needs to be sensible */
csum_len = ntohs(iph->tot_len);
if (csum_len > frag_desc->len - offset)
return -EINVAL;
csum_len -= frag_desc->ip_len;
/* IPv4 checksum must be valid */
if (ip_fast_csum((u8 *)iph, frag_desc->ip_len)) {
priv->stats.csum_sw++;
return 0;
}
frag_desc->ip_proto = 4;
frag_desc->trans_proto = iph->protocol;
csum = ~csum_tcpudp_magic(iph->saddr, iph->daddr,
csum_len,
frag_desc->trans_proto, 0);
} else if ((*version & 0xF0) == 0x60) {
struct ipv6hdr *ip6h, __ip6h;
int ip_len;
__be16 frag_off;
bool frag_hdr;
u8 protocol;
ip6h = rmnet_frag_header_ptr(frag_desc, offset, sizeof(*ip6h),
&__ip6h);
if (!ip6h)
return -EINVAL;
frag_desc->ip_proto = 6;
protocol = ip6h->nexthdr;
ip_len = rmnet_frag_ipv6_skip_exthdr(frag_desc,
offset + sizeof(*ip6h),
&protocol, &frag_off,
&frag_hdr);
if (ip_len < 0 || frag_off)
return -EINVAL;
/* Length needs to be sensible */
frag_desc->ip_len = (u16)ip_len;
csum_len = ntohs(ip6h->payload_len);
if (csum_len + frag_desc->ip_len > frag_desc->len - offset)
return -EINVAL;
frag_desc->trans_proto = protocol;
csum = ~csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
csum_len,
frag_desc->trans_proto, 0);
} else {
/* Not checksumable */
return -EINVAL;
}
/* Protocol check */
if (frag_desc->trans_proto != IPPROTO_TCP &&
frag_desc->trans_proto != IPPROTO_UDP)
return -EINVAL;
offset += frag_desc->ip_len;
/* Check for UDP zero csum packets */
if (frag_desc->trans_proto == IPPROTO_UDP) {
struct udphdr *uh, __uh;
uh = rmnet_frag_header_ptr(frag_desc, offset, sizeof(*uh),
&__uh);
if (!uh)
return -EINVAL;
if (!uh->check) {
if (frag_desc->ip_proto == 4) {
/* Zero checksum is valid */
priv->stats.csum_sw++;
return 1;
}
/* Not valid in IPv6 */
priv->stats.csum_sw++;
return 0;
}
}
/* Walk the frags and checksum each chunk */
list_for_each_entry(frag, &frag_desc->frags, list) {
u32 frag_size = skb_frag_size(&frag->frag);
if (!csum_len)
break;
if (offset < frag_size) {
void *addr = skb_frag_address(&frag->frag) + offset;
u32 len = min_t(u32, csum_len, frag_size - offset);
/* Checksum 'len' bytes and add them in */
csum = csum_partial(addr, len, csum);
csum_len -= len;
offset = 0;
} else {
offset -= frag_size;
}
}
priv->stats.csum_sw++;
return !csum_fold(csum);
}
/* Process a QMAPv5 packet header */
int rmnet_frag_process_next_hdr_packet(struct rmnet_frag_descriptor *frag_desc,
struct rmnet_port *port,
@ -1440,7 +1589,18 @@ int rmnet_frag_process_next_hdr_packet(struct rmnet_frag_descriptor *frag_desc,
priv->stats.csum_ok++;
frag_desc->csum_valid = true;
} else {
priv->stats.csum_valid_unset++;
int valid = rmnet_frag_checksum_pkt(frag_desc);
if (valid < 0) {
priv->stats.csum_validation_failed++;
} else if (valid) {
/* All's good */
priv->stats.csum_ok++;
frag_desc->csum_valid = true;
} else {
/* Checksum is actually bad */
priv->stats.csum_valid_unset++;
}
}
if (!rmnet_frag_pull(frag_desc, port,

View File

@ -1,4 +1,5 @@
/* Copyright (c) 2013-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@ -75,7 +76,8 @@ int rmnet_frag_descriptor_add_frags_from(struct rmnet_frag_descriptor *to,
struct rmnet_frag_descriptor *from,
u32 off, u32 len);
int rmnet_frag_ipv6_skip_exthdr(struct rmnet_frag_descriptor *frag_desc,
int start, u8 *nexthdrp, __be16 *fragp);
int start, u8 *nexthdrp, __be16 *frag_offp,
bool *frag_hdrp);
/* QMAP command packets */
void rmnet_frag_command(struct rmnet_frag_descriptor *frag_desc,