mirror of
https://github.com/torvalds/linux.git
synced 2026-05-12 16:18:45 +02:00
ipv6: Implement limits on extension header parsing
ipv6_{skip_exthdr,find_hdr}() and ip6_{tnl_parse_tlv_enc_lim,
protocol_deliver_rcu}() iterate over IPv6 extension headers until they
find a non-extension-header protocol or run out of packet data. The
loops have no iteration counter, relying solely on the packet length
to bound them. For a crafted packet with 8-byte extension headers
filling a 64KB jumbogram, this means a worst case of up to ~8k
iterations with a skb_header_pointer call each. ipv6_skip_exthdr(),
for example, is used where it parses the inner quoted packet inside
an incoming ICMPv6 error:
- icmpv6_rcv
- checksum validation
- case ICMPV6_DEST_UNREACH
- icmpv6_notify
- pskb_may_pull() <- pull inner IPv6 header
- ipv6_skip_exthdr() <- iterates here
- pskb_may_pull()
- ipprot->err_handler() <- sk lookup
The per-iteration cost of ipv6_skip_exthdr itself is generally
light, but skb_header_pointer becomes more costly on reassembled
packets: the first ~1232 bytes of the inner packet are in the skb's
linear area, but the remaining ~63KB are in the frag_list where
skb_copy_bits is needed to read data.
Initially, the idea was to add a configurable limit via a new
sysctl knob with default 8, in line with knobs from commit
47d3d7ac65 ("ipv6: Implement limits on Hop-by-Hop and Destination
options"), but two reasons eventually argued against it:
- It adds to UAPI that needs to be maintained forever, and
upcoming work is restricting extension header ordering anyway,
leaving little reason for another sysctl knob
- exthdrs_core.c is always built-in even when CONFIG_IPV6=n,
where struct net has no .ipv6 member, so the read site would
need an ifdef'd fallback to a constant anyway
Therefore, just use a constant (IP6_MAX_EXT_HDRS_CNT). All four
extension header walking functions are now bound by this limit.
Note that the check in ip6_protocol_deliver_rcu() happens right
before the goto resubmit, such that we don't have to have a test
for ipv6_ext_hdr() in the fast-path.
There's an ongoing IETF draft-iurman-6man-eh-occurrences to enforce
IPv6 extension headers ordering and occurrence. The latter also
discusses security implications. As per RFC8200 section 4.1, the
occurrence rules for extension headers provide a practical upper
bound which is 8. In order to be conservative, let's define
IP6_MAX_EXT_HDRS_CNT as 12 to leave enough room for quirky setups.
In the unlikely event that this is still not enough, then we might
need to reconsider a sysctl.
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Justin Iurman <justin.iurman@gmail.com>
Link: https://patch.msgid.link/20260429154648.809751-1-daniel@iogearbox.net
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
e027c218c4
commit
3744b0964d
|
|
@ -99,6 +99,7 @@
|
|||
FN(FRAG_TOO_FAR) \
|
||||
FN(TCP_MINTTL) \
|
||||
FN(IPV6_BAD_EXTHDR) \
|
||||
FN(IPV6_TOO_MANY_EXTHDRS) \
|
||||
FN(IPV6_NDISC_FRAG) \
|
||||
FN(IPV6_NDISC_HOP_LIMIT) \
|
||||
FN(IPV6_NDISC_BAD_CODE) \
|
||||
|
|
@ -494,6 +495,11 @@ enum skb_drop_reason {
|
|||
SKB_DROP_REASON_TCP_MINTTL,
|
||||
/** @SKB_DROP_REASON_IPV6_BAD_EXTHDR: Bad IPv6 extension header. */
|
||||
SKB_DROP_REASON_IPV6_BAD_EXTHDR,
|
||||
/**
|
||||
* @SKB_DROP_REASON_IPV6_TOO_MANY_EXTHDRS: Number of IPv6 extension
|
||||
* headers in the packet exceeds IP6_MAX_EXT_HDRS_CNT.
|
||||
*/
|
||||
SKB_DROP_REASON_IPV6_TOO_MANY_EXTHDRS,
|
||||
/** @SKB_DROP_REASON_IPV6_NDISC_FRAG: invalid frag (suppress_frag_ndisc). */
|
||||
SKB_DROP_REASON_IPV6_NDISC_FRAG,
|
||||
/** @SKB_DROP_REASON_IPV6_NDISC_HOP_LIMIT: invalid hop limit. */
|
||||
|
|
|
|||
|
|
@ -90,6 +90,9 @@ struct ip_tunnel_info;
|
|||
#define IP6_DEFAULT_MAX_DST_OPTS_LEN INT_MAX /* No limit */
|
||||
#define IP6_DEFAULT_MAX_HBH_OPTS_LEN INT_MAX /* No limit */
|
||||
|
||||
/* Hard limit on traversed IPv6 extension headers */
|
||||
#define IP6_MAX_EXT_HDRS_CNT 12
|
||||
|
||||
/*
|
||||
* Addr type
|
||||
*
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,
|
|||
__be16 *frag_offp)
|
||||
{
|
||||
u8 nexthdr = *nexthdrp;
|
||||
int exthdr_cnt = 0;
|
||||
|
||||
*frag_offp = 0;
|
||||
|
||||
|
|
@ -82,6 +83,8 @@ int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,
|
|||
|
||||
if (nexthdr == NEXTHDR_NONE)
|
||||
return -1;
|
||||
if (unlikely(exthdr_cnt++ >= IP6_MAX_EXT_HDRS_CNT))
|
||||
return -1;
|
||||
hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
|
||||
if (!hp)
|
||||
return -1;
|
||||
|
|
@ -190,6 +193,7 @@ int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
|
|||
{
|
||||
unsigned int start = skb_network_offset(skb) + sizeof(struct ipv6hdr);
|
||||
u8 nexthdr = ipv6_hdr(skb)->nexthdr;
|
||||
int exthdr_cnt = 0;
|
||||
bool found;
|
||||
|
||||
if (fragoff)
|
||||
|
|
@ -216,6 +220,9 @@ int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
|
|||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (unlikely(exthdr_cnt++ >= IP6_MAX_EXT_HDRS_CNT))
|
||||
return -EBADMSG;
|
||||
|
||||
hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
|
||||
if (!hp)
|
||||
return -EBADMSG;
|
||||
|
|
|
|||
|
|
@ -403,6 +403,7 @@ INDIRECT_CALLABLE_DECLARE(int tcp_v6_rcv(struct sk_buff *));
|
|||
void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr,
|
||||
bool have_final)
|
||||
{
|
||||
int exthdr_cnt = IP6CB(skb)->flags & IP6SKB_HOPBYHOP ? 1 : 0;
|
||||
const struct inet6_protocol *ipprot;
|
||||
struct inet6_dev *idev;
|
||||
unsigned int nhoff;
|
||||
|
|
@ -487,6 +488,10 @@ void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr,
|
|||
nexthdr = ret;
|
||||
goto resubmit_final;
|
||||
} else {
|
||||
if (unlikely(exthdr_cnt++ >= IP6_MAX_EXT_HDRS_CNT)) {
|
||||
SKB_DR_SET(reason, IPV6_TOO_MANY_EXTHDRS);
|
||||
goto discard;
|
||||
}
|
||||
goto resubmit;
|
||||
}
|
||||
} else if (ret == 0) {
|
||||
|
|
|
|||
|
|
@ -399,11 +399,15 @@ __u16 ip6_tnl_parse_tlv_enc_lim(struct sk_buff *skb, __u8 *raw)
|
|||
unsigned int nhoff = raw - skb->data;
|
||||
unsigned int off = nhoff + sizeof(*ipv6h);
|
||||
u8 nexthdr = ipv6h->nexthdr;
|
||||
int exthdr_cnt = 0;
|
||||
|
||||
while (ipv6_ext_hdr(nexthdr) && nexthdr != NEXTHDR_NONE) {
|
||||
struct ipv6_opt_hdr *hdr;
|
||||
u16 optlen;
|
||||
|
||||
if (unlikely(exthdr_cnt++ >= IP6_MAX_EXT_HDRS_CNT))
|
||||
break;
|
||||
|
||||
if (!pskb_may_pull(skb, off + sizeof(*hdr)))
|
||||
break;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user