mirror of
https://github.com/torvalds/linux.git
synced 2026-10-06 10:36:03 +02:00
The REMCSUM option carries an absolute checksum start and checksum field
offset. gue_remcsum() passes them to skb_remcsum_process(), whose
partial path stores offset - start in the u16 skb->csum_offset variable.
If offset is less than start, this underflows.
A forwarded packet can retain CHECKSUM_PARTIAL and reach a NETIF_F_HW_CSUM
driver which trusts the metadata, leading skb_copy_and_csum_dev() to write
two bytes about 64 KiB beyond the destination buffer.
Reject reversed tuples in validate_gue_flags(), after the existing length
validation, so all GUE parsers enforce the ordering in one place.
Fixes: fe881ef11c ("gue: Use checksum partial with remote checksum offload")
Signed-off-by: Jérémy Jean <Jeremy.Jean@oss.cyber.gouv.fr>
Link: https://patch.msgid.link/20260915124806.2852293-2-Jeremy.Jean@oss.cyber.gouv.fr
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
132 lines
3.6 KiB
C
132 lines
3.6 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
#ifndef __NET_GUE_H
|
|
#define __NET_GUE_H
|
|
|
|
/* Definitions for the GUE header, standard and private flags, lengths
|
|
* of optional fields are below.
|
|
*
|
|
* Diagram of GUE header:
|
|
*
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
* |Ver|C| Hlen | Proto/ctype | Standard flags |P|
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
* | |
|
|
* ~ Fields (optional) ~
|
|
* | |
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
* | Private flags (optional, P bit is set) |
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
* | |
|
|
* ~ Private fields (optional) ~
|
|
* | |
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
*
|
|
* C bit indicates control message when set, data message when unset.
|
|
* For a control message, proto/ctype is interpreted as a type of
|
|
* control message. For data messages, proto/ctype is the IP protocol
|
|
* of the next header.
|
|
*
|
|
* P bit indicates private flags field is present. The private flags
|
|
* may refer to options placed after this field.
|
|
*/
|
|
|
|
#include <asm/byteorder.h>
|
|
#include <linux/types.h>
|
|
|
|
struct guehdr {
|
|
union {
|
|
struct {
|
|
#if defined(__LITTLE_ENDIAN_BITFIELD)
|
|
__u8 hlen:5,
|
|
control:1,
|
|
version:2;
|
|
#elif defined (__BIG_ENDIAN_BITFIELD)
|
|
__u8 version:2,
|
|
control:1,
|
|
hlen:5;
|
|
#else
|
|
#error "Please fix <asm/byteorder.h>"
|
|
#endif
|
|
__u8 proto_ctype;
|
|
__be16 flags;
|
|
};
|
|
__be32 word;
|
|
};
|
|
};
|
|
|
|
/* Standard flags in GUE header */
|
|
|
|
#define GUE_FLAG_PRIV htons(1<<0) /* Private flags are in options */
|
|
#define GUE_LEN_PRIV 4
|
|
|
|
#define GUE_FLAGS_ALL (GUE_FLAG_PRIV)
|
|
|
|
/* Private flags in the private option extension */
|
|
|
|
#define GUE_PFLAG_REMCSUM htonl(1U << 31)
|
|
#define GUE_PLEN_REMCSUM 4
|
|
|
|
#define GUE_PFLAGS_ALL (GUE_PFLAG_REMCSUM)
|
|
|
|
/* Functions to compute options length corresponding to flags.
|
|
* If we ever have a lot of flags this can be potentially be
|
|
* converted to a more optimized algorithm (table lookup
|
|
* for instance).
|
|
*/
|
|
static inline size_t guehdr_flags_len(__be16 flags)
|
|
{
|
|
return ((flags & GUE_FLAG_PRIV) ? GUE_LEN_PRIV : 0);
|
|
}
|
|
|
|
static inline size_t guehdr_priv_flags_len(__be32 flags)
|
|
{
|
|
return (flags & GUE_PFLAG_REMCSUM) ? GUE_PLEN_REMCSUM : 0;
|
|
}
|
|
|
|
/* Validate standard and private flags. Returns non-zero (meaning invalid)
|
|
* if there is an unknown standard or private flags, if the options length for
|
|
* the flags exceeds the options length specified in hlen of the GUE header, or
|
|
* if a private option contains invalid data.
|
|
*/
|
|
static inline int validate_gue_flags(struct guehdr *guehdr, size_t optlen)
|
|
{
|
|
__be16 flags = guehdr->flags;
|
|
size_t len;
|
|
|
|
if (flags & ~GUE_FLAGS_ALL)
|
|
return 1;
|
|
|
|
len = guehdr_flags_len(flags);
|
|
if (len > optlen)
|
|
return 1;
|
|
|
|
if (flags & GUE_FLAG_PRIV) {
|
|
/* Private flags are last four bytes accounted in
|
|
* guehdr_flags_len
|
|
*/
|
|
void *data = (void *)&guehdr[1] + len;
|
|
__be32 pflags = *(__be32 *)(data - GUE_LEN_PRIV);
|
|
|
|
if (pflags & ~GUE_PFLAGS_ALL)
|
|
return 1;
|
|
|
|
len += guehdr_priv_flags_len(pflags);
|
|
if (len > optlen)
|
|
return 1;
|
|
|
|
if (pflags & GUE_PFLAG_REMCSUM) {
|
|
__be16 *pd = data;
|
|
|
|
/* The field offset pd[1] must not be less
|
|
* than the start pd[0].
|
|
*/
|
|
if (ntohs(pd[1]) < ntohs(pd[0]))
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|