Ground Embedded KO Subscribe

Notes / C Language

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.

GROUND EMBEDDED · NOTEC Language 05Arrays and strings 1volatile uint32_t *ODR 2 = (uint32_t*)0x40020014; 3*ODR |= (1u << 5); 4while (1) { 5 if (flag) toggle(); 6}

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

name[0]'A' 0x41name[1]'B' 0x42name[2]'C' 0x43name[3]'D' 0x44name[4]'\0' 0x00end marker
char name[5] = "ABCD". After the four letters, a 0 that marks the end takes one slot. With five letters there is no room for the 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 are sizeof(s) and strlen(s)?
  • In the 2D array uint8_t m[3][4], where does a write to m[1][5] land?
FREE PDFGet the 3 free ebooksIf you want to keep reading this topic as one book: BLDC motor control, serial communication and sensors at book length. PDFs are in Korean.Get them free →