VYPR
Unrated severityNVD Advisory· Published May 27, 2026· Updated May 27, 2026

CVE-2026-45897

CVE-2026-45897

Description

In the Linux kernel, the following vulnerability has been resolved:

netfilter: nft_counter: serialize reset with spinlock

Add a global static spinlock to serialize counter fetch+reset operations, preventing concurrent dump-and-reset from underrunning values.

The lock is taken before fetching the total so that two parallel resets cannot both read the same counter values and then both subtract them.

A global lock is used for simplicity since resets are infrequent. If this becomes a bottleneck, it can be replaced with a per-net lock later.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A race condition in Linux kernel netfilter nft_counter allows concurrent dump-and-reset operations to undercount packet/byte counters; fixed via a global spinlock.

Vulnerability

A race condition exists in the Linux kernel's netfilter subsystem, specifically in the nft_counter module, which is responsible for managing packet and byte counters used by nftables rules. During concurrent dump-and-reset operations (e.g., two parallel requests to retrieve and zero the counters), the counter values can be read simultaneously by both requests before either subtracts the fetched values. This leads to underrunning the counter values, resulting in inaccurate accounting. The affected kernel versions include those prior to the inclusion of commit 0cdc6d5a26f2d1f7f15a43526841b679445c32e2 [1]. The issue is reachable when a user with sufficient privileges (e.g., CAP_NET_ADMIN) triggers multiple simultaneous counter resets via nftables commands or related interfaces.

Exploitation

An attacker with local access and the CAP_NET_ADMIN capability (or root privileges) can exploit this race by initiating multiple concurrent dump-and-reset operations on nft_counter objects. The attacker does not need network access or user interaction beyond the initial trigger. The race window is small and requires precise timing, but the operation is infrequent under normal usage [1]. Exploitation can be achieved by spawning multiple processes or threads that simultaneously request counter reset via nftables netlink commands.

Impact

Successful exploitation leads to incorrect (undercounted) packet and byte statistics in nftables counters. While this does not directly allow code execution or privilege escalation, it undermines the integrity of accounting data used for monitoring, billing, or traffic analysis. If counters are used for dynamic rule decisions (e.g., rate limiting), the undercount could inadvertently bypass intended limits. The security impact is primarily on data integrity and potential availability of accurate accounting [1].

Mitigation

The fix is to apply the Linux kernel patch 0cdc6d5a26f2d1f7f15a43526841b679445c32e2, which was released on or shortly before 2026-05-27 [1]. This patch introduces a global static spinlock to serialize counter fetch+reset operations, preventing the race condition. System administrators should update to a kernel version containing this commit as soon as possible. No workaround exists besides avoiding concurrent resets, which is not a reliable mitigation. The vulnerability is not listed on CISA's Known Exploited Vulnerabilities (KEV) catalog as of its publication date.

AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

4
779c60a5190c

netfilter: nft_counter: serialize reset with spinlock

