@@ -31,6 +31,19 @@ struct ipv6_txoptions *ipeh_renew_options(struct sock *sk,
struct ipv6_txoptions *ipeh_fixup_options(struct ipv6_txoptions *opt_space,
struct ipv6_txoptions *opt);
+/* Generic extension header TLV parser */
+
+enum ipeh_parse_errors {
+ IPEH_PARSE_ERR_PAD1, /* Excessive PAD1 */
+ IPEH_PARSE_ERR_PADN, /* Excessive PADN */
+ IPEH_PARSE_ERR_PADNZ, /* Non-zero padding data */
+ IPEH_PARSE_ERR_EH_TOOBIG, /* Length of EH exceeds limit */
+ IPEH_PARSE_ERR_OPT_TOOBIG, /* Option size exceeds limit */
+ IPEH_PARSE_ERR_OPT_TOOMANY, /* Option count exceeds limit */
+ IPEH_PARSE_ERR_OPT_UNK_DISALW, /* Unknown option disallowed */
+ IPEH_PARSE_ERR_OPT_UNK, /* Unknown option */
+};
+
/* The generic TLV parser assumes that the type value of PAD1 is 0, and PADN
* is 1. This is true for IPv6 Destination and Hop-by-Hop Options. For Segment
* Routing TLVs, PAD1 is also 0, however PADN is 4 so the latter necessitates
@@ -40,6 +53,8 @@ struct ipv6_txoptions *ipeh_fixup_options(struct ipv6_txoptions *opt_space,
#define IPEH_TLV_PADN 1
bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb,
- int max_count, int off, int len);
+ int max_count, int off, int len,
+ bool (*parse_error)(struct sk_buff *skb,
+ int off, enum ipeh_parse_errors error));
#endif /* _NET_IPEH_H */
@@ -54,6 +54,61 @@
Generic functions
*********************/
+/* Handle parse errors from ipeh generic TLV parser */
+static bool ipv6_parse_error(struct sk_buff *skb, int off,
+ enum ipeh_parse_errors error)
+{
+ switch (error) {
+ case IPEH_PARSE_ERR_OPT_UNK_DISALW:
+ /* If unknown TLVs are disallowed by configuration
+ * then always silently drop packet. Note this also
+ * means no ICMP parameter problem is sent which
+ * could be a good property to mitigate a reflection DOS
+ * attack.
+ */
+
+ break;
+ case IPEH_PARSE_ERR_OPT_UNK:
+ /* High order two bits of option type indicate action to
+ * take on unrecognized option.
+ */
+ switch ((skb_network_header(skb)[off] & 0xC0) >> 6) {
+ case 0:
+ /* ignore */
+ return true;
+
+ case 1: /* drop packet */
+ break;
+
+ case 3: /* Send ICMP if not a multicast address and drop packet
+ *
+ * Actually, it is redundant check. icmp_send
+ * will recheck in any case.
+ */
+ if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr))
+ break;
+
+ /* fall through */
+ case 2: /* send ICMP PARM PROB regardless and drop packet */
+ icmpv6_send(skb, ICMPV6_PARAMPROB,
+ ICMPV6_UNK_OPTION, off);
+ break;
+ }
+ break;
+ default:
+ /* Silent drop on other errors */
+
+ break;
+ }
+
+ /* Will be dropping packet */
+
+ __IP6_INC_STATS(dev_net(skb->dev), __in6_dev_get(skb->dev),
+ IPSTATS_MIB_INHDRERRORS);
+
+ return false;
+}
+
static int ipv6_destopt_rcv(struct sk_buff *skb)
{
struct inet6_dev *idev = __in6_dev_get(skb->dev);
@@ -86,7 +141,7 @@ static int ipv6_destopt_rcv(struct sk_buff *skb)
if (ipeh_parse_tlv(tlvprocdestopt_lst, skb,
init_net.ipv6.sysctl.max_dst_opts_cnt,
- 2, extlen - 2)) {
+ 2, extlen - 2, ipv6_parse_error)) {
skb->transport_header += extlen;
opt = IP6CB(skb);
#if IS_ENABLED(CONFIG_IPV6_MIP6)
@@ -514,7 +569,7 @@ int ipv6_parse_hopopts(struct sk_buff *skb)
opt->flags |= IP6SKB_HOPBYHOP;
if (ipeh_parse_tlv(tlvprochopopt_lst, skb,
init_net.ipv6.sysctl.max_hbh_opts_cnt,
- 2, extlen - 2)) {
+ 2, extlen - 2, ipv6_parse_error)) {
skb->transport_header += extlen;
opt = IP6CB(skb);
opt->nhoff = sizeof(struct ipv6hdr);
@@ -143,55 +143,18 @@ struct ipv6_txoptions *ipeh_fixup_options(struct ipv6_txoptions *opt_space,
}
EXPORT_SYMBOL_GPL(ipeh_fixup_options);
-/* An unknown option is detected, decide what to do */
-
-static bool ip6_tlvopt_unknown(struct sk_buff *skb, int optoff,
- bool disallow_unknowns)
-{
- if (disallow_unknowns) {
- /* If unknown TLVs are disallowed by configuration
- * then always silently drop packet. Note this also
- * means no ICMP parameter problem is sent which
- * could be a good property to mitigate a reflection DOS
- * attack.
- */
-
- goto drop;
- }
-
- switch ((skb_network_header(skb)[optoff] & 0xC0) >> 6) {
- case 0: /* ignore */
- return true;
-
- case 1: /* drop packet */
- break;
-
- case 3: /* Send ICMP if not a multicast address and drop packet */
- /* Actually, it is redundant check. icmp_send
- * will recheck in any case.
- */
- if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr))
- break;
- /* fall through */
- case 2: /* send ICMP PARM PROB regardless and drop packet */
- icmpv6_param_prob(skb, ICMPV6_UNK_OPTION, optoff);
- return false;
- }
-
-drop:
- kfree_skb(skb);
- return false;
-}
-
/* Generic extension header TLV parser
*
* Arguments:
* - skb_transport_header points to the extension header containing options
* - off is offset from skb_transport_header where first TLV is
* - len is length of TLV block
+ * - parse_error is protocol specific function to handle parser errors
*/
bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb,
- int max_count, int off, int len)
+ int max_count, int off, int len,
+ bool (*parse_error)(struct sk_buff *skb,
+ int off, enum ipeh_parse_errors error))
{
const unsigned char *nh = skb_network_header(skb);
const struct tlvtype_proc *curr;
@@ -204,8 +167,15 @@ bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb,
max_count = -max_count;
}
- if (skb_transport_offset(skb) + off + len > skb_headlen(skb))
- goto bad;
+ if (skb_transport_offset(skb) + off + len > skb_headlen(skb)) {
+ if (!parse_error(skb, skb_transport_offset(skb),
+ IPEH_PARSE_ERR_EH_TOOBIG)) {
+ kfree_skb(skb);
+ return false;
+ }
+
+ len = skb_headlen(skb) - skb_transport_offset(skb) - off;
+ }
/* ops function based offset on network header */
off += skb_network_header_len(skb);
@@ -218,8 +188,10 @@ bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb,
case IPEH_TLV_PAD1:
optlen = 1;
padlen++;
- if (padlen > 7)
+ if (padlen > 7 &&
+ !parse_error(skb, off, IPEH_PARSE_ERR_PAD1))
goto bad;
+
break;
case IPEH_TLV_PADN:
@@ -229,7 +201,8 @@ bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb,
* See also RFC 4942, Section 2.1.9.5.
*/
padlen += optlen;
- if (padlen > 7)
+ if (padlen > 7 &&
+ !parse_error(skb, off, IPEH_PARSE_ERR_PADN))
goto bad;
/* RFC 4942 recommends receiving hosts to
@@ -237,17 +210,21 @@ bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb,
* only zeroes.
*/
for (i = 2; i < optlen; i++) {
- if (nh[off + i] != 0)
+ if (nh[off + i] != 0 &&
+ !parse_error(skb, off + i,
+ IPEH_PARSE_ERR_PADNZ))
goto bad;
}
break;
default: /* Other TLV code so scan list */
- if (optlen > len)
+ if (optlen > len &&
+ !parse_error(skb, off, IPEH_PARSE_ERR_OPT_TOOBIG))
goto bad;
tlv_count++;
- if (tlv_count > max_count)
+ if (tlv_count > max_count &&
+ !parse_error(skb, off, IPEH_PARSE_ERR_OPT_TOOMANY))
goto bad;
for (curr = procs; curr->type >= 0; curr++) {
@@ -262,8 +239,11 @@ bool ipeh_parse_tlv(const struct tlvtype_proc *procs, struct sk_buff *skb,
}
}
if (curr->type < 0 &&
- !ip6_tlvopt_unknown(skb, off, disallow_unknowns))
- return false;
+ !parse_error(skb, off,
+ disallow_unknowns ?
+ IPEH_PARSE_ERR_OPT_UNK_DISALW :
+ IPEH_PARSE_ERR_OPT_UNK))
+ goto bad;
padlen = 0;
break;