Skip to content

Robustness Improvements#91

Open
MarcAntoineCRUE wants to merge 12 commits intohmueller01:masterfrom
MarcAntoineCRUE:dev/performances
Open

Robustness Improvements#91
MarcAntoineCRUE wants to merge 12 commits intohmueller01:masterfrom
MarcAntoineCRUE:dev/performances

Conversation

@MarcAntoineCRUE
Copy link

Summary

This merge request significantly improves the robustness and safety of the PubSubClient library, without changing its public API or breaking existing usage. All changes are fully covered by the test suite (57/57 tests passing).

Key Fixes

  • Payload Length Underflow Protection: In handlePacket(), the calculation of payloadLen is now guarded to prevent unsigned underflow if a malformed packet claims a topic length larger than the actual buffer. This prevents potential buffer overreads and undefined behavior.
  • Payload Offset Overflow Protection: The calculation of payloadOffset now uses size_t instead of uint16_t to avoid silent overflow when handling large topics.
  • Consistent State in setBufferSize():
    The buffer size and pointer are only updated after a successful allocation. If allocation fails, the previous buffer and size remain unchanged, preventing inconsistent internal state.
  • Null Buffer Guards in connect() and disconnect(): Both methods now check for a valid buffer pointer before use, preventing possible null pointer dereference if buffer allocation failed at construction.
  • Type Safety in subscribeImpl() and unsubscribeImpl(): Internal length variables now use size_t instead of uint16_t to avoid narrowing/truncation when handling large topics.

Rationale

These changes address potential edge cases that could lead to:

  • Buffer overflows or underflows
  • Crashes due to null pointer dereference
  • Silent data corruption with large topics or payloads
  • Inconsistent internal state after memory allocation failures

All fixes are implemented with minimal code changes and maintain full backward compatibility.

return false;
}
size_t payloadLen = length - payloadOffset; // safe: payloadOffset <= length guaranteed above
uint8_t* payload = _buffer + payloadOffset;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm a bit hard to see by just browsing the code in the browser on GH, but shouldn't here also be some check against _bufferSize

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, I forgot some checks

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, not at that location handlePacket() can't be called without readPacket(). But I would suggest checking in loop(), with that nothing will ever be read.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I proposed a solution, could you check if it is ok for you?

Copy link
Owner

@hmueller01 hmueller01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A first glimps over the changes ... lots of good stuff, but still a few changes.

…ing buffer safety checks and memory management improvements
@MarcAntoineCRUE
Copy link
Author

@hmueller01 I think I finished the modification and ready for your final review if I'm right

@github-actions
Copy link

Memory usage change @ 2e44c48

