Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netlink: export policy in extended ACK

Add a new attribute NLMSGERR_ATTR_POLICY to the extended ACK
to advertise the policy, e.g. if an attribute was out of range,
you'll know the range that's permissible.

Add new NL_SET_ERR_MSG_ATTR_POL() and NL_SET_ERR_MSG_ATTR_POL()
macros to set this, since realistically it's only useful to do
this when the bad attribute (offset) is also returned.

Use it in lib/nlattr.c which practically does all the policy
validation.

v2:
- add and use netlink_policy_dump_attr_size_estimate()
v3:
- remove redundant break
v4:
- really remove redundant break ... sorry

Reviewed-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Johannes Berg and committed by
Jakub Kicinski
44f3625b d2681e93

+111 -28
+21 -11
include/linux/netlink.h
··· 68 68 * @_msg: message string to report - don't access directly, use 69 69 * %NL_SET_ERR_MSG 70 70 * @bad_attr: attribute with error 71 + * @policy: policy for a bad attribute 71 72 * @cookie: cookie data to return to userspace (for success) 72 73 * @cookie_len: actual cookie data length 73 74 */ 74 75 struct netlink_ext_ack { 75 76 const char *_msg; 76 77 const struct nlattr *bad_attr; 78 + const struct nla_policy *policy; 77 79 u8 cookie[NETLINK_MAX_COOKIE_LEN]; 78 80 u8 cookie_len; 79 81 }; ··· 97 95 #define NL_SET_ERR_MSG_MOD(extack, msg) \ 98 96 NL_SET_ERR_MSG((extack), KBUILD_MODNAME ": " msg) 99 97 100 - #define NL_SET_BAD_ATTR(extack, attr) do { \ 101 - if ((extack)) \ 98 + #define NL_SET_BAD_ATTR_POLICY(extack, attr, pol) do { \ 99 + if ((extack)) { \ 102 100 (extack)->bad_attr = (attr); \ 103 - } while (0) 104 - 105 - #define NL_SET_ERR_MSG_ATTR(extack, attr, msg) do { \ 106 - static const char __msg[] = msg; \ 107 - struct netlink_ext_ack *__extack = (extack); \ 108 - \ 109 - if (__extack) { \ 110 - __extack->_msg = __msg; \ 111 - __extack->bad_attr = (attr); \ 101 + (extack)->policy = (pol); \ 112 102 } \ 113 103 } while (0) 104 + 105 + #define NL_SET_BAD_ATTR(extack, attr) NL_SET_BAD_ATTR_POLICY(extack, attr, NULL) 106 + 107 + #define NL_SET_ERR_MSG_ATTR_POL(extack, attr, pol, msg) do { \ 108 + static const char __msg[] = msg; \ 109 + struct netlink_ext_ack *__extack = (extack); \ 110 + \ 111 + if (__extack) { \ 112 + __extack->_msg = __msg; \ 113 + __extack->bad_attr = (attr); \ 114 + __extack->policy = (pol); \ 115 + } \ 116 + } while (0) 117 + 118 + #define NL_SET_ERR_MSG_ATTR(extack, attr, msg) \ 119 + NL_SET_ERR_MSG_ATTR_POL(extack, attr, NULL, msg) 114 120 115 121 static inline void nl_set_extack_cookie_u64(struct netlink_ext_ack *extack, 116 122 u64 cookie)
+4
include/net/netlink.h
··· 1957 1957 bool netlink_policy_dump_loop(struct netlink_policy_dump_state *state); 1958 1958 int netlink_policy_dump_write(struct sk_buff *skb, 1959 1959 struct netlink_policy_dump_state *state); 1960 + int netlink_policy_dump_attr_size_estimate(const struct nla_policy *pt); 1961 + int netlink_policy_dump_write_attr(struct sk_buff *skb, 1962 + const struct nla_policy *pt, 1963 + int nestattr); 1960 1964 void netlink_policy_dump_free(struct netlink_policy_dump_state *state); 1961 1965 1962 1966 #endif
+2
include/uapi/linux/netlink.h
··· 129 129 * @NLMSGERR_ATTR_COOKIE: arbitrary subsystem specific cookie to 130 130 * be used - in the success case - to identify a created 131 131 * object or operation or similar (binary) 132 + * @NLMSGERR_ATTR_POLICY: policy for a rejected attribute 132 133 * @__NLMSGERR_ATTR_MAX: number of attributes 133 134 * @NLMSGERR_ATTR_MAX: highest attribute number 134 135 */ ··· 138 137 NLMSGERR_ATTR_MSG, 139 138 NLMSGERR_ATTR_OFFS, 140 139 NLMSGERR_ATTR_COOKIE, 140 + NLMSGERR_ATTR_POLICY, 141 141 142 142 __NLMSGERR_ATTR_MAX, 143 143 NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 1
+18 -17
lib/nlattr.c
··· 96 96 continue; 97 97 98 98 if (nla_len(entry) < NLA_HDRLEN) { 99 - NL_SET_ERR_MSG_ATTR(extack, entry, 100 - "Array element too short"); 99 + NL_SET_ERR_MSG_ATTR_POL(extack, entry, policy, 100 + "Array element too short"); 101 101 return -ERANGE; 102 102 } 103 103 ··· 195 195 pr_warn_ratelimited("netlink: '%s': attribute type %d has an invalid length.\n", 196 196 current->comm, pt->type); 197 197 if (validate & NL_VALIDATE_STRICT_ATTRS) { 198 - NL_SET_ERR_MSG_ATTR(extack, nla, 199 - "invalid attribute length"); 198 + NL_SET_ERR_MSG_ATTR_POL(extack, nla, pt, 199 + "invalid attribute length"); 200 200 return -EINVAL; 201 201 } 202 202 ··· 208 208 bool binary = pt->type == NLA_BINARY; 209 209 210 210 if (binary) 211 - NL_SET_ERR_MSG_ATTR(extack, nla, 212 - "binary attribute size out of range"); 211 + NL_SET_ERR_MSG_ATTR_POL(extack, nla, pt, 212 + "binary attribute size out of range"); 213 213 else 214 - NL_SET_ERR_MSG_ATTR(extack, nla, 215 - "integer out of range"); 214 + NL_SET_ERR_MSG_ATTR_POL(extack, nla, pt, 215 + "integer out of range"); 216 216 217 217 return -ERANGE; 218 218 } ··· 291 291 nla_get_range_signed(pt, &range); 292 292 293 293 if (value < range.min || value > range.max) { 294 - NL_SET_ERR_MSG_ATTR(extack, nla, 295 - "integer out of range"); 294 + NL_SET_ERR_MSG_ATTR_POL(extack, nla, pt, 295 + "integer out of range"); 296 296 return -ERANGE; 297 297 } 298 298 ··· 377 377 pr_warn_ratelimited("netlink: '%s': attribute type %d has an invalid length.\n", 378 378 current->comm, type); 379 379 if (validate & NL_VALIDATE_STRICT_ATTRS) { 380 - NL_SET_ERR_MSG_ATTR(extack, nla, 381 - "invalid attribute length"); 380 + NL_SET_ERR_MSG_ATTR_POL(extack, nla, pt, 381 + "invalid attribute length"); 382 382 return -EINVAL; 383 383 } 384 384 } ··· 386 386 if (validate & NL_VALIDATE_NESTED) { 387 387 if ((pt->type == NLA_NESTED || pt->type == NLA_NESTED_ARRAY) && 388 388 !(nla->nla_type & NLA_F_NESTED)) { 389 - NL_SET_ERR_MSG_ATTR(extack, nla, 390 - "NLA_F_NESTED is missing"); 389 + NL_SET_ERR_MSG_ATTR_POL(extack, nla, pt, 390 + "NLA_F_NESTED is missing"); 391 391 return -EINVAL; 392 392 } 393 393 if (pt->type != NLA_NESTED && pt->type != NLA_NESTED_ARRAY && 394 394 pt->type != NLA_UNSPEC && (nla->nla_type & NLA_F_NESTED)) { 395 - NL_SET_ERR_MSG_ATTR(extack, nla, 396 - "NLA_F_NESTED not expected"); 395 + NL_SET_ERR_MSG_ATTR_POL(extack, nla, pt, 396 + "NLA_F_NESTED not expected"); 397 397 return -EINVAL; 398 398 } 399 399 } ··· 550 550 551 551 return 0; 552 552 out_err: 553 - NL_SET_ERR_MSG_ATTR(extack, nla, "Attribute failed policy validation"); 553 + NL_SET_ERR_MSG_ATTR_POL(extack, nla, pt, 554 + "Attribute failed policy validation"); 554 555 return err; 555 556 } 556 557
+5
net/netlink/af_netlink.c
··· 2420 2420 tlvlen += nla_total_size(sizeof(u32)); 2421 2421 if (nlk_has_extack && extack && extack->cookie_len) 2422 2422 tlvlen += nla_total_size(extack->cookie_len); 2423 + if (err && nlk_has_extack && extack && extack->policy) 2424 + tlvlen += netlink_policy_dump_attr_size_estimate(extack->policy); 2423 2425 2424 2426 if (tlvlen) 2425 2427 flags |= NLM_F_ACK_TLVS; ··· 2454 2452 if (extack->cookie_len) 2455 2453 WARN_ON(nla_put(skb, NLMSGERR_ATTR_COOKIE, 2456 2454 extack->cookie_len, extack->cookie)); 2455 + if (extack->policy) 2456 + netlink_policy_dump_write_attr(skb, extack->policy, 2457 + NLMSGERR_ATTR_POLICY); 2457 2458 } 2458 2459 2459 2460 nlmsg_end(skb, rep);
+61
net/netlink/policy.c
··· 196 196 return !netlink_policy_dump_finished(state); 197 197 } 198 198 199 + int netlink_policy_dump_attr_size_estimate(const struct nla_policy *pt) 200 + { 201 + /* nested + type */ 202 + int common = 2 * nla_attr_size(sizeof(u32)); 203 + 204 + switch (pt->type) { 205 + case NLA_UNSPEC: 206 + case NLA_REJECT: 207 + /* these actually don't need any space */ 208 + return 0; 209 + case NLA_NESTED: 210 + case NLA_NESTED_ARRAY: 211 + /* common, policy idx, policy maxattr */ 212 + return common + 2 * nla_attr_size(sizeof(u32)); 213 + case NLA_U8: 214 + case NLA_U16: 215 + case NLA_U32: 216 + case NLA_U64: 217 + case NLA_MSECS: 218 + case NLA_S8: 219 + case NLA_S16: 220 + case NLA_S32: 221 + case NLA_S64: 222 + /* maximum is common, u64 min/max with padding */ 223 + return common + 224 + 2 * (nla_attr_size(0) + nla_attr_size(sizeof(u64))); 225 + case NLA_BITFIELD32: 226 + return common + nla_attr_size(sizeof(u32)); 227 + case NLA_STRING: 228 + case NLA_NUL_STRING: 229 + case NLA_BINARY: 230 + /* maximum is common, u32 min-length/max-length */ 231 + return common + 2 * nla_attr_size(sizeof(u32)); 232 + case NLA_FLAG: 233 + return common; 234 + } 235 + 236 + /* this should then cause a warning later */ 237 + return 0; 238 + } 239 + 199 240 static int 200 241 __netlink_policy_dump_write_attr(struct netlink_policy_dump_state *state, 201 242 struct sk_buff *skb, 202 243 const struct nla_policy *pt, 203 244 int nestattr) 204 245 { 246 + int estimate = netlink_policy_dump_attr_size_estimate(pt); 205 247 enum netlink_attribute_type type; 206 248 struct nlattr *attr; 207 249 ··· 376 334 goto nla_put_failure; 377 335 378 336 nla_nest_end(skb, attr); 337 + WARN_ON(attr->nla_len > estimate); 338 + 379 339 return 0; 380 340 nla_put_failure: 381 341 nla_nest_cancel(skb, attr); 382 342 return -ENOBUFS; 343 + } 344 + 345 + /** 346 + * netlink_policy_dump_write_attr - write a given attribute policy 347 + * @skb: the message skb to write to 348 + * @pt: the attribute's policy 349 + * @nestattr: the nested attribute ID to use 350 + * 351 + * Returns: 0 on success, an error code otherwise; -%ENODATA is 352 + * special, indicating that there's no policy data and 353 + * the attribute is generally rejected. 354 + */ 355 + int netlink_policy_dump_write_attr(struct sk_buff *skb, 356 + const struct nla_policy *pt, 357 + int nestattr) 358 + { 359 + return __netlink_policy_dump_write_attr(NULL, skb, pt, nestattr); 383 360 } 384 361 385 362 /**