check chunk header bounds in ubring recv paths - #3497
Conversation
There was a problem hiding this comment.
Pull request overview
This PR hardens the ubring shared-memory receive paths against malformed or malicious chunk headers by validating msg_len and cur_index before copying from the fixed-size (60-byte) payload, preventing out-of-bounds reads in UbrTrxRecvBlockMode and StartReadv.
Changes:
- Add
UBRing::IsRecvChunkHeaderValid()to validate(msg_len <= UBR_MSG_PAYLOAD_LEN) && (cur_index <= msg_len). - Apply the validation in both receive implementations (
UbrTrxRecvBlockModeandStartReadv), returningUBRING_ERRwitherrno=EBADMSGon invalid headers. - Add unit tests that cover the validation helper behavior for in-range and out-of-range values.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| test/brpc_ubring_unittest.cpp | Adds tests for the new receive chunk header validation helper. |
| src/brpc/ubshm/ub_ring.h | Introduces IsRecvChunkHeaderValid() to guard against invalid receive chunk header fields. |
| src/brpc/ubshm/ub_ring.cpp | Applies the new validation in both recv and readv receive loops before memcpy. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if (UNLIKELY(!IsRecvChunkHeaderValid(chunk_msg_len, cur_index))) { | ||
| LOG(ERROR) << "Trx readv failed, invalid chunk header msg_len=" | ||
| << (uint32_t)chunk_msg_len << " cur_index=" << (uint32_t)cur_index; | ||
| errno = EBADMSG; | ||
| return UBRING_ERR; | ||
| } |
| // The receive paths (UbrTrxRecvBlockMode / StartReadv) read `msg_len' and | ||
| // `cur_index' out of a chunk header the remote peer writes into the ring, then | ||
| // copy `msg_len - cur_index' bytes from the 60-byte `payload.inner'. A peer | ||
| // that writes msg_len > 60, or cur_index > msg_len (which underflows the | ||
| // uint8_t subtraction), makes that copy over-read the payload into adjacent | ||
| // shared memory. IsRecvChunkHeaderValid is the guard both paths now apply. | ||
| TEST(UBRingRecvChunkHeaderTest, reject_out_of_range_len_and_index) { | ||
| using brpc::ubring::UBRing; | ||
| // Legitimate values a well-formed peer produces: full payload, partial | ||
| // consume, and the fully-consumed boundary. | ||
| EXPECT_TRUE(UBRing::IsRecvChunkHeaderValid(UBR_MSG_PAYLOAD_LEN, 0)); | ||
| EXPECT_TRUE(UBRing::IsRecvChunkHeaderValid(10, 5)); | ||
| EXPECT_TRUE(UBRing::IsRecvChunkHeaderValid(0, 0)); | ||
| EXPECT_TRUE(UBRing::IsRecvChunkHeaderValid(UBR_MSG_PAYLOAD_LEN, | ||
| UBR_MSG_PAYLOAD_LEN)); | ||
| // msg_len past the payload capacity -> over-read source. | ||
| EXPECT_FALSE(UBRing::IsRecvChunkHeaderValid(UBR_MSG_PAYLOAD_LEN + 1, 0)); | ||
| EXPECT_FALSE(UBRing::IsRecvChunkHeaderValid(255, 0)); | ||
| // cur_index past msg_len -> `msg_len - cur_index' underflows to a large | ||
| // uint8_t. | ||
| EXPECT_FALSE(UBRing::IsRecvChunkHeaderValid(0, 1)); | ||
| EXPECT_FALSE(UBRing::IsRecvChunkHeaderValid(10, 20)); | ||
| } |
|
@zchuango Have a look if you have time |
OK |
|
@ubeddulla Thanks for fixing this! The bounds checks in both |
What problem does this PR solve?
Issue Number: resolve N/A
Problem Summary:
UbrTrxRecvBlockMode and StartReadv read a chunk's msg_len and cur_index straight from the receive-ring header that the remote peer writes into shared memory, then memcpy msg_len minus cur_index bytes out of the fixed 60-byte payload.inner without checking either value. A peer that sets msg_len above UBR_MSG_PAYLOAD_LEN (60), or cur_index above msg_len so the uint8_t subtraction wraps, makes that copy read past the payload into the adjacent chunk header and neighbouring ring slots. Under ASAN this shows up as a heap-buffer-overflow read of size 255 at 0 bytes after the 64-byte chunk. The send path already caps msg_len at UBR_MSG_PAYLOAD_LEN, so only the receive side was missing the check.
What is changed and the side effects?
Changed:
Added IsRecvChunkHeaderValid (msg_len <= UBR_MSG_PAYLOAD_LEN and cur_index <= msg_len) and apply it at both receive sites before the copy, rejecting a malformed chunk with errno=EBADMSG and the same UBRING_ERR the existing pre-checks return. Added a regression test in brpc_ubring_unittest.cpp.
Side effects:
Performance effects: one comparison per chunk, negligible.
Breaking backward compatibility: none. Valid chunks (msg_len <= 60, cur_index <= msg_len) behave exactly as before.
Check List: