mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* Re: [PATCH] tipc: validate data size before reading Gap ACK block header
@ 2026-09-10  6:38 netdev-bot+sashiko
  0 siblings, 0 replies; 4+ messages in thread
From: netdev-bot+sashiko @ 2026-09-10  6:38 UTC (permalink / raw)
  To: elb12345
  Cc: jmaloy, tung.quang.nguyen, tipc-discussion, netdev, linux-kernel,
	stable, kuba

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:

Critical: 0 · High: 0 · Medium: 1 (1) · Low: 0 (1)

- [Medium] Commit message overstates the impact of the fixed access in
  tipc_get_gap_ack_blks() (net/tipc/link.c:1422-1434).

Pre-existing issues:
- [Medium] tipc_get_gap_ack_blks() (net/tipc/link.c:1421-1441), the
  receive-side validator this patch extends, never bounds the peer-…
- [Low] The backward-compatibility branch of tipc_get_gap_ack_blks()
  writes into peer-supplied received packet data: p->bgack_cnt = 0…

--

tipc: validate data size before reading Gap ACK block header

This adds a length check to tipc_get_gap_ack_blks() so the Gap ACK block
header fields are only read when msg_data_sz(hdr) is at least
sizeof(struct tipc_gap_ack_blks), otherwise the function takes the
p = NULL path.  The commit is marked Fixes: d7626b5acff9 and Cc: stable.

(no Link: tags in the commit header)