2 files changed · +32 10
  • net/netfilter/nft_counter.c+16 5 modified
    diff --git a/net/netfilter/nft_counter.c b/net/netfilter/nft_counter.c
    index 0d70325280cc57..169ae93688bcc5 100644
    --- a/net/netfilter/nft_counter.c
    +++ b/net/netfilter/nft_counter.c
    @@ -32,6 +32,9 @@ struct nft_counter_percpu_priv {
     
     static DEFINE_PER_CPU(struct u64_stats_sync, nft_counter_sync);
     
    +/* control plane only: sync fetch+reset */
    +static DEFINE_SPINLOCK(nft_counter_lock);
    +
     static inline void nft_counter_do_eval(struct nft_counter_percpu_priv *priv,
     				       struct nft_regs *regs,
     				       const struct nft_pktinfo *pkt)
    @@ -148,13 +151,25 @@ static void nft_counter_fetch(struct nft_counter_percpu_priv *priv,
     	}
     }
     
    +static void nft_counter_fetch_and_reset(struct nft_counter_percpu_priv *priv,
    +					struct nft_counter_tot *total)
    +{
    +	spin_lock(&nft_counter_lock);
    +	nft_counter_fetch(priv, total);
    +	nft_counter_reset(priv, total);
    +	spin_unlock(&nft_counter_lock);
    +}
    +
     static int nft_counter_do_dump(struct sk_buff *skb,
     			       struct nft_counter_percpu_priv *priv,
     			       bool reset)
     {
     	struct nft_counter_tot total;
     
    -	nft_counter_fetch(priv, &total);
    +	if (unlikely(reset))
    +		nft_counter_fetch_and_reset(priv, &total);
    +	else
    +		nft_counter_fetch(priv, &total);
     
     	if (nla_put_be64(skb, NFTA_COUNTER_BYTES, cpu_to_be64(total.bytes),
     			 NFTA_COUNTER_PAD) ||
    @@ -162,9 +177,6 @@ static int nft_counter_do_dump(struct sk_buff *skb,
     			 NFTA_COUNTER_PAD))
     		goto nla_put_failure;
     
    -	if (reset)
    -		nft_counter_reset(priv, &total);
    -
     	return 0;
     
     nla_put_failure:
    -- 
    cgit 1.3-korg
    
    
    
  • net/netfilter/nft_counter.c+16 5 modified
    diff --git a/net/netfilter/nft_counter.c b/net/netfilter/nft_counter.c
    index 0d70325280cc57..169ae93688bcc5 100644
    --- a/net/netfilter/nft_counter.c
    +++ b/net/netfilter/nft_counter.c
    @@ -32,6 +32,9 @@ struct nft_counter_percpu_priv {
     
     static DEFINE_PER_CPU(struct u64_stats_sync, nft_counter_sync);
     
    +/* control plane only: sync fetch+reset */
    +static DEFINE_SPINLOCK(nft_counter_lock);
    +
     static inline void nft_counter_do_eval(struct nft_counter_percpu_priv *priv,
     				       struct nft_regs *regs,
     				       const struct nft_pktinfo *pkt)
    @@ -148,13 +151,25 @@ static void nft_counter_fetch(struct nft_counter_percpu_priv *priv,
     	}
     }
     
    +static void nft_counter_fetch_and_reset(struct nft_counter_percpu_priv *priv,
    +					struct nft_counter_tot *total)
    +{
    +	spin_lock(&nft_counter_lock);
    +	nft_counter_fetch(priv, total);
    +	nft_counter_reset(priv, total);
    +	spin_unlock(&nft_counter_lock);
    +}
    +
     static int nft_counter_do_dump(struct sk_buff *skb,
     			       struct nft_counter_percpu_priv *priv,
     			       bool reset)
     {
     	struct nft_counter_tot total;
     
    -	nft_counter_fetch(priv, &total);
    +	if (unlikely(reset))
    +		nft_counter_fetch_and_reset(priv, &total);
    +	else
    +		nft_counter_fetch(priv, &total);
     
     	if (nla_put_be64(skb, NFTA_COUNTER_BYTES, cpu_to_be64(total.bytes),
     			 NFTA_COUNTER_PAD) ||
    @@ -162,9 +177,6 @@ static int nft_counter_do_dump(struct sk_buff *skb,
     			 NFTA_COUNTER_PAD))
     		goto nla_put_failure;
     
    -	if (reset)
    -		nft_counter_reset(priv, &total);
    -
     	return 0;
     
     nla_put_failure:
    -- 
    cgit 1.3-korg
    
    
    
0cdc6d5a26f2

netfilter: nft_counter: serialize reset with spinlock

2 files changed · +32 10
  • net/netfilter/nft_counter.c+16 5 modified
    diff --git a/net/netfilter/nft_counter.c b/net/netfilter/nft_counter.c
    index 0d70325280cc57..169ae93688bcc5 100644
    --- a/net/netfilter/nft_counter.c
    +++ b/net/netfilter/nft_counter.c
    @@ -32,6 +32,9 @@ struct nft_counter_percpu_priv {
     
     static DEFINE_PER_CPU(struct u64_stats_sync, nft_counter_sync);
     
    +/* control plane only: sync fetch+reset */
    +static DEFINE_SPINLOCK(nft_counter_lock);
    +
     static inline void nft_counter_do_eval(struct nft_counter_percpu_priv *priv,
     				       struct nft_regs *regs,
     				       const struct nft_pktinfo *pkt)
    @@ -148,13 +151,25 @@ static void nft_counter_fetch(struct nft_counter_percpu_priv *priv,
     	}
     }
     
    +static void nft_counter_fetch_and_reset(struct nft_counter_percpu_priv *priv,
    +					struct nft_counter_tot *total)
    +{
    +	spin_lock(&nft_counter_lock);
    +	nft_counter_fetch(priv, total);
    +	nft_counter_reset(priv, total);
    +	spin_unlock(&nft_counter_lock);
    +}
    +
     static int nft_counter_do_dump(struct sk_buff *skb,
     			       struct nft_counter_percpu_priv *priv,
     			       bool reset)
     {
     	struct nft_counter_tot total;
     
    -	nft_counter_fetch(priv, &total);
    +	if (unlikely(reset))
    +		nft_counter_fetch_and_reset(priv, &total);
    +	else
    +		nft_counter_fetch(priv, &total);
     
     	if (nla_put_be64(skb, NFTA_COUNTER_BYTES, cpu_to_be64(total.bytes),
     			 NFTA_COUNTER_PAD) ||
    @@ -162,9 +177,6 @@ static int nft_counter_do_dump(struct sk_buff *skb,
     			 NFTA_COUNTER_PAD))
     		goto nla_put_failure;
     
    -	if (reset)
    -		nft_counter_reset(priv, &total);
    -
     	return 0;
     
     nla_put_failure:
    -- 
    cgit 1.3-korg
    
    
    
  • net/netfilter/nft_counter.c+16 5 modified
    diff --git a/net/netfilter/nft_counter.c b/net/netfilter/nft_counter.c
    index 0d70325280cc57..169ae93688bcc5 100644
    --- a/net/netfilter/nft_counter.c
    +++ b/net/netfilter/nft_counter.c
    @@ -32,6 +32,9 @@ struct nft_counter_percpu_priv {
     
     static DEFINE_PER_CPU(struct u64_stats_sync, nft_counter_sync);
     
    +/* control plane only: sync fetch+reset */
    +static DEFINE_SPINLOCK(nft_counter_lock);
    +
     static inline void nft_counter_do_eval(struct nft_counter_percpu_priv *priv,
     				       struct nft_regs *regs,
     				       const struct nft_pktinfo *pkt)
    @@ -148,13 +151,25 @@ static void nft_counter_fetch(struct nft_counter_percpu_priv *priv,
     	}
     }
     
    +static void nft_counter_fetch_and_reset(struct nft_counter_percpu_priv *priv,
    +					struct nft_counter_tot *total)
    +{
    +	spin_lock(&nft_counter_lock);
    +	nft_counter_fetch(priv, total);
    +	nft_counter_reset(priv, total);
    +	spin_unlock(&nft_counter_lock);
    +}
    +
     static int nft_counter_do_dump(struct sk_buff *skb,
     			       struct nft_counter_percpu_priv *priv,
     			       bool reset)
     {
     	struct nft_counter_tot total;
     
    -	nft_counter_fetch(priv, &total);
    +	if (unlikely(reset))
    +		nft_counter_fetch_and_reset(priv, &total);
    +	else
    +		nft_counter_fetch(priv, &total);
     
     	if (nla_put_be64(skb, NFTA_COUNTER_BYTES, cpu_to_be64(total.bytes),
     			 NFTA_COUNTER_PAD) ||
    @@ -162,9 +177,6 @@ static int nft_counter_do_dump(struct sk_buff *skb,
     			 NFTA_COUNTER_PAD))
     		goto nla_put_failure;
     
    -	if (reset)
    -		nft_counter_reset(priv, &total);
    -
     	return 0;
     
     nla_put_failure:
    -- 
    cgit 1.3-korg
    
    
    
0cdc6d5a26f2

netfilter: nft_counter: serialize reset with spinlock

2 files changed · +32 10
  • net/netfilter/nft_counter.c+16 5 modified
    diff --git a/net/netfilter/nft_counter.c b/net/netfilter/nft_counter.c
    index 0d70325280cc57..169ae93688bcc5 100644
    --- a/net/netfilter/nft_counter.c
    +++ b/net/netfilter/nft_counter.c
    @@ -32,6 +32,9 @@ struct nft_counter_percpu_priv {
     
     static DEFINE_PER_CPU(struct u64_stats_sync, nft_counter_sync);
     
    +/* control plane only: sync fetch+reset */
    +static DEFINE_SPINLOCK(nft_counter_lock);
    +
     static inline void nft_counter_do_eval(struct nft_counter_percpu_priv *priv,
     				       struct nft_regs *regs,
     				       const struct nft_pktinfo *pkt)
    @@ -148,13 +151,25 @@ static void nft_counter_fetch(struct nft_counter_percpu_priv *priv,
     	}
     }
     
    +static void nft_counter_fetch_and_reset(struct nft_counter_percpu_priv *priv,
    +					struct nft_counter_tot *total)
    +{
    +	spin_lock(&nft_counter_lock);
    +	nft_counter_fetch(priv, total);
    +	nft_counter_reset(priv, total);
    +	spin_unlock(&nft_counter_lock);
    +}
    +
     static int nft_counter_do_dump(struct sk_buff *skb,
     			       struct nft_counter_percpu_priv *priv,
     			       bool reset)
     {
     	struct nft_counter_tot total;
     
    -	nft_counter_fetch(priv, &total);
    +	if (unlikely(reset))
    +		nft_counter_fetch_and_reset(priv, &total);
    +	else
    +		nft_counter_fetch(priv, &total);
     
     	if (nla_put_be64(skb, NFTA_COUNTER_BYTES, cpu_to_be64(total.bytes),
     			 NFTA_COUNTER_PAD) ||
    @@ -162,9 +177,6 @@ static int nft_counter_do_dump(struct sk_buff *skb,
     			 NFTA_COUNTER_PAD))
     		goto nla_put_failure;
     
    -	if (reset)
    -		nft_counter_reset(priv, &total);
    -
     	return 0;
     
     nla_put_failure:
    -- 
    cgit 1.3-korg
    
    
    
  • net/netfilter/nft_counter.c+16 5 modified
    diff --git a/net/netfilter/nft_counter.c b/net/netfilter/nft_counter.c
    index 0d70325280cc57..169ae93688bcc5 100644
    --- a/net/netfilter/nft_counter.c
    +++ b/net/netfilter/nft_counter.c
    @@ -32,6 +32,9 @@ struct nft_counter_percpu_priv {
     
     static DEFINE_PER_CPU(struct u64_stats_sync, nft_counter_sync);
     
    +/* control plane only: sync fetch+reset */
    +static DEFINE_SPINLOCK(nft_counter_lock);
    +
     static inline void nft_counter_do_eval(struct nft_counter_percpu_priv *priv,
     				       struct nft_regs *regs,
     				       const struct nft_pktinfo *pkt)
    @@ -148,13 +151,25 @@ static void nft_counter_fetch(struct nft_counter_percpu_priv *priv,
     	}
     }
     
    +static void nft_counter_fetch_and_reset(struct nft_counter_percpu_priv *priv,
    +					struct nft_counter_tot *total)
    +{
    +	spin_lock(&nft_counter_lock);
    +	nft_counter_fetch(priv, total);
    +	nft_counter_reset(priv, total);
    +	spin_unlock(&nft_counter_lock);
    +}
    +
     static int nft_counter_do_dump(struct sk_buff *skb,
     			       struct nft_counter_percpu_priv *priv,
     			       bool reset)
     {
     	struct nft_counter_tot total;
     
    -	nft_counter_fetch(priv, &total);
    +	if (unlikely(reset))
    +		nft_counter_fetch_and_reset(priv, &total);
    +	else
    +		nft_counter_fetch(priv, &total);
     
     	if (nla_put_be64(skb, NFTA_COUNTER_BYTES, cpu_to_be64(total.bytes),
     			 NFTA_COUNTER_PAD) ||
    @@ -162,9 +177,6 @@ static int nft_counter_do_dump(struct sk_buff *skb,
     			 NFTA_COUNTER_PAD))
     		goto nla_put_failure;
     
    -	if (reset)
    -		nft_counter_reset(priv, &total);
    -
     	return 0;
     
     nla_put_failure:
    -- 
    cgit 1.3-korg
    
    
    
779c60a5190c

netfilter: nft_counter: serialize reset with spinlock

2 files changed · +32 10
  • net/netfilter/nft_counter.c+16 5 modified
    diff --git a/net/netfilter/nft_counter.c b/net/netfilter/nft_counter.c
    index 0d70325280cc57..169ae93688bcc5 100644
    --- a/net/netfilter/nft_counter.c
    +++ b/net/netfilter/nft_counter.c
    @@ -32,6 +32,9 @@ struct nft_counter_percpu_priv {
     
     static DEFINE_PER_CPU(struct u64_stats_sync, nft_counter_sync);
     
    +/* control plane only: sync fetch+reset */
    +static DEFINE_SPINLOCK(nft_counter_lock);
    +
     static inline void nft_counter_do_eval(struct nft_counter_percpu_priv *priv,
     				       struct nft_regs *regs,
     				       const struct nft_pktinfo *pkt)
    @@ -148,13 +151,25 @@ static void nft_counter_fetch(struct nft_counter_percpu_priv *priv,
     	}
     }
     
    +static void nft_counter_fetch_and_reset(struct nft_counter_percpu_priv *priv,
    +					struct nft_counter_tot *total)
    +{
    +	spin_lock(&nft_counter_lock);
    +	nft_counter_fetch(priv, total);
    +	nft_counter_reset(priv, total);
    +	spin_unlock(&nft_counter_lock);
    +}
    +
     static int nft_counter_do_dump(struct sk_buff *skb,
     			       struct nft_counter_percpu_priv *priv,
     			       bool reset)
     {
     	struct nft_counter_tot total;
     
    -	nft_counter_fetch(priv, &total);
    +	if (unlikely(reset))
    +		nft_counter_fetch_and_reset(priv, &total);
    +	else
    +		nft_counter_fetch(priv, &total);
     
     	if (nla_put_be64(skb, NFTA_COUNTER_BYTES, cpu_to_be64(total.bytes),
     			 NFTA_COUNTER_PAD) ||
    @@ -162,9 +177,6 @@ static int nft_counter_do_dump(struct sk_buff *skb,
     			 NFTA_COUNTER_PAD))
     		goto nla_put_failure;
     
    -	if (reset)
    -		nft_counter_reset(priv, &total);
    -
     	return 0;
     
     nla_put_failure:
    -- 
    cgit 1.3-korg
    
    
    
  • net/netfilter/nft_counter.c+16 5 modified
    diff --git a/net/netfilter/nft_counter.c b/net/netfilter/nft_counter.c
    index 0d70325280cc57..169ae93688bcc5 100644
    --- a/net/netfilter/nft_counter.c
    +++ b/net/netfilter/nft_counter.c
    @@ -32,6 +32,9 @@ struct nft_counter_percpu_priv {
     
     static DEFINE_PER_CPU(struct u64_stats_sync, nft_counter_sync);
     
    +/* control plane only: sync fetch+reset */
    +static DEFINE_SPINLOCK(nft_counter_lock);
    +
     static inline void nft_counter_do_eval(struct nft_counter_percpu_priv *priv,
     				       struct nft_regs *regs,
     				       const struct nft_pktinfo *pkt)
    @@ -148,13 +151,25 @@ static void nft_counter_fetch(struct nft_counter_percpu_priv *priv,
     	}
     }
     
    +static void nft_counter_fetch_and_reset(struct nft_counter_percpu_priv *priv,
    +					struct nft_counter_tot *total)
    +{
    +	spin_lock(&nft_counter_lock);
    +	nft_counter_fetch(priv, total);
    +	nft_counter_reset(priv, total);
    +	spin_unlock(&nft_counter_lock);
    +}
    +
     static int nft_counter_do_dump(struct sk_buff *skb,
     			       struct nft_counter_percpu_priv *priv,
     			       bool reset)
     {
     	struct nft_counter_tot total;
     
    -	nft_counter_fetch(priv, &total);
    +	if (unlikely(reset))
    +		nft_counter_fetch_and_reset(priv, &total);
    +	else
    +		nft_counter_fetch(priv, &total);
     
     	if (nla_put_be64(skb, NFTA_COUNTER_BYTES, cpu_to_be64(total.bytes),
     			 NFTA_COUNTER_PAD) ||
    @@ -162,9 +177,6 @@ static int nft_counter_do_dump(struct sk_buff *skb,
     			 NFTA_COUNTER_PAD))
     		goto nla_put_failure;
     
    -	if (reset)
    -		nft_counter_reset(priv, &total);
    -
     	return 0;
     
     nla_put_failure:
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"Missing serialization of the counter fetch and reset operations allows concurrent dump-and-reset requests to read the same counter values and both subtract them, causing underflow."

Attack vector

An attacker with the ability to send concurrent NFT_MSG_GETOBJ_RESET, NFT_MSG_GETSETELEM_RESET, or NFT_MSG_GETRULE_RESET requests (i.e., netfilter dump-and-reset operations) can trigger a race condition. Two parallel reset requests can both read the same counter total before either subtracts it, then both subtract the full amount, causing the counter to underrun. This requires the attacker to have sufficient privileges to issue these nf_tables netlink commands and to time the requests so they execute concurrently.

Affected code

The vulnerability is in `net/netfilter/nft_counter.c` in the function `nft_counter_do_dump()`. The original code called `nft_counter_fetch()` and then, if `reset` was set, called `nft_counter_reset()` without any synchronization between the two calls [patch_id=2661571].

What the fix does

The patch introduces a global static spinlock (`nft_counter_lock`) and a new helper `nft_counter_fetch_and_reset()` that holds the lock across both the fetch and reset operations. In `nft_counter_do_dump()`, when the `reset` flag is set, the code now calls the locked helper instead of performing the fetch and reset as separate unlocked steps. This ensures that two concurrent resets cannot both read the same counter values before either subtracts them, preventing the underrun described in the commit message [patch_id=2661571].

Preconditions

  • authAttacker must have privileges to issue nf_tables netlink dump-and-reset commands (NFT_MSG_GETOBJ_RESET, NFT_MSG_GETSETELEM_RESET, or NFT_MSG_GETRULE_RESET).
  • networkAttacker must be able to send concurrent requests to trigger the race condition.

Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

2

News mentions

0

No linked articles in our index yet.