* [PATCH net] macsec: reject replays after non-XPN receive PN exhaustion
@ 2026-10-08 8:25 Sung Byeongchan
2026-10-08 8:30 ` netdev-bot+sinfo
2026-10-08 16:35 ` Sabrina Dubroca
0 siblings, 2 replies; 4+ messages in thread
From: Sung Byeongchan @ 2026-10-08 8:25 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev
When a non-XPN RXSA accepts PN U32_MAX, advancing the receive PN cannot
represent the next value. The replay checks subsequently accept the same
authenticated terminal frame again.
Record receive-SA exhaustion after accepting the legitimate terminal frame
and reject subsequent frames until userspace explicitly resets the PN. Keep
XPN behavior unchanged. Preserve the exhaustion state if a hardware-offload
PN update fails and the old PN is restored.
The identical terminal-PN frame crossed the controlled port twice in all
three baseline runs. All three fixed runs rejected the replay. PN max-1,
fresh terminal-frame, and bad-ICV controls retained their expected
behavior.
The demonstrated impact is a replay and integrity-policy bypass. No memory
corruption or RCE/LPE primitive was observed.
This change was prepared with assistance from OpenAI Codex. I reviewed the
source change, test results, and this commit message.
Fixes: c09440f7dcb3 ("macsec: introduce IEEE 802.1AE driver")
Assisted-by: OpenAI Codex
Signed-off-by: Sung Byeongchan <tjdqudcks0424@naver.com>
---
drivers/net/macsec.c | 18 ++++++++++++++----
include/net/macsec.h | 1 +
2 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/drivers/net/macsec.c b/drivers/net/macsec.c
index 78a19b1346321..906108e10b32d 100644
--- a/drivers/net/macsec.c
+++ b/drivers/net/macsec.c
@@ -750,8 +750,10 @@ static bool macsec_post_decrypt(struct sk_buff *skb, struct macsec_secy *secy, u
/* Now perform replay protection check again
* (see IEEE 802.1AE-2006 figure 10-5)
*/
- if (secy->replay_protect && pn < lowest_pn &&
- (!secy->xpn || pn_same_half(pn, lowest_pn))) {
+ if (secy->replay_protect &&
+ (rx_sa->exhausted ||
+ (pn < lowest_pn &&
+ (!secy->xpn || pn_same_half(pn, lowest_pn))))) {
spin_unlock(&rx_sa->lock);
u64_stats_update_begin(&rxsc_stats->syncp);
rxsc_stats->stats.InPktsLate++;
@@ -813,6 +815,8 @@ static bool macsec_post_decrypt(struct sk_buff *skb, struct macsec_secy *secy, u
rx_sa->next_pn_halves.upper++;
rx_sa->next_pn_halves.lower = pn + 1;
}
+ if (secy->replay_protect && !secy->xpn && pn == U32_MAX)
+ rx_sa->exhausted = true;
spin_unlock(&rx_sa->lock);
}
@@ -1252,8 +1256,9 @@ static rx_handler_result_t macsec_handle_frame(struct sk_buff **pskb)
bool late;
spin_lock(&rx_sa->lock);
- late = rx_sa->next_pn_halves.lower >= secy->replay_window &&
- hdr_pn < (rx_sa->next_pn_halves.lower - secy->replay_window);
+ late = rx_sa->exhausted ||
+ (rx_sa->next_pn_halves.lower >= secy->replay_window &&
+ hdr_pn < (rx_sa->next_pn_halves.lower - secy->replay_window));
if (secy->xpn)
late = late && pn_same_half(rx_sa->next_pn_halves.lower, hdr_pn);
@@ -1409,6 +1414,7 @@ static int init_rx_sa(struct macsec_rx_sa *rx_sa, char *sak, int key_len,
rx_sa->ssci = MACSEC_UNDEF_SSCI;
rx_sa->active = false;
+ rx_sa->exhausted = false;
rx_sa->next_pn = 1;
refcount_set(&rx_sa->refcnt, 1);
spin_lock_init(&rx_sa->lock);
@@ -2388,6 +2394,7 @@ static int macsec_upd_rxsa(struct sk_buff *skb, struct genl_info *info)
struct nlattr *tb_rxsc[MACSEC_RXSC_ATTR_MAX + 1];
struct nlattr *tb_sa[MACSEC_SA_ATTR_MAX + 1];
bool was_active;
+ bool was_exhausted;
pn_t prev_pn;
int ret = 0;
@@ -2426,7 +2433,9 @@ static int macsec_upd_rxsa(struct sk_buff *skb, struct genl_info *info)
spin_lock_bh(&rx_sa->lock);
prev_pn = rx_sa->next_pn_halves;
+ was_exhausted = rx_sa->exhausted;
rx_sa->next_pn = nla_get_uint(tb_sa[MACSEC_SA_ATTR_PN]);
+ rx_sa->exhausted = false;
spin_unlock_bh(&rx_sa->lock);
}
@@ -2462,6 +2471,7 @@ static int macsec_upd_rxsa(struct sk_buff *skb, struct genl_info *info)
if (tb_sa[MACSEC_SA_ATTR_PN]) {
spin_lock_bh(&rx_sa->lock);
rx_sa->next_pn_halves = prev_pn;
+ rx_sa->exhausted = was_exhausted;
spin_unlock_bh(&rx_sa->lock);
}
rx_sa->active = was_active;
diff --git a/include/net/macsec.h b/include/net/macsec.h
index d962093ee9237..57e9789ac75ef 100644
--- a/include/net/macsec.h
+++ b/include/net/macsec.h
@@ -136,6 +136,7 @@ struct macsec_rx_sa {
};
refcount_t refcnt;
bool active;
+ bool exhausted;
struct macsec_rx_sa_stats __percpu *stats;
struct macsec_rx_sc *sc;
struct rcu_work destroy_work;
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH net] macsec: reject replays after non-XPN receive PN exhaustion
2026-10-08 8:25 [PATCH net] macsec: reject replays after non-XPN receive PN exhaustion Sung Byeongchan
@ 2026-10-08 8:30 ` netdev-bot+sinfo
2026-10-08 8:39 ` 성병찬
2026-10-08 16:35 ` Sabrina Dubroca
1 sibling, 1 reply; 4+ messages in thread
From: netdev-bot+sinfo @ 2026-10-08 8:30 UTC (permalink / raw)
To: Sung Byeongchan; +Cc: Sabrina Dubroca, netdev
Hi!
This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:
- How the issue was discovered, e.g. hit in production, hit during
development, syzbot report, manual code inspection, LLM or static
analysis tool scan.
Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.
The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net] macsec: reject replays after non-XPN receive PN exhaustion
2026-10-08 8:30 ` netdev-bot+sinfo
@ 2026-10-08 8:39 ` 성병찬
0 siblings, 0 replies; 4+ messages in thread
From: 성병찬 @ 2026-10-08 8:39 UTC (permalink / raw)
To: netdev-bot+sinfo; +Cc: Sabrina Dubroca, netdev
Hi,
This issue was identified during manual static source review of the
non-XPN receive PN exhaustion and replay-checking paths, with assistance
from OpenAI Codex.
I subsequently reproduced and validated it using a local veth pair and
the production software MACsec receive path. The identical authenticated
PN U32_MAX frame was delivered twice in all three baseline runs. With the
patch applied, the replay was rejected in all three runs, while the
PN max-1, fresh terminal-frame, and bad-ICV controls retained their
expected behavior.
Thanks,
Sung Byeongchan
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net] macsec: reject replays after non-XPN receive PN exhaustion
2026-10-08 8:25 [PATCH net] macsec: reject replays after non-XPN receive PN exhaustion Sung Byeongchan
2026-10-08 8:30 ` netdev-bot+sinfo
@ 2026-10-08 16:35 ` Sabrina Dubroca
1 sibling, 0 replies; 4+ messages in thread
From: Sabrina Dubroca @ 2026-10-08 16:35 UTC (permalink / raw)
To: Sung Byeongchan; +Cc: netdev
2026-10-08, 17:25:50 +0900, Sung Byeongchan wrote:
> When a non-XPN RXSA accepts PN U32_MAX, advancing the receive PN cannot
> represent the next value. The replay checks subsequently accept the same
> authenticated terminal frame again.
And in XPN? I don't see anything checking that .upper didn't wrap, so
we'll accept replays of the first chunk of sequence numbers?
It also feels strange to have an "exhausted" bit that only ever gets
set for !xpn.
> Record receive-SA exhaustion after accepting the legitimate terminal frame
> and reject subsequent frames until userspace explicitly resets the PN. Keep
> XPN behavior unchanged. Preserve the exhaustion state if a hardware-offload
> PN update fails and the old PN is restored.
>
> The identical terminal-PN frame crossed the controlled port twice in all
> three baseline runs. All three fixed runs rejected the replay. PN max-1,
> fresh terminal-frame, and bad-ICV controls retained their expected
> behavior.
We should also have selftests for all those limit cases (for 32b:
exhaustion/wrap, for XPN: both upper32 increase and exhaustion).
> diff --git a/drivers/net/macsec.c b/drivers/net/macsec.c
> index 78a19b1346321..906108e10b32d 100644
> --- a/drivers/net/macsec.c
> +++ b/drivers/net/macsec.c
> @@ -750,8 +750,10 @@ static bool macsec_post_decrypt(struct sk_buff *skb, struct macsec_secy *secy, u
> /* Now perform replay protection check again
> * (see IEEE 802.1AE-2006 figure 10-5)
> */
> - if (secy->replay_protect && pn < lowest_pn &&
> - (!secy->xpn || pn_same_half(pn, lowest_pn))) {
> + if (secy->replay_protect &&
> + (rx_sa->exhausted ||
> + (pn < lowest_pn &&
> + (!secy->xpn || pn_same_half(pn, lowest_pn))))) {
The code you're modifying wasn't particularly nice, but this is
completely unreadable. And there's another variant of this test
written in a slightly different, but also completely unreadable, for
the pre-check (well, pn_same_half doesn't take the same reference for
some mysterious reason).
--
Sabrina
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-08 16:35 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-08 8:25 [PATCH net] macsec: reject replays after non-XPN receive PN exhaustion Sung Byeongchan
2026-10-08 8:30 ` netdev-bot+sinfo
2026-10-08 8:39 ` 성병찬
2026-10-08 16:35 ` Sabrina Dubroca
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox