Integers footgun
Recently, a colleague of mine proposed the following change in our code base
Change:
    memcpy(network_buffer,
           original_message,
           MIN(original_message_size, OUT_BUFFER_MAX_SIZE));
To
    memcpy(network_buffer,
           &original_message[2],
           MIN(original_message_size - 2, OUT_BUFFER_MAX_SIZE));
The original intend was to skip the first two bytes (if any) because we didn't need them.
To have the full picture,
- network_bufferis defined as- char[OUT_BUFFER_MAX_SIZE], 0-initialized.
- original_messageis- char[2*OUT_BUFFER_MAX_SIZE], , 0-initialized as well.
- By design, original_messageis either empty (original_message_sizeis then 0) or not , then hasoriginal_message_size18 characters.
- OUT_BUFFER_MAX_SIZEis 16
- original_message_sizeis- uint8_t
Let's do the review:
- If cpp original_messageis not empty, thenoriginal_message_size - 2is equal toOUT_BUFFER_MAX_SIZEand we we fill network_buffer, skipping its first 2 characters.
- If original_messageis empty, thenoriginal_message_size - 2underflows asoriginal_message_sizeis unsigned and unsigned arithmetic underflow is defined, such thatMIN(original_message_size - 2, OUT_BUFFER_MAX_SIZE)is 16, and we fill 160intonetwork_bufferin an array already filled with 0.
LGTM, right ?
And of course, it's not. Because original_message_size is a uint8_t, integer promotion kicks in.
Integer types smaller than int are promoted when an operation is performed on them, either in a int if int is big enough to cover all values from the original type,
or unsigned int otherwise. You can see at work on C++ insight
#include <cstdio>
#include <cstdint>
int main()
{
   uint8_t x = 0;
   auto y = x - 2;
   unsigned int u = 0;
   auto v = u - 2;
}
So in our case,  original_message_size is promoted to an int, such that is original_message_size - 2 is -2 when original_message is empty.
And from there, memory havoc follows, likely resulting in a segfault.