Skip to content

Fix off-by-one in TCP MBAP length validation (1-byte OOB write) - #123

Open
94xhn wants to merge 1 commit into
debevv:masterfrom
94xhn:fix/tcp-mbap-length-off-by-one
Open

Fix off-by-one in TCP MBAP length validation (1-byte OOB write)#123
94xhn wants to merge 1 commit into
debevv:masterfrom
94xhn:fix/tcp-mbap-length-off-by-one

Conversation

@94xhn

@94xhn 94xhn commented Jul 13, 2026

Copy link
Copy Markdown

Bug

In recv_msg_header() (TCP branch), the MBAP length field is validated with:

if (length < 2 || length > 255)
    return NMBS_ERROR_INVALID_TCP_MBAP;

// Receive the rest of the message
err = recv(nmbs, length - 2);

At this point the 8-byte MBAP header has already been parsed, so msg.buf_idx == 8. recv(nmbs, length - 2) writes starting at buf_idx, i.e. into msg.buf[buf_idx .. buf_idx + (length - 2) - 1].

msg.buf is declared as uint8_t buf[260] (nanomodbus.h), so valid indices are 0..259. With the maximum currently-accepted length == 255, the write is recv(nmbs, 253), covering buf[8..260] — index 260 is one byte past the end of the array. That extra byte lands on the low byte of msg.buf_idx itself, the very next struct member after buf, corrupting internal parser state from a single attacker-controlled length byte on the wire.

I reproduced this with a mock platform.read sending a crafted MBAP header (length = 0x00FF = 255) followed by 253 payload bytes: msg.buf_idx gets clobbered from 8 to the low byte of the last payload byte written.

Fix

Tighten the upper bound from 255 to 254. With length == 254, recv(nmbs, 252) writes exactly buf[8..259], filling buf[260] with zero slack and no overflow. Verified this boundary case is still accepted and produces no out-of-bounds write, and that ordinary small requests are unaffected.

I also ran the project's own test suite (tests/nanomodbus_tests.c) before and after the change with identical pass results.

This is the same one-line class of issue as the existing off-by-one/overflow checks in this file; no other behavior is changed.

recv_msg_header() accepted a TCP MBAP length field up to 255, but
after the 8-byte MBAP header is parsed only 252 bytes remain
available in msg.buf (uint8_t buf[260], valid indices 0-259, with
8 bytes already consumed). A length of 255 causes
recv(nmbs, length - 2) to write 253 bytes starting at buf_idx 8,
i.e. buf[8..260], one byte past the end of buf[260]. That extra
byte lands on the low byte of the immediately following buf_idx
struct member, corrupting internal library state from a single
attacker-controlled byte on the wire (CWE-193).

Change the upper bound from 255 to 254 so the maximum accepted
length exactly fills buf[8..259] with no overflow, matching the
actual space available after the MBAP header.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant