flawopen.com/Безопасность памяти/strcpy vs strncpy
Безопаснее ли strncpy(), чем strcpy()?
Поле фиксированной ширины похоже на ряд ячеек: последняя должна содержать маркер конца, если код будет считать байты C-строкой. strncpy() записывает не более n байт и предотвращает выход за буфер только если n не больше его реального размера. Если источник заполняет лимит, нулевой терминатор не добавляется. Следующая строковая функция может читать за пределами назначения в поисках отсутствующего маркера.
char dst[16]; /* 1. Unbounded — classic overflow */ strcpy(dst, src); /* 2. Bounded, but may not terminate. If strlen(src) >= 16, dst has no '\0' and every later strlen/printf reads out of bounds. */ strncpy(dst, src, sizeof(dst)); /* 3. Off-by-one: room for 16 chars plus a terminator = 17 bytes, but dst only holds 16. */ strncpy(dst, src, sizeof(dst)); dst[sizeof(dst)] = '\0'; /* OOB! */
char dst[16]; /* Preferred: snprintf always null-terminates (when size > 0), and tells you about truncation. */ int n = snprintf(dst, sizeof(dst), "%s", src); if (n < 0 || (size_t)n >= sizeof(dst)) { /* truncated — handle it */ return ERR_TOO_LONG; } /* Or strlcpy where available — always terminates, returns the length it tried to create. */ if (strlcpy(dst, src, sizeof(dst)) >= sizeof(dst)) { return ERR_TOO_LONG; }
Для чего изначально предназначалась strncpy
Это поведение не является дефектом: strncpy() предназначена для полей фиксированной ширины с заполнением нулями, а не для безопасного копирования C-строк в общем случае. Если исходная строка короче n, функция дополняет назначение нулевыми байтами до n - 1. Если среди первых n байт источника нет нуля, копируются ровно n байт, а назначение не завершается. Это подходит некоторым фиксированным полям, но опасно для API, ожидающих строку с нулевым терминатором.
Если источник короче n, дополнение записывает всё поле целиком. Для большого буфера и короткой строки это может быть менее эффективно, чем копирование до терминатора.
Всегда проверяйте усечение
Предотвратить переполнение — только половина задачи. Тихое усечение тоже опасно: укороченный hostname, путь или значение авторизации может изменить смысл. snprintf возвращает длину, которую хотела бы записать; сравните её с размером буфера. Неверное прочтение этого значения как числа записанных байтов имело значение и в случае Citrix Bleed.
Частые вопросы
Они завершают строку и проверяют границы, но их переносимость неодинакова: хорошая поддержка в Windows и неполная — в других системах. Для нескольких платформ snprintf обычно доступнее.
Нет. Она появилась в OpenBSD и доступна в BSD и macOS; её также добавили в новые версии glibc. Проверьте поддержку на целевых системах.
Предпочитайте std::string и std::string_view, которые управляют длиной. Функции C используйте только на границе API, требующей сырой буфер.