Из ядра Linux выпилили strncpy: шесть лет, 362 коммита, одна функция

Из ядра Linux выпилили strncpy: шесть лет, 362 коммита, одна функция

19 июня Торвальдс влил merge, который вычистил из ядра Linux функцию strncpy . Шесть лет работы, 362 коммита, семьдесят человек. Ради одной функции из стандартной библиотеки C.

Звучит как анекдот про бюрократию. На деле это наглядный разбор того, почему «просто заменить небезопасную функцию» в C — совсем не «просто», даже когда принципиального спора о замене нет.

Почему её считают «безопасной»

Из-за буквы n . strcpy копирует до нуля и легко улетает за буфер, а strncpy(dst, src, n) берёт максимум n байт — выглядит как та же функция, но с ремнём безопасности. Её и в самом ядре нашли в сотнях мест; что уж говорить про прикладной код.

Вера ложная. man про strncpy пишет прямым текстом: функция создаёт «null-padded character sequence, not a string ». Это не про строки вообще. Появилась она в Edition 7 AT&T Unix около 1979 года — под имена файлов в directory entries, где поле фиксированной ширины и могло содержать ноль, один или несколько хвостовых нулей. Такие fixed-width поля живы и сейчас, но это редкий частный случай — а strncpy сорок лет тащат в обычный код как «strcpy, который не стреляет в ногу».

Стреляет, просто тихо. Если strlen(src) >= n , то strncpy запишет ровно n байт и не поставит завершающий \0 . Дальше любой strlen , strcmp или printk("%s", …) уходит читать за буфер: мусор, чужая память, в плохой день — утечка данных. А если наоборот, strlen(src) < n , остаток буфера до n забивается нулями: скопировали четыре байта в буфер на четыре килобайта — получите 4092 лишних записи на ровном месте. В документации ядра API так и назвали — ambiguous и fragile: по одному вызову не понять, чего хотел автор. Строку с нулём? Padding? Поле фиксированной ширины?

Где зарыты шесть лет

Замена — это не «найти и поменять на безопасный аналог». Безопасного аналога нет. Под разные намерения — свой набор: strscpy() для нормальной C-строки с \0 , strscpy_pad() — то же с обнулением хвоста, strtomem() / strtomem_pad() для полей фиксированной ширины без терминатора, memcpy_and_pad() для bounded-копии из источника, который может быть не завершён нулём, memtostr() / memtostr_pad() для обратного случая, ну и обычный memcpy() , если это вообще не строка. Восемь функций там, где раньше была одна.

И sed -i 's/strncpy/strscpy/g' тут не работает в принципе, потому что верный ответ зависит от намерения автора:

Один и тот же вызов, разный верный ответ. И так 362 раза: каждый надо открыть и прочитать глазами — это строка или бинарное поле, нужен ли ноль, нужен ли padding, завершён ли источник, известен ли размер приёмника на компиляции.

Вот почему тянулось с 2019-го. Не потому что был спор — затея шла под крылом Kernel Self-Protection Project Киса Кука, который выпиливает не отдельные баги, а целые классы уязвимостей. А потому что 362 раза требовалось ручное решение, и одной автоправкой его не выразить. Больше всех разгрёб Джастин Ститт — 211 коммитов из этих 362.

В вашей C/C++ базе strncpy скорее всего живёт прямо сейчас, и кто-то когда-то вписал её именно как «безопасный вариант». Это не баг в трекере — это код, который надо перечитать. И если соберётесь чистить, главная работа будет не в замене функции, а в том, чтобы понять, что в каждом буфере вообще лежит. Ядру на это хватило шести лет и семидесяти человек — на API, который десятилетиями выглядел как безопасная версия strcpy .

← Cybersecurity