Skip to content

SN_Decode_Register: reject short total_len before NUL-terminator write#540

Open
dxbjavid wants to merge 1 commit into
wolfSSL:masterfrom
dxbjavid:sn-register-bounds-check
Open

SN_Decode_Register: reject short total_len before NUL-terminator write#540
dxbjavid wants to merge 1 commit into
wolfSSL:masterfrom
dxbjavid:sn-register-bounds-check

Conversation

@dxbjavid
Copy link
Copy Markdown

What

SN_Decode_Register (src/mqtt_sn_packet.c:825) writes a NUL terminator
into the receive buffer using the offset
total_len - (rx_payload - rx_buf) and stores regist->topicName = rx_payload without verifying that total_len is at least the number
of bytes already consumed by the fixed portion of the header.

In the extended-length encoding the fixed header is 8 bytes:

[0x01]                  SN_PACKET_LEN_IND (1 byte)
[len_hi][len_lo]        16-bit total length    (2 bytes)
[type]                  message type           (1 byte)
[topicId_hi][topicId_lo]                       (2 bytes)
[packet_id_hi][packet_id_lo]                   (2 bytes)

A malformed packet such as 01 00 07 0A DE AD BE EF declares
total_len = 7 even though those 8 bytes have already been consumed,
which the existing total_len < 7 check at line 851 does not catch
(7 is not < 7). The subtraction total_len - (rx_payload - rx_buf) = 7 - 8 = -1 then runs in signed ptrdiff_t, so the NUL terminator is
written at rx_payload[-1] (one byte before regist->topicName)
and regist->topicName is left pointing one byte past the parsed
packet at non-NUL-terminated memory.

SN_Decode_Register is reached from SN_Client_HandlePacket
(src/mqtt_sn_client.c:91) when the broker / gateway pushes a REGISTER
to the client; the resulting topicName is then passed straight to
the application's register callback as a C string, where any
strlen/printf("%s", ...) walks past the receive buffer.

SN_Decode_Publish (src/mqtt_sn_packet.c:1248) already performs the
equivalent check before computing its payload length; this change
brings SN_Decode_Register in line.

Reproducer

Build with cmake .. -DWOLFMQTT_SN=yes and -fsanitize=address:

byte buf[8] = { 0x01, 0x00, 0x07, 0x0A,
                0xDE, 0xAD, 0xBE, 0xEF };
SN_Register reg;
memset(&reg, 0, sizeof(reg));
SN_Decode_Register(buf, 8, &reg);
(void)strlen(reg.topicName);    /* OOB read */

ASAN before the fix:

==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x...
READ of size 1 at 0x... thread T0
    #0 ...strlen
    #1 main (test_sn_register2)
Address ... is located in stack of thread T0 at offset 40 in frame
  This frame has 2 object(s):
    [32, 40) 'buf' <== Memory access at offset 40 overflows this variable

After the fix the malformed input is rejected with
MQTT_CODE_ERROR_MALFORMED_DATA (-3) and ASAN reports no errors.
Well-formed REGISTER packets (single-byte and extended length) still
decode unchanged.

Fix

Reject the packet with MQTT_CODE_ERROR_MALFORMED_DATA when
total_len does not cover the bytes the fixed header already consumed,
matching the existing pattern in SN_Decode_Publish. The check lives
inside the callee so callers don't need to validate the length field
themselves.

Build / test

  • cmake .. -DWOLFMQTT_TLS=no -DWOLFMQTT_BROKER=yes -DWOLFMQTT_SN=yes
    then make -j — clean build.
  • Standalone ASAN reproducer above: pre-patch fires
    stack-buffer-overflow; post-patch returns -3.
  • Standalone smoke tests for well-formed REGISTER (single-byte and
    3-byte length encodings) both decode the topic name unchanged.

Add a bounds check ensuring total_len covers at least the bytes the
fixed header consumes (length + type + topicId + packet_id) before
computing the topic-name length and writing the NUL terminator.

A malformed REGISTER with extended-length encoding can declare a
total_len smaller than the 8 bytes the extended header itself consumes
(e.g. [0x01][0x00][0x07][type][topicId][packet_id]). With the prior
code the index 'total_len - (rx_payload - rx_buf)' computes to a
negative value, so the NUL terminator is written before
regist->topicName instead of after, and regist->topicName ends up
pointing past the parsed packet at non-NUL-terminated memory.

When the SN client passes this topic name into the application
register callback as a C string (e.g. printed via %s or measured with
strlen()), the read walks past rx_buf into adjacent memory. ASAN flags
this as a stack-buffer-overflow on the strlen() scan.
@wolfSSL-Bot
Copy link
Copy Markdown

Can one of the admins verify this patch?

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.

2 participants