CVE-2020-36691: Uncontrolled Recursion (CWE-674)
value: An issue was discovered in the Linux kernel before 5.8. lib/nlattr.c allows attackers to cause a denial of service (unbounded recursion) via a nested Netlink policy with a back reference.
CWE description: The product does not properly control the amount of recursion that takes place,  consuming excessive resources, such as allocated memory or the program stack.
CVSS v3 base score: 5.5
Published: 2023-03-24T17:15Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: lib/nlattr.c
Commit date: 2020-04-30 17:51:41 -0700
Commit message: netlink: limit recursion depth in policy validation

Now that we have nested policies, we can theoretically
recurse forever parsing attributes if a (sub-)policy
refers back to a higher level one. This is a situation
that has happened in nl80211, and we've avoided it there
by not linking it.

Add some code to netlink parsing to limit recursion depth.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

Diff: diff --git a/lib/nlattr.c b/lib/nlattr.c
index 3df05db732ca..7f7ebd89caa4 100644
--- a/lib/nlattr.c
+++ b/lib/nlattr.c
@@ -42,10 +42,24 @@ static const u8 nla_attr_minlen[NLA_TYPE_MAX+1] = {
 	[NLA_S16]	= sizeof(s16),
 	[NLA_S32]	= sizeof(s32),
 	[NLA_S64]	= sizeof(s64),
 };
 
