Skip to content

Fix exception function code wraparound when function >= 0x80 - #873

Closed
afonsojanu wants to merge 1 commit into
stephane:masterfrom
afonsojanu:fix/exception-function-code-wraparound
Closed

afonsojanu wants to merge 1 commit into
stephane:masterfrom
afonsojanu:fix/exception-function-code-wraparound

Conversation

@afonsojanu

Copy link
Copy Markdown

response_exception() builds the exception function byte with plain integer addition:

sft->function = sft->function + 0x80;

modbus_reply() routes any function code its switch doesn't recognize through the default case into this function, and that includes raw bytes from 0x80 to 0xFF (a client can send those directly with modbus_send_raw_request, or they show up as garbage/unsupported function codes on the wire). When sft->function is already >= 0x80, the addition carries past 0xFF. sft->function is an int, so the overflow itself is harmless, but the value then gets written into the response buffer as a single byte in build_response_basis (rsp[7] = sft->function; for TCP, similarly for RTU), which truncates it back into range. For a function byte like 0xFF, 0xFF + 0x80 = 0x17F, which truncates to 0x7F, a value with the top bit clear. The client then sees what looks like an ordinary function code instead of an exception.

This is the same class of bug as #845, which covers modbus_reply_exception() (already has an open PR, #869). This report is about the separate code path inside modbus_reply() itself, used both for the "unrecognized function code" case and the "truncated request" case, which #869 doesn't touch.

Fix: use bitwise OR instead of addition, matching the actual Modbus rule (set the top bit of the function code). For any function code below 0x80 this produces exactly the same byte as before; for one at or above 0x80 it now stays correct instead of wrapping.

Verified with a small standalone repro that opens a modbus_reply() call through a socketpair and inspects the raw response bytes: with a request function byte of 0xFF, the reply's function byte came back as 0x7F before this change and 0xFF after.

Also adds a client-side test next to the existing "invalid function code" test in unit-test-client.c, using a function byte that already has its top bit set (0xFF), which fails before the fix and passes after.

response_exception() builds the exception function byte with plain
integer addition (function + 0x80). modbus_reply()'s default case
routes any unrecognized function code there, including raw bytes from
0x80 to 0xFF, so the addition can carry past 0xFF. Once that gets
truncated to a single byte for the wire in build_response_basis, the
result wraps back below 0x80 and the response looks like an ordinary
function code instead of an exception.

Switch to a bitwise OR, which matches the Modbus spec (set the top
bit) and gives the same result as the addition whenever the top bit
was already clear.

Adds a client-side regression test alongside the existing invalid
function code test, using a request byte that already has the top bit
set.
@cla-bot

cla-bot Bot commented Sep 14, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement. In order for us to review and merge your code, please fill https://forms.gle/5635zjphDo5JEJQSA to get added. Your document will be manually checked by the maintainer. Be patient...

@afonsojanu afonsojanu closed this Oct 5, 2026
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