Arrays and strings: how buffer overflow happens
An array is a run of same-typed values placed consecutively in memory, and a string is a char array that ends with a null character.
Arrays
uint8_t buf[8]; is eight consecutive bytes. buf[i] only means "i slots after the start address", so C does not check the range. Writing to buf[8] corrupts whatever variable sits right after the array. That is a buffer overflow.
Strings
A C string is a char array with '\0' at the end. "OK" takes three bytes.
char msg[16];
snprintf(msg, sizeof msg, "T=%d", temp); // never exceeds the size
Use strncpy and snprintf instead of functions that do not check length, such as strcpy and sprintf.
Where arrays appear in embedded code
- UART receive buffers and communication packets
- A buffer of ADC samples to average
- Lookup tables and tables of settings
Define the array size with one macro and use the same macro in the loop condition. That removes mistakes when the size changes.
In pictures: a string ends with 0
A C string does not store its length. strlen, printf("%s") and strcpy all keep reading memory until they meet a 0. Without the 0 they read or write past the array into the neighbouring variable.
What a buffer overflow looks like in practice
char cmd[8];
uint8_t motor_enable = 0;
void on_uart_line(const char *line) {
strcpy(cmd, line); // writes past cmd if line has 8 or more characters
}
If motor_enable sits right after cmd, one long string changes its value. The length of data arriving over a link cannot be trusted, so always pass the size and limit the copy.
size_t n = strnlen(line, sizeof(cmd) - 1);
memcpy(cmd, line, n);
cmd[n] = '\0'; // write the terminator yourself
Switching to safer functions
| Risky | Use instead | Watch out |
|---|---|---|
strcpy |
Measure the length, then memcpy |
Write the final 0 yourself |
sprintf |
snprintf(buf, sizeof(buf), ...) |
A return value at or above the size means it was truncated |
strncpy |
Same as above | Writes no 0 when the buffer is full |
gets |
Do not use | No length limit at all |
Despite its name, strncpy is not safe. If the source is longer than the buffer, it returns without writing a terminating 0.
An array passed to a function loses its size
void clear(uint8_t buf[]) {
memset(buf, 0, sizeof(buf)); // wrong: clears only the size of a pointer (4)
}
void clear2(uint8_t *buf, size_t len) { memset(buf, 0, len); } // pass the length too
An array received as a function argument is a pointer. sizeof returns the pointer size, not the size of the whole array. A function that takes an array should take its length as well.
Where the array is declared, get its element count like this.
#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
Check the index before writing
if (idx < ARRAY_LEN(table)) table[idx] = value;
When a value from outside, such as a channel number or sensor ID received over a link, is used as an index, always check the range. C raises no error for an out-of-range index and simply writes to that address.
Check yourself
- For
char s[] = "hello";, what aresizeof(s)andstrlen(s)? - In the 2D array
uint8_t m[3][4], where does a write tom[1][5]land?