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:
commit
e11e13749b
@ -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,
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user