Пока идёт активная подготовка больших статей про проверку кода операционной системы 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, ей необходимо передавать только нультерминальные строки.
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));
Программа долгое время может работать стабильно, если на вход функции поступают только короткие строки, но в цикле лимит буфера можно быстро исчерпать.
Полезные ссылки по теме:
Не дожидаясь публикации основных больших статей , мы отправили отчёт разработчикам Haiku OS, и они уже приступили к исправлению ошибок: https://git.haiku-os.org/haiku/log/?qt=grep&q=pvs