[PATCH AUTOSEL 6.19-5.15] ipv6: exthdrs: annotate data-race over multiple sysctl

Sasha Levin
2026-02-14T21:25:53Z

From: Eric Dumazet <edumazet@google.com>

[ Upstream commit 978b67d28358b0b4eacfa94453d1ad4e09b123ad ]

Following four sysctls can change under us, add missing READ_ONCE().

- ipv6.sysctl.max_dst_opts_len
- ipv6.sysctl.max_dst_opts_cnt
- ipv6.sysctl.max_hbh_opts_len
- ipv6.sysctl.max_hbh_opts_cnt

Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Link: https://patch.msgid.link/20260115094141.3124990-8-edumazet@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

## Analysis of commit: "ipv6: exthdrs: annotate data-race over multiple
sysctl"

### 1. COMMIT MESSAGE ANALYSIS

The commit message is straightforward: it adds `READ_ONCE()` annotations
to four sysctl variables that can be modified concurrently from
userspace while being read in the packet processing path. The author is
Eric Dumazet, a prolific and highly respected networking maintainer at
Google, who frequently contributes data-race annotations and fixes.
Reviewed by Simon Horman, another well-known networking reviewer.

The four sysctls affected:
- `ipv6.sysctl.max_dst_opts_len`
- `ipv6.sysctl.max_dst_opts_cnt`
- `ipv6.sysctl.max_hbh_opts_len`
- `ipv6.sysctl.max_hbh_opts_cnt`

### 2. CODE CHANGE ANALYSIS

The changes are minimal and mechanical - wrapping four sysctl reads with
`READ_ONCE()`:

1. **`ipv6_destopt_rcv()`** (line ~317):
`net->ipv6.sysctl.max_dst_opts_len` →
`READ_ONCE(net->ipv6.sysctl.max_dst_opts_len)`
2. **`ipv6_destopt_rcv()`** (line ~326):
`net->ipv6.sysctl.max_dst_opts_cnt` →
`READ_ONCE(net->ipv6.sysctl.max_dst_opts_cnt)`
3. **`ipv6_parse_hopopts()`** (line ~1053):
`net->ipv6.sysctl.max_hbh_opts_len` →
`READ_ONCE(net->ipv6.sysctl.max_hbh_opts_len)`
4. **`ipv6_parse_hopopts()`** (line ~1056):
`net->ipv6.sysctl.max_hbh_opts_cnt` →
`READ_ONCE(net->ipv6.sysctl.max_hbh_opts_cnt)`

These are in the IPv6 extension header packet receive path - hot path
code that processes every incoming IPv6 packet with destination options
or hop-by-hop options. The sysctl values can be changed from userspace
at any time via `/proc/sys/net/ipv6/`, creating a data race.

### 3. BUG MECHANISM

Without `READ_ONCE()`, the compiler is free to:
- Re-read the value multiple times (store tearing), potentially getting
different values in the same function
- Optimize based on assumptions about the value not changing

This is a real data race detectable by KCSAN (Kernel Concurrency
Sanitizer). While the practical consequences of this particular race are
relatively mild (the comparison values might be slightly stale or torn),
the race is real and in a networking hot path.

### 4. CLASSIFICATION

This is a **data-race fix** — category 3 (Race Conditions) from the
analysis framework. `READ_ONCE()`/`WRITE_ONCE()` annotations are a
common pattern for KCSAN-detected data races and are regularly
backported to stable.

### 5. SCOPE AND RISK ASSESSMENT

- **Lines changed**: ~8 lines across one file
- **Files touched**: 1 (`net/ipv6/exthdrs.c`)
- **Risk**: Extremely low. `READ_ONCE()` is a pure compiler annotation
that generates identical or near-identical machine code on most
architectures. It cannot introduce regressions.
- **Subsystem**: IPv6 networking — core infrastructure used by virtually
all systems

### 6. USER IMPACT

- **Who is affected**: Any system processing IPv6 packets with extension
headers where sysctl values might be modified concurrently
- **Severity**: Low to medium — the race could theoretically cause
inconsistent enforcement of the max length/count limits, but more
importantly it silences KCSAN reports and ensures correct compiler
behavior
- **In the networking hot path**: These functions process packets, so
correctness matters

### 7. STABILITY INDICATORS

- **Author**: Eric Dumazet (Google, top networking contributor) — very
high trust
- **Reviewer**: Simon Horman — respected networking reviewer
- **Pattern**: This is part of a series (patch 8 of a set) of data-race
annotations, which is a well-established pattern in the networking
subsystem

### 8. DEPENDENCY CHECK

This commit is self-contained. `READ_ONCE()` is a basic kernel primitive
available in all stable trees. The sysctl variables being annotated have
existed for a long time. No dependencies on other patches.

### 9. VERDICT

This is a small, surgical, zero-risk fix for a real data race in the
IPv6 networking path. It follows the well-established pattern of
`READ_ONCE()` annotations that Eric Dumazet has been systematically
adding across the networking stack. These annotations are routinely
backported to stable trees. The fix is obviously correct, has
essentially zero regression risk, and addresses a real concurrency issue
in core networking code.

**YES**

net/ipv6/exthdrs.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
index a23eb8734e151..54088fa0c09d0 100644
--- a/net/ipv6/exthdrs.c
+++ b/net/ipv6/exthdrs.c
@@ -314,7 +314,7 @@ static int ipv6_destopt_rcv(struct sk_buff *skb)
}

extlen = (skb_transport_header(skb)[1] + 1) << 3;
- if (extlen > net->ipv6.sysctl.max_dst_opts_len)
+ if (extlen > READ_ONCE(net->ipv6.sysctl.max_dst_opts_len))
goto fail_and_free;

opt->lastopt = opt->dst1 = skb_network_header_len(skb);
@@ -322,7 +322,8 @@ static int ipv6_destopt_rcv(struct sk_buff *skb)
dstbuf = opt->dst1;
#endif

- if (ip6_parse_tlv(false, skb, net->ipv6.sysctl.max_dst_opts_cnt)) {
+ if (ip6_parse_tlv(false, skb,
+ READ_ONCE(net->ipv6.sysctl.max_dst_opts_cnt))) {
skb->transport_header += extlen;
opt = IP6CB(skb);
#if IS_ENABLED(CONFIG_IPV6_MIP6)
@@ -1049,11 +1050,12 @@ int ipv6_parse_hopopts(struct sk_buff *skb)
}

extlen = (skb_transport_header(skb)[1] + 1) << 3;
- if (extlen > net->ipv6.sysctl.max_hbh_opts_len)
+ if (extlen > READ_ONCE(net->ipv6.sysctl.max_hbh_opts_len))
goto fail_and_free;

opt->flags |= IP6SKB_HOPBYHOP;
- if (ip6_parse_tlv(true, skb, net->ipv6.sysctl.max_hbh_opts_cnt)) {
+ if (ip6_parse_tlv(true, skb,
+ READ_ONCE(net->ipv6.sysctl.max_hbh_opts_cnt))) {
skb->transport_header += extlen;
opt = IP6CB(skb);
opt->nhoff = sizeof(struct ipv6hdr);
--
2.51.0