+/*
+ * Nested policies might refer back to the original
+ * policy in some cases, and userspace could try to
+ * abuse that and recurse by nesting in the right
+ * ways. Limit recursion to avoid this problem.
+ */
+#define MAX_POLICY_RECURSION_DEPTH	10
+
+static int __nla_validate_parse(const struct nlattr *head, int len, int maxtype,
+				const struct nla_policy *policy,
+				unsigned int validate,
+				struct netlink_ext_ack *extack,
+				struct nlattr **tb, unsigned int depth);
+
 static int validate_nla_bitfield32(const struct nlattr *nla,
 				   const u32 valid_flags_mask)
 {
 	const struct nla_bitfield32 *bf = nla_data(nla);
 
@@ -68,11 +82,11 @@ static int validate_nla_bitfield32(const struct nlattr *nla,
 }
 
 static int nla_validate_array(const struct nlattr *head, int len, int maxtype,
 			      const struct nla_policy *policy,
 			      struct netlink_ext_ack *extack,
-			      unsigned int validate)
+			      unsigned int validate, unsigned int depth)
 {
 	const struct nlattr *entry;
 	int rem;
 
 	nla_for_each_attr(entry, head, len, rem) {
@@ -85,12 +99,13 @@ static int nla_validate_array(const struct nlattr *head, int len, int maxtype,
 			NL_SET_ERR_MSG_ATTR(extack, entry,
 					    "Array element too short");
 			return -ERANGE;
 		}
 
-		ret = __nla_validate(nla_data(entry), nla_len(entry),
-				     maxtype, policy, validate, extack);
+		ret = __nla_validate_parse(nla_data(entry), nla_len(entry),
+					   maxtype, policy, validate, extack,
+					   NULL, depth + 1);
 		if (ret < 0)
 			return ret;
 	}
 
 	return 0;
@@ -154,11 +169,11 @@ static int nla_validate_int_range(const struct nla_policy *pt,
 	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)
+			struct netlink_ext_ack *extack, unsigned int depth)
 {
 	u16 strict_start_type = policy[0].strict_start_type;
 	const struct nla_policy *pt;
 	int minlen = 0, attrlen = nla_len(nla), type = nla_type(nla);
 	int err = -ERANGE;
@@ -267,13 +282,14 @@ static int validate_nla(const struct nlattr *nla, int maxtype,
 		if (attrlen == 0)
 			break;
 		if (attrlen < NLA_HDRLEN)
 			goto out_err;
 		if (pt->nested_policy) {
-			err = __nla_validate(nla_data(nla), nla_len(nla), pt->len,
-					     pt->nested_policy, validate,
-					     extack);
+			err = __nla_validate_parse(nla_data(nla), nla_len(nla),
+						   pt->len, pt->nested_policy,
+						   validate, extack, NULL,
+						   depth + 1);
 			if (err < 0) {
 				/*
 				 * return directly to preserve the inner
 				 * error message/attribute pointer
 				 */
@@ -292,11 +308,11 @@ static int validate_nla(const struct nlattr *nla, int maxtype,
 		if (pt->nested_policy) {
 			int err;
 
 			err = nla_validate_array(nla_data(nla), nla_len(nla),
 						 pt->len, pt->nested_policy,
-						 extack, validate);
+						 extack, validate, depth);
 			if (err < 0) {
 				/*
 				 * return directly to preserve the inner
 				 * error message/attribute pointer
 				 */
@@ -356,15 +372,21 @@ static int validate_nla(const struct nlattr *nla, int maxtype,
 
 static int __nla_validate_parse(const struct nlattr *head, int len, int maxtype,
 				const struct nla_policy *policy,
 				unsigned int validate,
 				struct netlink_ext_ack *extack,
-				struct nlattr **tb)
+				struct nlattr **tb, unsigned int depth)
 {
 	const struct nlattr *nla;
 	int rem;
 
+	if (depth >= MAX_POLICY_RECURSION_DEPTH) {
+		NL_SET_ERR_MSG(extack,
+			       "allowed policy recursion depth exceeded");
+		return -EINVAL;
+	}
+
 	if (tb)
 		memset(tb, 0, sizeof(struct nlattr *) * (maxtype + 1));
 
 	nla_for_each_attr(nla, head, len, rem) {
 		u16 type = nla_type(nla);
@@ -377,11 +399,11 @@ static int __nla_validate_parse(const struct nlattr *head, int len, int maxtype,
 			}
 			continue;
 		}
 		if (policy) {
 			int err = validate_nla(nla, maxtype, policy,
-					       validate, extack);
+					       validate, extack, depth);
 
 			if (err < 0)
 				return err;
 		}
 
@@ -419,11 +441,11 @@ static int __nla_validate_parse(const struct nlattr *head, int len, int maxtype,
 int __nla_validate(const struct nlattr *head, int len, int maxtype,
 		   const struct nla_policy *policy, unsigned int validate,
 		   struct netlink_ext_ack *extack)
 {
 	return __nla_validate_parse(head, len, maxtype, policy, validate,
-				    extack, NULL);
+				    extack, NULL, 0);
 }
 EXPORT_SYMBOL(__nla_validate);
 
 /**
  * nla_policy_len - Determin the max. length of a policy
@@ -474,11 +496,11 @@ int __nla_parse(struct nlattr **tb, int maxtype,
 		const struct nlattr *head, int len,
 		const struct nla_policy *policy, unsigned int validate,
 		struct netlink_ext_ack *extack)
 {
 	return __nla_validate_parse(head, len, maxtype, policy, validate,
-				    extack, tb);
+				    extack, tb, 0);
 }
 EXPORT_SYMBOL(__nla_parse);
 
 /**
  * nla_find - Find a specific attribute in a stream of attributes

Vulnerable code: 			      unsigned int validate)
		ret = __nla_validate(nla_data(entry), nla_len(entry),
				     maxtype, policy, validate, extack);
			struct netlink_ext_ack *extack)
			err = __nla_validate(nla_data(nla), nla_len(nla), pt->len,
					     pt->nested_policy, validate,
					     extack);
						 extack, validate);
				struct nlattr **tb)
					       validate, extack);
				    extack, NULL);
				    extack, tb);
Fixed code: /*
 * Nested policies might refer back to the original
 * policy in some cases, and userspace could try to
 * abuse that and recurse by nesting in the right
 * ways. Limit recursion to avoid this problem.
 */
#define MAX_POLICY_RECURSION_DEPTH	10

static int __nla_validate_parse(const struct nlattr *head, int len, int maxtype,
				const struct nla_policy *policy,
				unsigned int validate,
				struct netlink_ext_ack *extack,
				struct nlattr **tb, unsigned int depth);

			      unsigned int validate, unsigned int depth)
		ret = __nla_validate_parse(nla_data(entry), nla_len(entry),
					   maxtype, policy, validate, extack,
					   NULL, depth + 1);
			struct netlink_ext_ack *extack, unsigned int depth)
			err = __nla_validate_parse(nla_data(nla), nla_len(nla),
						   pt->len, pt->nested_policy,
						   validate, extack, NULL,
						   depth + 1);
						 extack, validate, depth);
				struct nlattr **tb, unsigned int depth)
	if (depth >= MAX_POLICY_RECURSION_DEPTH) {
		NL_SET_ERR_MSG(extack,
			       "allowed policy recursion depth exceeded");
		return -EINVAL;
	}

					       validate, extack, depth);
				    extack, NULL, 0);
				    extack, tb, 0);