netlink: add mask validation
authorJakub Kicinski <kuba@kernel.org>
Mon, 5 Oct 2020 22:07:38 +0000 (15:07 -0700)
committerDavid S. Miller <davem@davemloft.net>
Tue, 6 Oct 2020 13:25:55 +0000 (06:25 -0700)
We don't have good validation policy for existing unsigned int attrs
which serve as flags (for new ones we could use NLA_BITFIELD32).
With increased use of policy dumping having the validation be
expressed as part of the policy is important. Add validation
policy in form of a mask of supported/valid bits.

Support u64 in the uAPI to be future-proof, but really for now
the embedded mask member can only hold 32 bits, so anything with
bit 32+ set will always fail validation.

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/netlink.h
include/uapi/linux/netlink.h
lib/nlattr.c
net/netlink/policy.c

index c5aa46f379bccc0009009c7dabb80c057369cb60..2b9e41075f199cdac0a63053aa8cb5046fb56954 100644 (file)
@@ -200,6 +200,7 @@ enum nla_policy_validation {
        NLA_VALIDATE_RANGE_WARN_TOO_LONG,
        NLA_VALIDATE_MIN,
        NLA_VALIDATE_MAX,
+       NLA_VALIDATE_MASK,
        NLA_VALIDATE_RANGE_PTR,
        NLA_VALIDATE_FUNCTION,
 };
@@ -317,6 +318,7 @@ struct nla_policy {
        u16             len;
        union {
                const u32 bitfield32_valid;
+               const u32 mask;
                const char *reject_message;
                const struct nla_policy *nested_policy;
                struct netlink_range_validation *range;
@@ -368,6 +370,8 @@ struct nla_policy {
        (tp == NLA_S8 || tp == NLA_S16 || tp == NLA_S32 || tp == NLA_S64)
 
 #define __NLA_ENSURE(condition) BUILD_BUG_ON_ZERO(!(condition))
+#define NLA_ENSURE_UINT_TYPE(tp)                       \
+       (__NLA_ENSURE(__NLA_IS_UINT_TYPE(tp)) + tp)
 #define NLA_ENSURE_UINT_OR_BINARY_TYPE(tp)             \
        (__NLA_ENSURE(__NLA_IS_UINT_TYPE(tp) || \
                      tp == NLA_MSECS ||                \
@@ -416,6 +420,12 @@ struct nla_policy {
        .max = _max,                                    \
 }
 
+#define NLA_POLICY_MASK(tp, _mask) {                   \
+       .type = NLA_ENSURE_UINT_TYPE(tp),               \
+       .validation_type = NLA_VALIDATE_MASK,           \
+       .mask = _mask,                                  \
+}
+
 #define NLA_POLICY_VALIDATE_FN(tp, fn, ...) {          \
        .type = NLA_ENSURE_NO_VALIDATION_PTR(tp),       \
        .validation_type = NLA_VALIDATE_FUNCTION,       \
index eac8a6a648ea3cd7c6df262f7451c0597e63b24f..d02e472ba54c23906e704b23780268ed68eaf6ec 100644 (file)
@@ -331,6 +331,7 @@ enum netlink_attribute_type {
  *     the index, if limited inside the nesting (U32)
  * @NL_POLICY_TYPE_ATTR_BITFIELD32_MASK: valid mask for the
  *     bitfield32 type (U32)
+ * @NL_POLICY_TYPE_ATTR_MASK: mask of valid bits for unsigned integers (U64)
  * @NL_POLICY_TYPE_ATTR_PAD: pad attribute for 64-bit alignment
  */
 enum netlink_policy_type_attr {
@@ -346,6 +347,7 @@ enum netlink_policy_type_attr {
        NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE,
        NL_POLICY_TYPE_ATTR_BITFIELD32_MASK,
        NL_POLICY_TYPE_ATTR_PAD,
+       NL_POLICY_TYPE_ATTR_MASK,
 
        /* keep last */
        __NL_POLICY_TYPE_ATTR_MAX,
index 80ff9fe83696f881a5b5cda30c2afe13cd33b25a..9c99f5daa4d2ada5192038099de17feffbe421ca 100644 (file)
@@ -323,6 +323,37 @@ static int nla_validate_int_range(const struct nla_policy *pt,
        }
 }
 
+static int nla_validate_mask(const struct nla_policy *pt,
+                            const struct nlattr *nla,
+                            struct netlink_ext_ack *extack)
+{
+       u64 value;
+
+       switch (pt->type) {
+       case NLA_U8:
+               value = nla_get_u8(nla);
+               break;
+       case NLA_U16:
+               value = nla_get_u16(nla);
+               break;
+       case NLA_U32:
+               value = nla_get_u32(nla);
+               break;
+       case NLA_U64:
+               value = nla_get_u64(nla);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (value & ~(u64)pt->mask) {
+               NL_SET_ERR_MSG_ATTR(extack, nla, "reserved bit set");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
 static int validate_nla(const struct nlattr *nla, int maxtype,
                        const struct nla_policy *policy, unsigned int validate,
                        struct netlink_ext_ack *extack, unsigned int depth)
@@ -503,6 +534,11 @@ static int validate_nla(const struct nlattr *nla, int maxtype,
                if (err)
                        return err;
                break;
+       case NLA_VALIDATE_MASK:
+               err = nla_validate_mask(pt, nla, extack);
+               if (err)
+                       return err;
+               break;
        case NLA_VALIDATE_FUNCTION:
                if (pt->validate) {
                        err = pt->validate(nla, extack);
index cf23c015172190c2b704a10260ba6348656ad54f..ee26d01328ee682bc34f2bb955c717eb080f07e6 100644 (file)
@@ -263,6 +263,14 @@ send_attribute:
                else
                        type = NL_ATTR_TYPE_U64;
 
+               if (pt->validation_type == NLA_VALIDATE_MASK) {
+                       if (nla_put_u64_64bit(skb, NL_POLICY_TYPE_ATTR_MASK,
+                                             pt->mask,
+                                             NL_POLICY_TYPE_ATTR_PAD))
+                               goto nla_put_failure;
+                       break;
+               }
+
                nla_get_range_unsigned(pt, &range);
 
                if (nla_put_u64_64bit(skb, NL_POLICY_TYPE_ATTR_MIN_VALUE_U,