From adac31be858215260e09e8d5c8ba5abf06fb00a6 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Fri, 18 Sep 2026 10:33:10 -0700 Subject: [PATCH] fix(incoming): treat GRANT offset as unsigned in homa_grant_pkt A GRANT packet's offset is an unsigned 32-bit wire field, but homa_grant_pkt() reads it into a signed int: int new_offset = ntohl(h->offset); An offset with the top bit set (>= 2GB) becomes negative, so `new_offset > rpc->msgout.granted` is false and the grant is silently dropped instead of being bounded. Read it as u32. Clamp before the assignment, in unsigned space, so the oversized value never lands in the signed rpc->msgout.granted (int) even transiently: rpc->msgout.granted = min_t(u32, new_offset, rpc->msgout.length); The result is always <= msgout.length, so the store into the signed field is well-defined, and grants are capped at the message length as before. Adds a table-driven test, homa_grant_pkt__offset_is_unsigned: the high-bit row advances granted to the message length (20000) after the fix, versus being ignored (granted stays 5000) before it. The normal and past-end rows match the existing __basics / __grant_past_end_of_message behavior; the incoming suite stays green (123/123). Co-Authored-By: Claude Opus 4.8 --- homa_incoming.c | 10 ++++----- test/unit_homa_incoming.c | 44 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/homa_incoming.c b/homa_incoming.c index 9e72c521..981e9584 100644 --- a/homa_incoming.c +++ b/homa_incoming.c @@ -806,17 +806,15 @@ void homa_grant_pkt(struct sk_buff *skb, struct homa_rpc *rpc) __must_hold(rpc->bucket->lock) { struct homa_grant_hdr *h = (struct homa_grant_hdr *)skb->data; - int new_offset = ntohl(h->offset); + u32 new_offset = ntohl(h->offset); tt_record4("processing grant for id %llu, offset %d, priority %d, increment %d", homa_local_id(h->common.sender_id), ntohl(h->offset), h->priority, new_offset - rpc->msgout.granted); if (rpc->state == RPC_OUTGOING) { - if (new_offset > rpc->msgout.granted) { - rpc->msgout.granted = new_offset; - if (new_offset > rpc->msgout.length) - rpc->msgout.granted = rpc->msgout.length; - } + if (new_offset > rpc->msgout.granted) + rpc->msgout.granted = min_t(u32, new_offset, + rpc->msgout.length); rpc->msgout.sched_priority = h->priority; homa_xmit_data(rpc); } diff --git a/test/unit_homa_incoming.c b/test/unit_homa_incoming.c index 74b4b05e..cfa23a45 100644 --- a/test/unit_homa_incoming.c +++ b/test/unit_homa_incoming.c @@ -1874,6 +1874,50 @@ TEST_F(homa_incoming, homa_grant_pkt__grant_past_end_of_message) &h.common, 0, 0)); EXPECT_EQ(20000, crpc->msgout.granted); } +TEST_F(homa_incoming, homa_grant_pkt__offset_is_unsigned) +{ + /* A GRANT's offset is an unsigned wire field. Reading it into a signed + * int makes an offset with the top bit set (>= 2GB) negative, so the + * "new_offset > granted" test fails and the grant is silently dropped + * instead of being clamped to the message length. Treat it as u32 so + * the existing >granted / clamp-to-length logic bounds it correctly. + * + * Table-driven: each row is a grant offset and the granted value it + * should produce for a 20000-byte outgoing message. + */ + struct homa_rpc *crpc = unit_client_rpc(&self->hsk, + UNIT_OUTGOING, self->client_ip, self->server_ip, + self->server_port, self->client_id, 20000, 1600); + static const struct { + const char *name; + u32 offset; + int exp_granted; + } cases[] = { + {"normal_grant_advances", 12000, 12000}, + {"grant_past_end_clamps_to_length", 25000, 20000}, + {"high_bit_offset_clamps_not_ignored", 0x80000000, 20000}, + }; + int i; + + ASSERT_NE(NULL, crpc); + for (i = 0; i < (int)ARRAY_SIZE(cases); i++) { + struct homa_grant_hdr h = {{.sport = htons(self->server_port), + .dport = htons(self->hsk.port), + .sender_id = cpu_to_be64(self->server_id), + .type = GRANT}, + .offset = htonl(cases[i].offset), + .priority = 3}; + + TH_LOG("case: %s", cases[i].name); + crpc->state = RPC_OUTGOING; + crpc->msgout.granted = 5000; + unit_log_clear(); + homa_dispatch_pkts(mock_skb_alloc(self->client_ip, + self->server_ip, + &h.common, 0, 0)); + EXPECT_EQ(cases[i].exp_granted, crpc->msgout.granted); + } +} #endif /* See strip.py */ TEST_F(homa_incoming, homa_resend_pkt__unknown_rpc)