Technical Root Cause Analysis
1. Executive Summary
In AF_RXRPC, Path MTU Discovery (PMTUD) probes are implemented as RXRPC_ACK_PING packets padded with zero pages up to the trial probe MTU size (probe_mtu = call->peer->pmtud_trial + sizeof(struct rxrpc_wire_header)). In rxrpc_send_ACK() (net/rxrpc/output.c), when why == rxrpc_propose_ack_ping_for_mtu_probe, the kernel appends up to PAGE_SIZE chunks of zero pages to call->local->kvec inside a while (len < probe_mtu) loop. However, the loop fails to validate nr_kv against the capacity of call->local->kvec (which is 47 elements). When probe_mtu is large (which can occur due to unsigned underflow in rxrpc_assess_MTU_size() when peer->if_mtu < peer->hdrsize, or an excessively large MTU), nr_kv advances past index 46, performing an out-of-bounds write past the allocated 1584-byte region of struct rxrpc_local in the kmalloc-2k slab cache.
2. Detailed Technical Breakdown
Struct Layout and Object Sizing
struct rxrpc_local is defined in net/rxrpc/ar-internal.h:347. At the end of the structure (byte offset 832):
struct rxrpc_local {
...
struct sockaddr_rxrpc srx; /* local address, offset 792, size 36 */
/* offset 832 (6656 bits) */
union {
struct kvec kvec[1 + RXRPC_MAX_NR_JUMBO > 3 + 16 ?
1 + RXRPC_MAX_NR_JUMBO : 3 + 16];
struct bio_vec bvec[3 + 16];
};
};
RXRPC_MAX_NR_JUMBO is defined as 46 (net/rxrpc/protocol.h:99).
- Therefore,
kvec has 1 + 46 = 47 elements (indices 0..46).
- Each
struct kvec on 64-bit systems is 16 bytes (iov_base [8 bytes] + iov_len [8 bytes]).
- Sizing:
47 * 16 = 752 bytes.
- Total size of
struct rxrpc_local: 832 + 752 = 1584 bytes.
- When allocated by
rxrpc_alloc_local() (net/rxrpc/local_object.c:115), it is placed in the kmalloc-2k cache with an allocated size of 1584 bytes.
PMTUD Probe Construction in rxrpc_send_ACK()
When an MTU probe ping is generated (net/rxrpc/call_event.c:484 -> rxrpc_send_probe_for_pmtud() -> rxrpc_send_ACK()):
rxrpc_alloc_ack() reserves the first 3 kvec entries:
kv[0]: wire header and ack packet (whdr and ack)
kv[1]: SACK table
kv[2]: ack filler and trailer
It returns nr_kv = 3.
- In
rxrpc_send_ACK() (net/rxrpc/output.c:325-338):
/* Extend a path MTU probe ACK. */
if (why == rxrpc_propose_ack_ping_for_mtu_probe) {
size_t probe_mtu = call->peer->pmtud_trial + sizeof(struct rxrpc_wire_header);
if (len > probe_mtu)
goto skip;
while (len < probe_mtu) {
size_t part = umin(probe_mtu - len, PAGE_SIZE);
kv[nr_kv].iov_base = page_address(ZERO_PAGE(0));
kv[nr_kv].iov_len = part;
len += part;
nr_kv++;
}
}
- The author of the union sized it assuming a PING ACK would only ever be padded up to 64K (
3 + 16 entries, since 16 * 4096 = 65536).
- However, there is no check ensuring
nr_kv < ARRAY_SIZE(call->local->kvec).
How probe_mtu Exceeds the Array Limit
In net/rxrpc/peer_object.c:210-213:
peer->max_data = umin(RXRPC_JUMBO(1), peer->if_mtu - peer->hdrsize);
peer->pmtud_good = 500;
peer->pmtud_bad = peer->if_mtu - peer->hdrsize + 1;
peer->pmtud_trial = umin(peer->max_data, peer->pmtud_bad - 1);
peer->pmtud_pending = true;
peer->if_mtu and peer->hdrsize are unsigned integers (unsigned int and unsigned short). When peer->if_mtu is less than peer->hdrsize (for instance, when a network device MTU is configured to a very small value, or when MTU is 0 on devices where dev->min_mtu == 0), peer->if_mtu - peer->hdrsize unsigned-underflows to a value near UINT_MAX.
In net/rxrpc/peer_event.c:450, rxrpc_input_probe_for_pmtud() updates trial:
trial = (good + bad) / 2;
peer->pmtud_trial = trial;
Without strict bounding against the maximum allowable probe size or ARRAY_SIZE(call->local->kvec), pmtud_trial can easily reach or exceed 180,224 bytes.
When probe_mtu >= 180,250, the padding loop executes 44 or more times:
nr_kv increments from 3 to 47.
- At iteration 44 (
nr_kv == 47), the code accesses kv[47].iov_base.
- Offset calculation:
832 + 47 * 16 = 1584 bytes.
- This immediately writes 8 bytes (
page_address(ZERO_PAGE(0))) at byte offset 1584—exactly 0 bytes past the end of the 1584-byte allocated rxrpc_local object.
3. Classification and Production Impact
- RootCauseFiles:
net/rxrpc/output.c: Missing bounds check on nr_kv against ARRAY_SIZE(call->local->kvec) in rxrpc_send_ACK().
net/rxrpc/peer_object.c: Unsigned underflow vulnerability in rxrpc_assess_MTU_size() when calculating peer->if_mtu - peer->hdrsize.
- MemoryCorruption:
true. Writing iov_base (8 bytes) and iov_len (8 bytes) beyond the boundary of struct rxrpc_local corrupts the slab metadata and/or the payload of the neighboring object in the kmalloc-2k slab cache.
- InfoLeak:
"none". The memory appended to the I/O vector consists of zero-page addresses (ZERO_PAGE(0)). No out-of-bounds or uninitialized heap/stack memory is leaked or transmitted.
- RawDenialOfService:
true. Corrupting adjacent slab objects in kmalloc-2k leads to kernel crashes, data structure corruption, or kernel panic in production kernels.
- IsNetCandidate:
true. The defect is in the RxRPC network socket implementation (AF_RXRPC), specifically in packet output and PMTUD processing.
- IsKVMCandidate:
false. Virtualization and KVM subsystems are not involved.