Board flash % RAM for global variables %
arduino:avr:uno 🔺 +296 - +430 +0.92 - +1.33 0 - 0 0.0 - 0.0
arduino:megaavr:uno2018 🔺 +124 - +254 +0.25 - +0.52 0 - 0 0.0 - 0.0
arduino:samd:mkr1000 🔺 +72 - +72 +0.03 - +0.03 0 - 0 0.0 - 0.0
esp32:esp32:esp32 🔺 +52 - +60 0.0 - 0.0 0 - 0 0.0 - 0.0
esp8266:esp8266:generic N/A N/A N/A N/A
Click for full report table
Board examples/mqtt_auth
flash
% examples/mqtt_auth
RAM for global variables
% examples/mqtt_basic
flash
% examples/mqtt_basic
RAM for global variables
% examples/mqtt_progmem
flash
% examples/mqtt_progmem
RAM for global variables
% examples/mqtt_publish_in_callback
flash
% examples/mqtt_publish_in_callback
RAM for global variables
% examples/mqtt_reconnect_nonblocking
flash
% examples/mqtt_reconnect_nonblocking
RAM for global variables
% examples/mqtt_stream
flash
% examples/mqtt_stream
RAM for global variables
% examples/mqtt_esp
flash
% examples/mqtt_esp
RAM for global variables
% examples/mqtt_esp_large_message
flash
% examples/mqtt_esp_large_message
RAM for global variables
%
arduino:avr:uno 296 0.92 0 0.0 430 1.33 0 0.0 426 1.32 0 0.0 296 0.92 0 0.0 386 1.2 0 0.0 304 0.94 0 0.0
arduino:megaavr:uno2018 130 0.27 0 0.0 124 0.25 0 0.0 254 0.52 0 0.0 130 0.27 0 0.0 128 0.26 0 0.0 130 0.27 0 0.0
arduino:samd:mkr1000 72 0.03 0 0.0 72 0.03 0 0.0 72 0.03 0 0.0 72 0.03 0 0.0 72 0.03 0 0.0 72 0.03 0 0.0
esp32:esp32:esp32 52 0.0 0 0.0 52 0.0 0 0.0 52 0.0 0 0.0 52 0.0 0 0.0 52 0.0 0 0.0 56 0.0 0 0.0 60 0.0 0 0.0
esp8266:esp8266:generic N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A
Click for full report CSV
Board,examples/mqtt_auth<br>flash,%,examples/mqtt_auth<br>RAM for global variables,%,examples/mqtt_basic<br>flash,%,examples/mqtt_basic<br>RAM for global variables,%,examples/mqtt_progmem<br>flash,%,examples/mqtt_progmem<br>RAM for global variables,%,examples/mqtt_publish_in_callback<br>flash,%,examples/mqtt_publish_in_callback<br>RAM for global variables,%,examples/mqtt_reconnect_nonblocking<br>flash,%,examples/mqtt_reconnect_nonblocking<br>RAM for global variables,%,examples/mqtt_stream<br>flash,%,examples/mqtt_stream<br>RAM for global variables,%,examples/mqtt_esp<br>flash,%,examples/mqtt_esp<br>RAM for global variables,%,examples/mqtt_esp_large_message<br>flash,%,examples/mqtt_esp_large_message<br>RAM for global variables,%
arduino:avr:uno,296,0.92,0,0.0,430,1.33,0,0.0,426,1.32,0,0.0,296,0.92,0,0.0,386,1.2,0,0.0,304,0.94,0,0.0
arduino:megaavr:uno2018,130,0.27,0,0.0,124,0.25,0,0.0,254,0.52,0,0.0,130,0.27,0,0.0,128,0.26,0,0.0,130,0.27,0,0.0
arduino:samd:mkr1000,72,0.03,0,0.0,72,0.03,0,0.0,72,0.03,0,0.0,72,0.03,0,0.0,72,0.03,0,0.0,72,0.03,0,0.0
esp32:esp32:esp32,52,0.0,0,0.0,52,0.0,0,0.0,52,0.0,0,0.0,52,0.0,0,0.0,52,0.0,0,0.0,,,,,56,0.0,0,0.0,60,0.0,0,0.0
esp8266:esp8266:generic,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A

Copy link
Owner

@hmueller01 hmueller01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I proposed a solution, could you check if it is ok for you?

The change looks good for me!

@github-actions
Copy link

Memory usage change @ 433923c

