[PATCH net v3 1/1] ipv6: flowlabel: cap duplicate leases per socket

Zhiling Zou [email protected]
Newsgroups org.kernel.vger.netdev
Message-ID <69d00f6ba09b1d1afd9281370cf7ae5e2ce806b6.1787387550.git.zhilinz@nebusec.ai>
From: Zhiling Zou <[email protected]>

ipv6_flowlabel_get() allocates an ipv6_fl_socklist entry for every
successful GET. The recheck path for a compatible existing flowlabel
links another lease without applying any lease admission check. Repeated
GET requests for one shareable label can therefore grow a socket's lease
list without bound.

Count the socket's existing leases during the lookup and reject a new
unprivileged lease once the count reaches FL_MAX_PER_SOCK. This matches
mem_check()'s per-socket accounting and also covers leases acquired from
distinct globally interned labels. The IPv6 sockopt lock serializes the
count with fl_link() and PUT, so no global ip6_fl_lock is needed.

Check CAP_NET_ADMIN only when the socket reaches the limit. This avoids a
capability audit on successful unprivileged GET requests below the cap,
while privileged callers remain unrestricted.

Do the admission check before updating linger and expires. A rejected GET
does not refresh the shared label, matching the existing socket-list
allocation failure path.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: [email protected]
Reported-by: Vega <[email protected]>
Signed-off-by: Zhiling Zou <[email protected]>
---
changes in v3:
- Count the socket's full lease list instead of only matching leases.
- Defer the CAP_NET_ADMIN check until the lease count reaches the limit.
- v2 Link: https://lore.kernel.org/all/528bc30d301bcf32aca37cda1933b617d2d295f4.1786447968.git.zhilinz@nebusec.ai/

changes in v2:
- Count only leases of the flowlabel being reused.
- Fold the count into the existing socket-list lookup.
- Keep new-label admission under the existing mem_check() policy.
- Stop after the first matching lease for CAP_NET_ADMIN callers.
- Explain why a rejected GET does not refresh linger or expires.
- Correct the reuse-path explanation and trim the Fixes hash.
- v1 Link: https://lore.kernel.org/all/cf4fdc79ae4dc46bd4eb7eeb57e5de2091c13cd3.1785746178.git.zhilinz@nebusec.ai/

 net/ipv6/ip6_flowlabel.c | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/net/ipv6/ip6_flowlabel.c b/net/ipv6/ip6_flowlabel.c
index 1ab5ad0dcf24f..b864dfc87f779 100644
--- a/net/ipv6/ip6_flowlabel.c
+++ b/net/ipv6/ip6_flowlabel.c
@@ -617,6 +617,7 @@ static int ipv6_flowlabel_get(struct sock *sk, struct in6_flowlabel_req *freq,
 	struct ipv6_fl_socklist *sfl, *sfl1 = NULL;
 	struct ip6_flowlabel *fl, *fl1 = NULL;
 	struct net *net = sock_net(sk);
+	int lease_count = 0;
 	int err;
 
 	if (freq->flr_flags & IPV6_FL_F_REFLECT) {
@@ -647,16 +648,20 @@ static int ipv6_flowlabel_get(struct sock *sk, struct in6_flowlabel_req *freq,
 		err = -EEXIST;
 		rcu_read_lock();
 		for_each_sk_fl_rcu(sk, sfl) {
+			lease_count++;
 			if (sfl->fl->label == freq->flr_label) {
 				if (freq->flr_flags & IPV6_FL_F_EXCL) {
 					rcu_read_unlock();
 					goto done;
 				}
-				fl1 = sfl->fl;
-				if (!atomic_inc_not_zero(&fl1->users))
-					fl1 = NULL;
-				break;
+				if (!fl1) {
+					fl1 = sfl->fl;
+					if (!atomic_inc_not_zero(&fl1->users))
+						fl1 = NULL;
+				}
 			}
+			if (fl1 && lease_count >= FL_MAX_PER_SOCK)
+				break;
 		}
 		rcu_read_unlock();
 
@@ -679,6 +684,11 @@ static int ipv6_flowlabel_get(struct sock *sk, struct in6_flowlabel_req *freq,
 			err = -ENOMEM;
 			if (!sfl1)
 				goto release;
+			/* sockopt_lock_sock() serializes the count and fl_link(). */
+			err = -ENOBUFS;
+			if (lease_count >= FL_MAX_PER_SOCK &&
+			    !capable(CAP_NET_ADMIN))
+				goto release;
 			if (fl->linger > fl1->linger)
 				fl1->linger = fl->linger;
 			if ((long)(fl->expires - fl1->expires) > 0)
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.