ip_tun_fill_encap_opts() and its helpers dump LWTUNNEL_IP_OPTS,
LWTUNNEL_IP6_OPTS and the geneve, vxlan and erspan options nested in
them without NLA_F_NESTED. Commit ed02551f58b9 ("lwtunnel: change to
use nla_parse_nested on new options") made the parsing of all of them
strict, on the grounds that new attributes should be strict from the
start, but left the dump as it was, and the two sides have disagreed
ever since. So "ip route restore" cannot send back the ip and ip6
encap routes with tunnel options saved by "ip route save":
# ip route add 192.0.2.0/24 encap ip id 1 dst 198.51.100.2 \
geneve_opts 0:0:12121212 dev dummy0
# ip route save 192.0.2.0/24 > route.bin
# ip route del 192.0.2.0/24
# ip route restore < route.bin
Error: NLA_F_NESTED is missing.
The flag is required at three levels:
- ip_tun_policy and ip6_tun_policy validate LWTUNNEL_IP_OPTS and
LWTUNNEL_IP6_OPTS strictly through .strict_start_type.
- ip_tun_parse_opts() validates the options nested in them with
nla_validate(), which is strict as well.
- ip_tun_parse_opts_geneve(), ip_tun_parse_opts_vxlan() and
ip_tun_parse_opts_erspan() parse each option with
nla_parse_nested().
Start the strict validation after LWTUNNEL_IP(6)_OPTS, validate the
options with lwtunnel_nla_validate(), which is nla_validate() without
the NLA_F_NESTED check, and parse each option with lwtunnel_nla_parse().
netlink has no validation level that keeps the other strict checks and
drops that one, so lwtunnel_nla_validate() clears the flag when calling
__nla_validate(). Nothing else is relaxed: unknown option types and
trailing bytes after the last option are still rejected, and attributes
added to ip_tun_policy and ip6_tun_policy later are still validated
strictly.
Fixes: ed02551f58b9 ("lwtunnel: change to use nla_parse_nested on new options")
Fixes: 2f1d370b997a ("lwtunnel: add support for multiple geneve opts")
Fixes: 7b6a70f73764 ("lwtunnel: be STRICT to validate the new
LWTUNNEL_IP(6)_OPTS")
Assisted-by: LLM
Signed-off-by: Yuya Kusakabe <[email protected]>
---
include/net/lwtunnel.h | 22 ++++++++++++++++++++++
net/ipv4/ip_tunnel_core.c | 24 ++++++++++++++----------
2 files changed, 36 insertions(+), 10 deletions(-)
diff --git a/include/net/lwtunnel.h b/include/net/lwtunnel.h
index 046978d6224c..59d7ec7b04f2 100644
--- a/include/net/lwtunnel.h
+++ b/include/net/lwtunnel.h
@@ -80,6 +80,28 @@ static inline int lwtunnel_nla_parse(struct nlattr *tb[],
int maxtype,
extack);
}
+/**
+ * lwtunnel_nla_validate - validate the attributes nested in an lwtunnel encap
+ * @nla: encap attribute passed to &lwtunnel_encap_ops.build_state, or an
+ * attribute nested in it
+ * @maxtype: maximum attribute type to be expected
+ * @policy: validation policy
+ * @extack: extended ACK report struct
+ *
+ * Like nla_validate(), except that NLA_F_NESTED is not required on the
+ * attributes nested in @nla, for the reason given for lwtunnel_nla_parse().
+ *
+ * Return: 0 on success or a negative error code.
+ */
+static inline int lwtunnel_nla_validate(const struct nlattr *nla, int maxtype,
+ const struct nla_policy *policy,
+ struct netlink_ext_ack *extack)
+{
+ return __nla_validate(nla_data(nla), nla_len(nla), maxtype, policy,
+ NL_VALIDATE_STRICT & ~NL_VALIDATE_NESTED,
+ extack);
+}
+
#ifdef CONFIG_LWTUNNEL
DECLARE_STATIC_KEY_FALSE(nf_hooks_lwtunnel_enabled);
diff --git a/net/ipv4/ip_tunnel_core.c b/net/ipv4/ip_tunnel_core.c
index bab42b9e277f..c87827ffc347 100644
--- a/net/ipv4/ip_tunnel_core.c
+++ b/net/ipv4/ip_tunnel_core.c
@@ -466,7 +466,9 @@ int skb_tunnel_check_pmtu(struct sk_buff *skb, struct
dst_entry *encap_dst,
EXPORT_SYMBOL(skb_tunnel_check_pmtu);
static const struct nla_policy ip_tun_policy[LWTUNNEL_IP_MAX + 1] = {
- [LWTUNNEL_IP_UNSPEC] = { .strict_start_type = LWTUNNEL_IP_OPTS },
+ [LWTUNNEL_IP_UNSPEC] = {
+ .strict_start_type = LWTUNNEL_IP_OPTS + 1
+ },
[LWTUNNEL_IP_ID] = { .type = NLA_U64 },
[LWTUNNEL_IP_DST] = { .type = NLA_U32 },
[LWTUNNEL_IP_SRC] = { .type = NLA_U32 },
@@ -509,8 +511,8 @@ static int ip_tun_parse_opts_geneve(struct nlattr *attr,
struct nlattr *tb[LWTUNNEL_IP_OPT_GENEVE_MAX + 1];
int data_len, err;
- err = nla_parse_nested(tb, LWTUNNEL_IP_OPT_GENEVE_MAX, attr,
- geneve_opt_policy, extack);
+ err = lwtunnel_nla_parse(tb, LWTUNNEL_IP_OPT_GENEVE_MAX, attr,
+ geneve_opt_policy, extack);
if (err)
return err;
@@ -546,8 +548,8 @@ static int ip_tun_parse_opts_vxlan(struct nlattr *attr,
struct nlattr *tb[LWTUNNEL_IP_OPT_VXLAN_MAX + 1];
int err;
- err = nla_parse_nested(tb, LWTUNNEL_IP_OPT_VXLAN_MAX, attr,
- vxlan_opt_policy, extack);
+ err = lwtunnel_nla_parse(tb, LWTUNNEL_IP_OPT_VXLAN_MAX, attr,
+ vxlan_opt_policy, extack);
if (err)
return err;
@@ -575,8 +577,8 @@ static int ip_tun_parse_opts_erspan(struct nlattr *attr,
int err;
u8 ver;
- err = nla_parse_nested(tb, LWTUNNEL_IP_OPT_ERSPAN_MAX, attr,
- erspan_opt_policy, extack);
+ err = lwtunnel_nla_parse(tb, LWTUNNEL_IP_OPT_ERSPAN_MAX, attr,
+ erspan_opt_policy, extack);
if (err)
return err;
@@ -626,8 +628,8 @@ static int ip_tun_parse_opts(struct nlattr *attr, struct
ip_tunnel_info *info,
if (!attr)
return 0;
- err = nla_validate(nla_data(attr), nla_len(attr), LWTUNNEL_IP_OPTS_MAX,
- ip_opts_policy, extack);
+ err = lwtunnel_nla_validate(attr, LWTUNNEL_IP_OPTS_MAX,
+ ip_opts_policy, extack);
if (err)
return err;
@@ -975,7 +977,9 @@ static const struct lwtunnel_encap_ops ip_tun_lwt_ops = {
};
static const struct nla_policy ip6_tun_policy[LWTUNNEL_IP6_MAX + 1] = {
- [LWTUNNEL_IP6_UNSPEC] = { .strict_start_type = LWTUNNEL_IP6_OPTS },
+ [LWTUNNEL_IP6_UNSPEC] = {
+ .strict_start_type = LWTUNNEL_IP6_OPTS + 1
+ },
[LWTUNNEL_IP6_ID] = { .type = NLA_U64 },
[LWTUNNEL_IP6_DST] = { .len = sizeof(struct in6_addr) },
[LWTUNNEL_IP6_SRC] = { .len = sizeof(struct in6_addr) },
--
2.50.1