Про разницу между strlcat и strncat
Пока идёт активная подготовка больших статей про проверку кода операционной системы Haiku OS, я хочу привести один пример популярной ошибки с функцией strncat из этого проекта. Освежить знания по этой теме будет полезно для всех C и C++ разработчиков.

Описание функций
Функция strncat служит для конкатенации строк и имеет следующую сигнатуру:
char *strncat(char *dest, const char *src, size_t n);
Она добавляет не более n символов из строки src к строке dest, при этом строка src может не заканчиваться терминальным нулём. В строке dest должно быть достаточно места, иначе поведение программы становится непредсказуемым из-за переполнения буфера, т.к. функция не производит контроля границ.
Функция strlcat служит для конкатенации строк и делает это более безопасно, чем функция strncat. strlcat имеет следующую сигнатуру:
size_t strlcat(char *dst, const char *src, size_t size)
В отличие от других функций, она принимает полный размер буфера и гарантирует наличие терминального символа у результата. Для корректной работы функции strlcat, ей необходимо передавать только нультерминальные строки.
Ошибка в Haiku OS
V645 The 'strncat' function call could lead to the 'output' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. NamespaceDump.cpp 101
static void
dump_acpi_namespace(acpi_ns_device_info *device, char *root, int indenting)
{
char result[255];
char output[320];
char tabs[255] = "";
char hid[16] = "";
int i;
size_t written = 0;
for (i = 0; i < indenting; i++)
strlcat(tabs, "| ", sizeof(tabs));
strlcat(tabs, "|--- ", sizeof(tabs));
....
void *counter = NULL;
while (....) {
uint32 type = device->acpi->get_object_type(result);
snprintf(output, sizeof(output), "%s%s", tabs, result + depth);
switch(type) {
case ACPI_TYPE_INTEGER:
strncat(output, " INTEGER", sizeof(output));
break;
case ACPI_TYPE_STRING:
strncat(output, " STRING", sizeof(output));
break;
case ACPI_TYPE_BUFFER:
strncat(output, " BUFFER", sizeof(output));
break;
case ACPI_TYPE_PACKAGE:
strncat(output, " PACKAGE", sizeof(output));
break;
....
case ACPI_TYPE_MUTEX:
strncat(output, " MUTEX", sizeof(output));
break;
case ACPI_TYPE_REGION:
strncat(output, " REGION", sizeof(output));
break;
case ACPI_TYPE_POWER:
strncat(output, " POWER", sizeof(output));
break;
case ACPI_TYPE_PROCESSOR:
strncat(output, " PROCESSOR", sizeof(output));
break;
case ACPI_TYPE_THERMAL:
strncat(output, " THERMAL", sizeof(output));
break;
case ACPI_TYPE_BUFFER_FIELD:
strncat(output, " BUFFER_FIELD", sizeof(output));
break;
case ACPI_TYPE_ANY:
default:
break;
}
....
}
....
}
Анализатор обнаружил смешанный код, состоящий из вызовов функций strlcat и strncat. Причём, вызовы функции strlcat являются корректными:
char tabs[255] = "";
....
strlcat(tabs, "|--- ", sizeof(tabs));
в них передаётся нультерминальная строка и размер всего буфера.
В то же время, многочисленные вызовы strncat в цикле являются ошибочными и могут привести к ошибке:
char output[320];
....
strncat(output, " INTEGER", sizeof(output));
Программа долгое время может работать стабильно, если на вход функции поступают только короткие строки, но в цикле лимит буфера можно быстро исчерпать.
Заключение
Полезные ссылки по теме:
P.S.
Не дожидаясь публикации основных больших статей , мы отправили отчёт разработчикам Haiku OS, и они уже приступили к исправлению ошибок: https://git.haiku-os.org/haiku/log/?qt=grep&q=pvs
0