> diff --git a/net/tipc/link.c b/net/tipc/link.c
> index 6427c69f89294..0be86cbdc913d 100644
> --- a/net/tipc/link.c
> +++ b/net/tipc/link.c
> @@ -1419,6 +1419,8 @@ u16 tipc_get_gap_ack_blks(struct tipc_gap_ack_blks **ga, struct tipc_link *l,
>  
>  	/* Does peer support the Gap ACK blocks feature? */
>  	if (l->peer_caps & TIPC_GAP_ACK_BLOCK) {
> +		if (msg_data_sz(hdr) < sizeof(*p))
> +			goto out;
>  		p = (struct tipc_gap_ack_blks *)msg_data(hdr);
>  		sz = ntohs(p->len);

[Severity: Medium]
Can the impact described in the changelog actually be produced?  The
changelog says:

    "causing an out-of-bounds read of up to 4 bytes past the valid skb
     data"

and

    "the OOB accesses inside the function itself are undefined behavior
     and are detectable by KASAN."

Both call paths run on a validated and linearized skb.  tipc_msg_validate()
in net/tipc/msg.c enforces:

	msz = msg_size(hdr);
	if (unlikely(msz < hsz))
		return false;
	...
	if (unlikely(skb->len < msz))
		return false;

with hsz bounded by MAX_H_SIZE and pskb_may_pull(skb, hsz) already done.
tipc_rcv() in net/tipc/node.c linearizes the skb, and tipc_link_proto_rcv()
linearizes again.

So msg_data(hdr) is at worst exactly at skb_tail_pointer(), and the worst
case read covers [skb->tail, skb->tail + 4), which is skb tailroom or the
leading bytes of skb_shared_info.  Both live inside the same kmalloc'ed
head allocation, which KASAN does not poison.

Would it be more accurate to describe this as a read of stale bytes past
msg_size() rather than a KASAN-detectable out-of-bounds read?

The changelog also says:

    "In the backward-compatible code path, a 1-byte out-of-bounds write also
     occurs through "p->bgack_cnt = 0"."

That store is reached only when the stale bytes happen to satisfy
sz == struct_size(p, gacks, p->ugack_cnt) with a non-zero p->ugack_cnt, so
"also occurs" reads stronger than the code allows.

Since both callers already discard the result (glen > dlen in
tipc_link_proto_rcv(), glen > msg_data_sz(hdr) in tipc_bcast_sync_rcv()),
is the Cc: stable justified, or is this hardening plus a small behavioural
cleanup?  Before the patch a short STATE_MSG could be dropped or accepted
non-deterministically depending on the trailing garbage, which is worth
mentioning in the changelog instead.

>  		/* Sanity check */

[Severity: Medium]
This isn't a bug introduced by this patch, but while the validator in
tipc_get_gap_ack_blks() is being extended: does the sanity check bound the
peer-supplied counts by MAX_GAP_ACK_BLKS?

The check just below is:

	if (sz == struct_size(p, gacks, size_add(p->ugack_cnt, p->bgack_cnt))) {

With u8 counts this accepts up to 510 blocks, i.e. sz up to about 2044,
while net/tipc/msg.h declares:

	#define MAX_GAP_ACK_BLKS	128

and the legitimate sender caps each direction in
__tipc_build_gap_ack_blks():

			if (++n >= MAX_GAP_ACK_BLKS / 2) {

The consumer then asserts the msg.h limit in tipc_link_advance_transmq():

				WARN_ONCE(more(start, end) ||
					  (!offset &&
					   si < last_ga->bgack_cnt) ||
					  si > MAX_GAP_ACK_BLKS,
					  "Corrupted Gap ACK: %d %d %d %d %d\n",

Can a peer that negotiated TIPC_GAP_ACK_BLOCK send bgack_cnt >= 129 with a
matching len, get the record accepted here, and then trip that WARN_ONCE
via tipc_bcast_sync_rcv() -> tipc_link_bc_ack_rcv() ->
tipc_link_advance_transmq()?  That would be fatal with panic_on_warn.

No out-of-bounds access follows, since the kmemdup is sized by
struct_size(ga, gacks, ga->bgack_cnt) and every walk is bounded by the same
count.  Would adding a ugack_cnt + bgack_cnt <= MAX_GAP_ACK_BLKS check here
be worthwhile while this function is being touched?

> @@ -1435,6 +1437,7 @@ u16 tipc_get_gap_ack_blks(struct tipc_gap_ack_blks **ga, struct tipc_link *l,
>  		}
>  	}
>  	/* Other cases: ignore! */
> +out:
>  	p = NULL;
>  
>  ok:

[Severity: Low]
This is a pre-existing issue, but the backward-compatibility branch of
tipc_get_gap_ack_blks() writes into the received packet:

		} else if (uc && sz == struct_size(p, gacks, p->ugack_cnt)) {
			if (p->ugack_cnt) {
				p->bgack_cnt = 0;

Is the data area known to be exclusively owned at that point?  The only
preparation on this path is skb_linearize():

net/tipc/link.c:tipc_link_proto_rcv() {
	if (skb_linearize(skb))
		goto exit;

	hdr = buf_msg(skb);
	data = msg_data(hdr);

and net/tipc/node.c:tipc_rcv() does the same:

		if (unlikely(skb_linearize(skb))) {
			tipc_node_put(n);
			goto discard;
		}

skb_linearize() returns immediately for an already-linear skb, so it does
not unshare a cloned-but-linear data buffer, and git grep finds no
skb_cloned()/skb_unshare()/skb_ensure_writable() on the LINK_PROTOCOL
receive path in net/tipc/.

If the same frame is delivered to an AF_PACKET tap, packet_rcv() does
skb_share_check() -> skb_clone(), so the buffer TIPC then writes to is
shared, and the clone's reader sees bgack_cnt as 0.

This store predates the patch, and the patch only removes its
out-of-bounds aspect.  If there is a respin, could the unicast count and
size be returned to the caller instead of zeroing a field inside the
received packet?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/AS8P251MB000136A75E6C720544CAC017C8B32%40AS8P251MB0001.EURP251.PROD.OUTLOOK.COM

^ permalink raw reply	[flat|nested] 4+ messages in thread
* [PATCH] tipc: validate data size before reading Gap ACK block header
@ 2026-09-06 23:38 Aamir Ahmed
  2026-09-08  2:05 ` Tung Quang Nguyen
  0 siblings, 1 reply; 4+ messages in thread
From: Aamir Ahmed @ 2026-09-06 23:38 UTC (permalink / raw)
  To: Jon Maloy, Tung Quang Nguyen
  Cc: tipc-discussion, netdev, linux-kernel, stable

tipc_get_gap_ack_blks() reads the Gap ACK block header fields (len,
ugack_cnt, bgack_cnt) from msg_data(hdr) without first checking that the
message data area is large enough to hold the header struct.  A peer
that has negotiated TIPC_GAP_ACK_BLOCK capability can send a STATE_MSG
or broadcast PROTOCOL message with a data area shorter than
sizeof(struct tipc_gap_ack_blks), causing an out-of-bounds read of up
to 4 bytes past the valid skb data.

In the backward-compatible code path, a 1-byte out-of-bounds write also
occurs through "p->bgack_cnt = 0".

Both callers (tipc_link_proto_rcv and tipc_bcast_sync_rcv) validate the
returned size against msg_data_sz() after the function returns, so the
invalid data is never used further.  However, the OOB accesses inside
the function itself are undefined behavior and are detectable by KASAN.

Add a check at the start of the function that msg_data_sz(hdr) is at
least sizeof(*p) before reading any fields, jumping to the p = NULL path
when the data area is too small.

Fixes: d7626b5acff9 ("tipc: introduce Gap ACK blocks for broadcast link")
Cc: stable@vger.kernel.org
---
 net/tipc/link.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/net/tipc/link.c b/net/tipc/link.c
index 6427c69f8929..0be86cbdc913 100644
--- a/net/tipc/link.c
+++ b/net/tipc/link.c
@@ -1419,6 +1419,8 @@ u16 tipc_get_gap_ack_blks(struct tipc_gap_ack_blks **ga, struct tipc_link *l,
 
 	/* Does peer support the Gap ACK blocks feature? */
 	if (l->peer_caps & TIPC_GAP_ACK_BLOCK) {
+		if (msg_data_sz(hdr) < sizeof(*p))
+			goto out;
 		p = (struct tipc_gap_ack_blks *)msg_data(hdr);
 		sz = ntohs(p->len);
 		/* Sanity check */
@@ -1435,6 +1437,7 @@ u16 tipc_get_gap_ack_blks(struct tipc_gap_ack_blks **ga, struct tipc_link *l,
 		}
 	}
 	/* Other cases: ignore! */
+out:
 	p = NULL;
 
 ok:
-- 
2.43.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-09-10  6:38 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-10  6:38 [PATCH] tipc: validate data size before reading Gap ACK block header netdev-bot+sashiko
  -- strict thread matches above, loose matches on Subject: below --
2026-09-06 23:38 Aamir Ahmed
2026-09-08  2:05 ` Tung Quang Nguyen
2026-09-09 23:55   ` Aamir Ahmed

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®