From: Paolo Abeni <pabeni@redhat.com>
To: zhilinz@nebusec.ai
Cc: netdev@vger.kernel.org, dsahern@kernel.org, idosch@nvidia.com,
davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com, horms@kernel.org, vega@nebusec.ai
Subject: Re: [PATCH net v2 1/1] ipv6: flowlabel: cap duplicate leases per socket
Date: Tue, 18 Aug 2026 10:47:41 +0200 [thread overview]
Message-ID: <20260818084741.44873-1-pabeni@redhat.com> (raw)
In-Reply-To: <528bc30d301bcf32aca37cda1933b617d2d295f4.1786447968.git.zhilinz@nebusec.ai>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/
---
ipv6: flowlabel: cap duplicate leases per socket
This adds a duplicate-lease counter to the socket-list walk in
ipv6_flowlabel_get() and rejects a new unprivileged lease with -ENOBUFS
once the count reaches FL_MAX_PER_SOCK, before linger and expires are
refreshed.
A few questions about the new counting and the capability query below.
> diff --git a/net/ipv6/ip6_flowlabel.c b/net/ipv6/ip6_flowlabel.c
> index 1ab5ad0dcf24..55cc5ec485c5 100644
> --- a/net/ipv6/ip6_flowlabel.c
> +++ b/net/ipv6/ip6_flowlabel.c
> @@ -617,6 +617,8 @@ 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);
> + bool cap_net_admin = false;
> + int dup_count = 0;
> int err;
>
> if (freq->flr_flags & IPV6_FL_F_REFLECT) {
> @@ -652,10 +654,14 @@ static int ipv6_flowlabel_get(struct sock *sk, struct in6_flowlabel_req *freq,
> 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;
> + cap_net_admin = capable(CAP_NET_ADMIN);
[Severity: Medium]
Is it intentional that capable(CAP_NET_ADMIN) now runs on the normal
success path of every reuse GET?
The only consumer is the later test:
err = -ENOBUFS;
if (dup_count >= FL_MAX_PER_SOCK && !cap_net_admin)
goto release;
which is only reachable once dup_count has saturated at FL_MAX_PER_SOCK,
so for a GET that succeeds with dup_count between 1 and 31 the result is
never read.
capable() is the auditing variant:
kernel/capability.c:capable() -> ns_capable() ->
ns_capable_common(ns, cap, CAP_OPT_NONE) -> security_capable()
and SELinux audits whenever CAP_OPT_NOAUDIT is not set:
security/selinux/hooks.c:cred_has_capability() {
...
if (!(opts & CAP_OPT_NOAUDIT)) {
int rc2 = avc_audit(sid, sid, sclass, av, &avd, rc, &ad);
...
}
So does an unprivileged process looping
setsockopt(IPV6_FLOWLABEL_MGR, IPV6_FL_A_GET) on a label it already holds
now emit one "denied { net_admin }" record per syscall, on a path that
previously performed no capability check at all? capable() also sets
PF_SUPERPRIV for privileged callers that never end up using the privilege.
Since the "if (!fl1)" block is re-entered whenever atomic_inc_not_zero()
failed, can this also run more than once per syscall?
Would evaluating the capability at the rejection site, or using a
*_noaudit variant for this speculative query, give the same policy without
success-path side effects? Note the "|| cap_net_admin" loop shortcut just
below depends on the eager evaluation, so the ordering would need reworking
rather than just moving the call.
> + }
> + if (++dup_count >= FL_MAX_PER_SOCK || cap_net_admin)
> + break;
[Severity: Medium]
This isn't a bug introduced by this patch, but a question about how much
of the pre-existing growth path the new counter covers.
dup_count is only incremented inside the "sfl->fl->label ==
freq->flr_label" branch, so the new limit is per (socket, label). The
pre-existing use of the same constant in mem_check() bounds the total
number of lease entries on the socket:
rcu_read_lock();
for_each_sk_fl_rcu(sk, sfl)
count++;
rcu_read_unlock();
if (room <= 0 ||
((count >= FL_MAX_PER_SOCK ||
...
Is FL_MAX_PER_SOCK now expressing two different policies, and would a
separate name for the per-label limit be clearer?
Also, the reuse path reached through fl_lookup():
if (!fl1)
fl1 = fl_lookup(net, freq->flr_label);
if (fl1) {
recheck:
arrives at recheck with dup_count == 0 and never consults mem_check(), so
the first lease of each distinct globally interned shareable label is still
admitted with no accounting. Can an unprivileged caller still grow one
socket's lease list to roughly FL_MAX_PER_SOCK times the number of
shareable labels reachable in the netns, each node pinning an
ip6_flowlabel and lengthening the O(n) socket-list walks?
The residual looks bounded in practice: mem_check() refuses new-label
creation for an unprivileged socket once fl_size >= FL_MAX_PER_SOCK, and
net->ipv6.flowlabel_count is capped at unpriv_user_limit (~3072) per netns,
which works out to roughly 3 MB of ipv6_fl_socklist nodes per socket
reached via ~10^5 setsockopt calls. Before the patch a single label could
be leased an unbounded number of times, so this is an improvement; would
accounting the whole lease list the way mem_check() does close the rest?
> }
> }
> rcu_read_unlock();
> @@ -679,6 +685,10 @@ 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 (dup_count >= FL_MAX_PER_SOCK && !cap_net_admin)
> + goto release;
[Severity: Low]
Could a selftest be added for this new -ENOBUFS return? The existing
in-tree contract test cannot reach it.
tools/testing/selftests/net/ipv6_flowlabel_mgr.c:run_tests() performs only
three duplicate GETs of label 1 followed by three PUTs:
explain("can now put exactly three references");
expect_pass(flowlabel_put(fd, 1));
which is well below FL_MAX_PER_SOCK, and the wrapper runs it as root:
tools/testing/selftests/net/ipv6_flowlabel.sh:
./in_netns.sh ./ipv6_flowlabel_mgr
so capable(CAP_NET_ADMIN) is true and the walk breaks after the first
match with dup_count == 1. As it stands, the test would still pass if the
cap were removed, off by one, or rejected the first duplicate. Exercising
it would need more than 32 duplicate GETs after dropping CAP_NET_ADMIN,
similar to what the existing IPV6_FL_S_USER case already does in a setuid
child.
> if (fl->linger > fl1->linger)
> fl1->linger = fl->linger;
> if ((long)(fl->expires - fl1->expires) > 0)
--
This is an AI-generated review.
prev parent reply other threads:[~2026-08-18 8:47 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-11 12:40 [PATCH net v2 0/1] ipv6: flowlabel: cap duplicate leases per socket Zhiling Zou
2026-08-11 12:40 ` [PATCH net v2 1/1] " Zhiling Zou
2026-08-18 8:47 ` Paolo Abeni [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260818084741.44873-1-pabeni@redhat.com \
--to=pabeni@redhat.com \
--cc=davem@davemloft.net \
--cc=dsahern@kernel.org \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=idosch@nvidia.com \
--cc=kuba@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=vega@nebusec.ai \
--cc=zhilinz@nebusec.ai \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox