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_buffer
is defined aschar[OUT_BUFFER_MAX_SIZE]
, 0-initialized.original_message
ischar[2*OUT_BUFFER_MAX_SIZE]
, , 0-initialized as well.- By design,
original_message
is either empty (original_message_size
is then 0) or not , then hasoriginal_message_size
18 characters. OUT_BUFFER_MAX_SIZE
is 16original_message_size
isuint8_t
Let's do the review:
- If
cpp original_message
is not empty, thenoriginal_message_size - 2
is equal toOUT_BUFFER_MAX_SIZE
and we we fill network_buffer, skipping its first 2 characters. - If
original_message
is empty, thenoriginal_message_size - 2
underflows asoriginal_message_size
is unsigned and unsigned arithmetic underflow is defined, such thatMIN(original_message_size - 2, OUT_BUFFER_MAX_SIZE)
is 16, and we fill 160
intonetwork_buffer
in 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.