Board flash % RAM for global variables %
arduino:avr:uno 🔺 +336 - +534 +1.04 - +1.66 0 - 0 0.0 - 0.0
arduino:megaavr:uno2018 🔺 +164 - +362 +0.34 - +0.74 0 - 0 0.0 - 0.0
arduino:samd:mkr1000 🔺 +96 - +96 +0.04 - +0.04 0 - 0 0.0 - 0.0
esp32:esp32:esp32 🔺 +80 - +92 +0.01 - +0.01 0 - 0 0.0 - 0.0
esp8266:esp8266:generic N/A N/A N/A N/A
Click for full report table
Board examples/mqtt_auth
flash
% examples/mqtt_auth
RAM for global variables
% examples/mqtt_basic
flash
% examples/mqtt_basic
RAM for global variables
% examples/mqtt_progmem
flash
% examples/mqtt_progmem
RAM for global variables
% examples/mqtt_publish_in_callback
flash
% examples/mqtt_publish_in_callback
RAM for global variables
% examples/mqtt_reconnect_nonblocking
flash
% examples/mqtt_reconnect_nonblocking
RAM for global variables
% examples/mqtt_stream
flash
% examples/mqtt_stream
RAM for global variables
% examples/mqtt_esp
flash
% examples/mqtt_esp
RAM for global variables
% examples/mqtt_esp_large_message
flash
% examples/mqtt_esp_large_message
RAM for global variables
%
arduino:avr:uno 336 1.04 0 0.0 470 1.46 0 0.0 534 1.66 0 0.0 336 1.04 0 0.0 426 1.32 0 0.0 344 1.07 0 0.0
arduino:megaavr:uno2018 170 0.35 0 0.0 164 0.34 0 0.0 362 0.74 0 0.0 170 0.35 0 0.0 168 0.35 0 0.0 170 0.35 0 0.0
arduino:samd:mkr1000 96 0.04 0 0.0 96 0.04 0 0.0 96 0.04 0 0.0 96 0.04 0 0.0 96 0.04 0 0.0 96 0.04 0 0.0
esp32:esp32:esp32 80 0.01 0 0.0 80 0.01 0 0.0 80 0.01 0 0.0 80 0.01 0 0.0 80 0.01 0 0.0 84 0.01 0 0.0 92 0.01 0 0.0
esp8266:esp8266:generic N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A
Click for full report CSV
Board,examples/mqtt_auth<br>flash,%,examples/mqtt_auth<br>RAM for global variables,%,examples/mqtt_basic<br>flash,%,examples/mqtt_basic<br>RAM for global variables,%,examples/mqtt_progmem<br>flash,%,examples/mqtt_progmem<br>RAM for global variables,%,examples/mqtt_publish_in_callback<br>flash,%,examples/mqtt_publish_in_callback<br>RAM for global variables,%,examples/mqtt_reconnect_nonblocking<br>flash,%,examples/mqtt_reconnect_nonblocking<br>RAM for global variables,%,examples/mqtt_stream<br>flash,%,examples/mqtt_stream<br>RAM for global variables,%,examples/mqtt_esp<br>flash,%,examples/mqtt_esp<br>RAM for global variables,%,examples/mqtt_esp_large_message<br>flash,%,examples/mqtt_esp_large_message<br>RAM for global variables,%
arduino:avr:uno,336,1.04,0,0.0,470,1.46,0,0.0,534,1.66,0,0.0,336,1.04,0,0.0,426,1.32,0,0.0,344,1.07,0,0.0
arduino:megaavr:uno2018,170,0.35,0,0.0,164,0.34,0,0.0,362,0.74,0,0.0,170,0.35,0,0.0,168,0.35,0,0.0,170,0.35,0,0.0
arduino:samd:mkr1000,96,0.04,0,0.0,96,0.04,0,0.0,96,0.04,0,0.0,96,0.04,0,0.0,96,0.04,0,0.0,96,0.04,0,0.0
esp32:esp32:esp32,80,0.01,0,0.0,80,0.01,0,0.0,80,0.01,0,0.0,80,0.01,0,0.0,80,0.01,0,0.0,,,,,84,0.01,0,0.0,92,0.01,0,0.0
esp8266:esp8266:generic,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A

} else {
return false;
}
newBuffer = (uint8_t*)realloc(_buffer, size);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not sure, if the original buffer is still intact, if realloc fails. So I read the man ...

If realloc() fails the original block is left untouched; it is not freed or moved.

@hmueller01
Copy link
Owner

@MarcAntoineCRUE Somehow endPublish() fails now ...

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.

3 participants