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 aschar[OUT_BUFFER_MAX_SIZE], 0-initialized.original_messageischar[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 16original_message_sizeisuint8_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.