Commit 31df0cff authored by David S. Miller's avatar David S. Miller

Merge branch 'netlink-nested-policy-validation'

Johannes Berg says:

====================
netlink: nested policy validation

This adds nested policy validation, which lets you specify the
nested attribute type, e.g. NLA_NESTED with sub-policy, or the
new NLA_NESTED_ARRAY with sub-sub-policy.

Changes in v2:
 * move setting the bad attr pointer/message into validate_nla()
 * remove the recursion patch since that's no longer needed
 * simply skip the generic bad attr pointer/message setting in
   case of nested nla_validate() failing since that could fail
   only due to validate_nla() failing inside, which already sets
   the extack information

Changes in v3:
 * fix NLA_REJECT to have an error message if none is in policy
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 1042caa7 1501d135
...@@ -172,7 +172,7 @@ enum { ...@@ -172,7 +172,7 @@ enum {
NLA_FLAG, NLA_FLAG,
NLA_MSECS, NLA_MSECS,
NLA_NESTED, NLA_NESTED,
NLA_NESTED_COMPAT, NLA_NESTED_ARRAY,
NLA_NUL_STRING, NLA_NUL_STRING,
NLA_BINARY, NLA_BINARY,
NLA_S8, NLA_S8,
...@@ -201,9 +201,11 @@ enum { ...@@ -201,9 +201,11 @@ enum {
* NLA_NUL_STRING Maximum length of string (excluding NUL) * NLA_NUL_STRING Maximum length of string (excluding NUL)
* NLA_FLAG Unused * NLA_FLAG Unused
* NLA_BINARY Maximum length of attribute payload * NLA_BINARY Maximum length of attribute payload
* NLA_NESTED Don't use `len' field -- length verification is * NLA_NESTED,
* done by checking len of nested header (or empty) * NLA_NESTED_ARRAY Length verification is done by checking len of
* NLA_NESTED_COMPAT Minimum length of structure payload * nested header (or empty); len field is used if
* validation_data is also used, for the max attr
* number in the nested policy.
* NLA_U8, NLA_U16, * NLA_U8, NLA_U16,
* NLA_U32, NLA_U64, * NLA_U32, NLA_U64,
* NLA_S8, NLA_S16, * NLA_S8, NLA_S16,
...@@ -226,6 +228,16 @@ enum { ...@@ -226,6 +228,16 @@ enum {
* NLA_REJECT This attribute is always rejected and validation data * NLA_REJECT This attribute is always rejected and validation data
* may point to a string to report as the error instead * may point to a string to report as the error instead
* of the generic one in extended ACK. * of the generic one in extended ACK.
* NLA_NESTED Points to a nested policy to validate, must also set
* `len' to the max attribute number.
* Note that nla_parse() will validate, but of course not
* parse, the nested sub-policies.
* NLA_NESTED_ARRAY Points to a nested policy to validate, must also set
* `len' to the max attribute number. The difference to
* NLA_NESTED is the structure - NLA_NESTED has the
* nested attributes directly inside, while an array has
* the nested attributes at another level down and the
* attributes directly in the nesting don't matter.
* All other Unused * All other Unused
* *
* Example: * Example:
...@@ -239,7 +251,7 @@ enum { ...@@ -239,7 +251,7 @@ enum {
struct nla_policy { struct nla_policy {
u16 type; u16 type;
u16 len; u16 len;
void *validation_data; const void *validation_data;
}; };
#define NLA_POLICY_EXACT_LEN(_len) { .type = NLA_EXACT_LEN, .len = _len } #define NLA_POLICY_EXACT_LEN(_len) { .type = NLA_EXACT_LEN, .len = _len }
...@@ -249,6 +261,11 @@ struct nla_policy { ...@@ -249,6 +261,11 @@ struct nla_policy {
#define NLA_POLICY_ETH_ADDR NLA_POLICY_EXACT_LEN(ETH_ALEN) #define NLA_POLICY_ETH_ADDR NLA_POLICY_EXACT_LEN(ETH_ALEN)
#define NLA_POLICY_ETH_ADDR_COMPAT NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN) #define NLA_POLICY_ETH_ADDR_COMPAT NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN)
#define NLA_POLICY_NESTED(maxattr, policy) \
{ .type = NLA_NESTED, .validation_data = policy, .len = maxattr }
#define NLA_POLICY_NESTED_ARRAY(maxattr, policy) \
{ .type = NLA_NESTED_ARRAY, .validation_data = policy, .len = maxattr }
/** /**
* struct nl_info - netlink source information * struct nl_info - netlink source information
* @nlh: Netlink message header of original request * @nlh: Netlink message header of original request
......
...@@ -45,12 +45,11 @@ static const u8 nla_attr_minlen[NLA_TYPE_MAX+1] = { ...@@ -45,12 +45,11 @@ static const u8 nla_attr_minlen[NLA_TYPE_MAX+1] = {
}; };
static int validate_nla_bitfield32(const struct nlattr *nla, static int validate_nla_bitfield32(const struct nlattr *nla,
u32 *valid_flags_allowed) const u32 *valid_flags_mask)
{ {
const struct nla_bitfield32 *bf = nla_data(nla); const struct nla_bitfield32 *bf = nla_data(nla);
u32 *valid_flags_mask = valid_flags_allowed;
if (!valid_flags_allowed) if (!valid_flags_mask)
return -EINVAL; return -EINVAL;
/*disallow invalid bit selector */ /*disallow invalid bit selector */
...@@ -68,12 +67,41 @@ static int validate_nla_bitfield32(const struct nlattr *nla, ...@@ -68,12 +67,41 @@ static int validate_nla_bitfield32(const struct nlattr *nla,
return 0; return 0;
} }
static int nla_validate_array(const struct nlattr *head, int len, int maxtype,
const struct nla_policy *policy,
struct netlink_ext_ack *extack)
{
const struct nlattr *entry;
int rem;
nla_for_each_attr(entry, head, len, rem) {
int ret;
if (nla_len(entry) == 0)
continue;
if (nla_len(entry) < NLA_HDRLEN) {
NL_SET_ERR_MSG_ATTR(extack, entry,
"Array element too short");
return -ERANGE;
}
ret = nla_validate(nla_data(entry), nla_len(entry),
maxtype, policy, extack);
if (ret < 0)
return ret;
}
return 0;
}
static int validate_nla(const struct nlattr *nla, int maxtype, static int validate_nla(const struct nlattr *nla, int maxtype,
const struct nla_policy *policy, const struct nla_policy *policy,
const char **error_msg) struct netlink_ext_ack *extack)
{ {
const struct nla_policy *pt; const struct nla_policy *pt;
int minlen = 0, attrlen = nla_len(nla), type = nla_type(nla); int minlen = 0, attrlen = nla_len(nla), type = nla_type(nla);
int err = -ERANGE;
if (type <= 0 || type > maxtype) if (type <= 0 || type > maxtype)
return 0; return 0;
...@@ -91,24 +119,31 @@ static int validate_nla(const struct nlattr *nla, int maxtype, ...@@ -91,24 +119,31 @@ static int validate_nla(const struct nlattr *nla, int maxtype,
switch (pt->type) { switch (pt->type) {
case NLA_EXACT_LEN: case NLA_EXACT_LEN:
if (attrlen != pt->len) if (attrlen != pt->len)
return -ERANGE; goto out_err;
break; break;
case NLA_REJECT: case NLA_REJECT:
if (pt->validation_data && error_msg) if (extack && pt->validation_data) {
*error_msg = pt->validation_data; NL_SET_BAD_ATTR(extack, nla);
return -EINVAL; extack->_msg = pt->validation_data;
return -EINVAL;
}
err = -EINVAL;
goto out_err;
case NLA_FLAG: case NLA_FLAG:
if (attrlen > 0) if (attrlen > 0)
return -ERANGE; goto out_err;
break; break;
case NLA_BITFIELD32: case NLA_BITFIELD32:
if (attrlen != sizeof(struct nla_bitfield32)) if (attrlen != sizeof(struct nla_bitfield32))
return -ERANGE; goto out_err;
return validate_nla_bitfield32(nla, pt->validation_data); err = validate_nla_bitfield32(nla, pt->validation_data);
if (err)
goto out_err;
break;
case NLA_NUL_STRING: case NLA_NUL_STRING:
if (pt->len) if (pt->len)
...@@ -116,13 +151,15 @@ static int validate_nla(const struct nlattr *nla, int maxtype, ...@@ -116,13 +151,15 @@ static int validate_nla(const struct nlattr *nla, int maxtype,
else else
minlen = attrlen; minlen = attrlen;
if (!minlen || memchr(nla_data(nla), '\0', minlen) == NULL) if (!minlen || memchr(nla_data(nla), '\0', minlen) == NULL) {
return -EINVAL; err = -EINVAL;
goto out_err;
}
/* fall through */ /* fall through */
case NLA_STRING: case NLA_STRING:
if (attrlen < 1) if (attrlen < 1)
return -ERANGE; goto out_err;
if (pt->len) { if (pt->len) {
char *buf = nla_data(nla); char *buf = nla_data(nla);
...@@ -131,32 +168,58 @@ static int validate_nla(const struct nlattr *nla, int maxtype, ...@@ -131,32 +168,58 @@ static int validate_nla(const struct nlattr *nla, int maxtype,
attrlen--; attrlen--;
if (attrlen > pt->len) if (attrlen > pt->len)
return -ERANGE; goto out_err;
} }
break; break;
case NLA_BINARY: case NLA_BINARY:
if (pt->len && attrlen > pt->len) if (pt->len && attrlen > pt->len)
return -ERANGE; goto out_err;
break; break;
case NLA_NESTED_COMPAT:
if (attrlen < pt->len)
return -ERANGE;
if (attrlen < NLA_ALIGN(pt->len))
break;
if (attrlen < NLA_ALIGN(pt->len) + NLA_HDRLEN)
return -ERANGE;
nla = nla_data(nla) + NLA_ALIGN(pt->len);
if (attrlen < NLA_ALIGN(pt->len) + NLA_HDRLEN + nla_len(nla))
return -ERANGE;
break;
case NLA_NESTED: case NLA_NESTED:
/* a nested attributes is allowed to be empty; if its not, /* a nested attributes is allowed to be empty; if its not,
* it must have a size of at least NLA_HDRLEN. * it must have a size of at least NLA_HDRLEN.
*/ */
if (attrlen == 0) if (attrlen == 0)
break; break;
if (attrlen < NLA_HDRLEN)
goto out_err;
if (pt->validation_data) {
err = nla_validate(nla_data(nla), nla_len(nla), pt->len,
pt->validation_data, extack);
if (err < 0) {
/*
* return directly to preserve the inner
* error message/attribute pointer
*/
return err;
}
}
break;
case NLA_NESTED_ARRAY:
/* a nested array attribute is allowed to be empty; if its not,
* it must have a size of at least NLA_HDRLEN.
*/
if (attrlen == 0)
break;
if (attrlen < NLA_HDRLEN)
goto out_err;
if (pt->validation_data) {
int err;
err = nla_validate_array(nla_data(nla), nla_len(nla),
pt->len, pt->validation_data,
extack);
if (err < 0) {
/*
* return directly to preserve the inner
* error message/attribute pointer
*/
return err;
}
}
break;
default: default:
if (pt->len) if (pt->len)
minlen = pt->len; minlen = pt->len;
...@@ -164,10 +227,13 @@ static int validate_nla(const struct nlattr *nla, int maxtype, ...@@ -164,10 +227,13 @@ static int validate_nla(const struct nlattr *nla, int maxtype,
minlen = nla_attr_minlen[pt->type]; minlen = nla_attr_minlen[pt->type];
if (attrlen < minlen) if (attrlen < minlen)
return -ERANGE; goto out_err;
} }
return 0; return 0;
out_err:
NL_SET_ERR_MSG_ATTR(extack, nla, "Attribute failed policy validation");
return err;
} }
/** /**
...@@ -192,12 +258,10 @@ int nla_validate(const struct nlattr *head, int len, int maxtype, ...@@ -192,12 +258,10 @@ int nla_validate(const struct nlattr *head, int len, int maxtype,
int rem; int rem;
nla_for_each_attr(nla, head, len, rem) { nla_for_each_attr(nla, head, len, rem) {
int err = validate_nla(nla, maxtype, policy, NULL); int err = validate_nla(nla, maxtype, policy, extack);
if (err < 0) { if (err < 0)
NL_SET_BAD_ATTR(extack, nla);
return err; return err;
}
} }
return 0; return 0;
...@@ -253,7 +317,7 @@ int nla_parse(struct nlattr **tb, int maxtype, const struct nlattr *head, ...@@ -253,7 +317,7 @@ int nla_parse(struct nlattr **tb, int maxtype, const struct nlattr *head,
struct netlink_ext_ack *extack) struct netlink_ext_ack *extack)
{ {
const struct nlattr *nla; const struct nlattr *nla;
int rem, err; int rem;
memset(tb, 0, sizeof(struct nlattr *) * (maxtype + 1)); memset(tb, 0, sizeof(struct nlattr *) * (maxtype + 1));
...@@ -261,17 +325,12 @@ int nla_parse(struct nlattr **tb, int maxtype, const struct nlattr *head, ...@@ -261,17 +325,12 @@ int nla_parse(struct nlattr **tb, int maxtype, const struct nlattr *head,
u16 type = nla_type(nla); u16 type = nla_type(nla);
if (type > 0 && type <= maxtype) { if (type > 0 && type <= maxtype) {
static const char _msg[] = "Attribute failed policy validation";
const char *msg = _msg;
if (policy) { if (policy) {
err = validate_nla(nla, maxtype, policy, &msg); int err = validate_nla(nla, maxtype, policy,
if (err < 0) { extack);
NL_SET_BAD_ATTR(extack, nla);
if (extack) if (err < 0)
extack->_msg = msg; return err;
goto errout;
}
} }
tb[type] = (struct nlattr *)nla; tb[type] = (struct nlattr *)nla;
...@@ -282,9 +341,7 @@ int nla_parse(struct nlattr **tb, int maxtype, const struct nlattr *head, ...@@ -282,9 +341,7 @@ int nla_parse(struct nlattr **tb, int maxtype, const struct nlattr *head,
pr_warn_ratelimited("netlink: %d bytes leftover after parsing attributes in process `%s'.\n", pr_warn_ratelimited("netlink: %d bytes leftover after parsing attributes in process `%s'.\n",
rem, current->comm); rem, current->comm);
err = 0; return 0;
errout:
return err;
} }
EXPORT_SYMBOL(nla_parse); EXPORT_SYMBOL(nla_parse);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment