Вебинар: Каждая идиома когда-то была проблемой - 11.09
Все ускоряют разработку ПО, строя его из кирпичиков (сторонних компонентов/библиотек), но мало кто задумывается о вытекающих из этого потенциальных проблемах. Попробую повторить, почему стоит хотя бы немного думать о балансе, добавляя в проект новую зависимость. Статья является ремейком, который я решил сделать убедившись, что старая моя рекомендация прошла испытание временем.

Десять лет назад я опубликовал статью "Сопротивляйтесь добавлению в проект новых библиотек", и озвучил эту же тему в подборке из 42-х рекомендаций.
Я получил много критических комментариев касательно своей рекомендации и засомневался, что прав. Всё меняется, прогресс не стоит на месте, все собирают приложение как конструктор из деталей, развились пакетные менеджеры. Всем удобно, все довольны. Так зачем я лезу со своими сомнениями?
Поэтому в последующих материалах, например в подборке антипаттернов программистов 2023-го года, я эту тему уже не упоминал. Но ещё несколько лет наблюдений и моя позиция вернулась к прежней — по возможности следует избегать внедрять новые библиотеки, получая всё новые зависимости.
Я был прав в рекомендациях, хотя выбрал не лучший способ изложения и примеры. Более того, в старых рассуждениях нет важного пункта, который проявил себя в теме разработки безопасного ПО и его сертификации. Вернее, он есть — №4, но звучит слишком вскользь. Поэтому давайте попробую ещё раз.
В начале я приведу старый, чуть сокращённый вариант текста, а затем усилю новыми аргументами и соображениями. В целом всё изложенное в силе и прошу обдумать прочитанное, прежде чем мы пойдём на второй круг споров в комментариях :)
Итак, вам понадобилось реализовать в проекте функциональность X. Теоретики разработки программного обеспечения в этот момент говорят, что для этого нужно взять уже существующую библиотеку Y и использовать её для реализации необходимых вам вещей. Собственно, это классический подход в разработке программного обеспечения — повторное использование своих или чужих наработок (сторонних библиотек). Именно этим путём движется большинство программистов.
Однако теоретики в статьях и книгах, забывают упомянуть, в какой ад превращается поддержка нескольких десятков сторонних библиотек, живущих в вашем проекте, скажем, по прошествии 10 лет.
Я рекомендую всячески сопротивляться добавлению в проект каждой новой библиотеки. Прошу понять меня правильно — я вовсе не говорю, что не надо использовать библиотеки и писать всё самостоятельно. Это просто-напросто глупо. Дело в том, что часто новая библиотека добавляется в проект по прихоти одного разработчика с целью использовать в ней какую-то маленькую "фитюльку". Добавить новую библиотеку несложно, вот только потом всей команде много лет придётся нести груз её поддержки.
Наблюдая за развитием некоторых больших проектов, я могу перечислить ряд проблем из-за наличия большого количества сторонних библиотек. Наверное, я перечислю далеко не все проблемы, но даже следующий список должен побудить вас задуматься:
namespace, у вас начинают пересекаться имена. Это приводит к ошибкам компиляции или к скрытым ошибкам. Например, начинает использоваться константа не из того enum, из которого вы планировали.Ещё раз подчеркну: я не призываю вас отказаться от использования сторонних библиотек. Если в программе вам понадобилось работать с изображениями в формате PNG, то вам надо взять библиотеку LibPNG и не изобретать велосипед.
Но даже работая с PNG, надо остановиться и подумать. А нужна ли библиотека? Какие операции нужно выполнять с изображениями? Быть может, если вся задача сводится к тому, чтобы сохранить какое-то изображение в *.png-файл, можно обойтись системными функциями. Например, если у вас Windows приложение, то вам поможет WIC. А если вы уже используете библиотеку MFC, то вообще не надо усложнять код, ведь есть класс CImagе (см. обсуждение на сайте Stack Overflow). Минус одна библиотека — отлично!
Сопротивляйтесь добавлению в проект новых библиотек. Добавлять следует только тогда, когда очевидно, что без библиотеки не обойтись.
Вот некоторые возможные манёвры:
P.S. Многим рассказанное здесь придётся не по душе. Например, то, что я рекомендую использовать не переносимую универсальную библиотеку, а допустим WinAPI. На это будут возражения, основанные на том, что тем самым мы привязываем проект к одной операционной системе. И потом будет очень сложно сделать программу переносимой. Я с этим не согласен. Часто идея "потом перенесём на другую операционную систему" живёт только в голове разработчика. На самом деле такая задача вообще может быть никогда не поставлена руководством. Или проект "загнётся" из-за излишней сложности и универсальности, ещё до момента популярности и необходимости портирования. Плюс не забывайте пункт (8) в списке проблем, приведённый выше.
Всё сказанное верно и сейчас, хотя акценты сместились. Сейчас на передний план выходит необходимость контроля слишком большой кодовой базы при построении процессов безопасной разработки. Пункт №4 обрёл особую актуальность.
Когда вы добавляете в проект библиотеку, возникают транзитивных зависимости. Это значит, что библиотека использует другие библиотеки, а те в свою очередь ещё библиотеки и так далее. Запросто бывает, что вам в проекте требуется 10 библиотек, а по факту их используется уже 100 штук. В каких-то языках, как С++, это выражено слабо, а при разработке на JavaScript, Java, Python, наоборот, быстро возникают огромные цепочки зависимостей.
Если в одной из этих библиотек обнаружится уязвимость, значит, потенциально уязвимо и ваше приложение. Пользователю всё равно, уязвимо ваше приложение из-за вашего собственного кода или стороннего.
Конечно, уязвимость в какой-то библиотеке не означает автоматически, что уязвимо и ваше приложение. Опасные функции могут просто никогда не вызываться. Или вызываться, но работать с такими данными, которые не приведут к проблемам. Это значит, что уязвимость не лежит на поверхности атаки.
Однако от этого не сильно легче. Ведь нужно быть уверенным, что уязвимостью воспользоваться нельзя. На практике это сделать непросто.
В любом случае, если вы заботитесь о репутации своего проекта, вам надо отслеживать информацию о новых уязвимостях во всех компонентах, которые используются напрямую или косвенно. Для этого существуют инструменты композиционного анализа (SCA).
Если есть инструменты композиционного анализа, то в чём проблема с большим количеством зависимостей? Чем их больше, тем больше шанс что кто-то найдёт новое слабое место и воспользуется им.
Это ещё не всё. Причиной уязвимости может быть не только случайный баг, но и умышленное действие. Всё более популярными становятся атаки на цепочки поставок. Злоумышленники различными способами пытаются внедрить недекларированные возможности в различные открытые библиотеки. Например, они какое-то время вносят полезные правки и доработки, зарабатывают положительную репутацию, а затем вносят незаметную закладку в код. Через транзитивные зависимости они могут воспользоваться ей сразу в большом количестве приложений. Соответственно, чем больше сторонних библиотек вы используете, тем выше шанс атаки на вас через цепочку поставок.
Появилась и ещё одна разновидность такой атаки, связанная с созданием кода с помощью генеративного ИИ (GenAI). Решая задачу, ИИ иногда галлюцинирует в названиях требуемых пакетов. Подмечено, что есть определённые паттерны в галлюцинациях, и этим пользуются. Создаются и публикуются заражённые пакеты, с названиями, которые с наибольшей вероятностью могут быть выдуманы GenAI. Звучит надуманно, но это работает и такие атаки уже успешно применялись. Они получили название Slopsquatting [смотри также 1, 2].
Следующая тема — статический анализ кода. С точки зрения безопасности, вы должны проверять не только свой, но и весь сторонний код. Этим мало кто занимается, так как все обычно думают про анализ только своего кода. А кто задумывается — сталкивается с огромным объёмом работ.
Однако если вы захотите провести сертификационные испытания своего проекта, то нет разницы, код написан вами или взят извне. Раз вы используете сторонний код, значит, теперь это ваш код, и вы несёте за него ответственность. Это логично — нет разницы, кто автор кода, из-за которого приложение окажется уязвимым.
На практике одной компании может быть непосильно провести статический анализ всего заимствованного кода. Отсюда возникают проекты доверенных репозиториев и консорциумы компаний для совместного анализа заимствованных компонентов. Можно идти путём анализа исходных модулей, составляющих поверхность атаки. Здесь опять всплывает задача определения этой поверхности.
Не буду здесь углубляться. Но тем, кому будет суждено погрузится в мир ГОСТ Р 56939—2024, ГОСТ Р 71207—2024, методического документа ВУ и НДВ, сертификационных испытаний и т.д., прочувствуют какую стоимость им предстоит платить за каждую когда-то добавленную в проект зависимость.
Неспроста в ГОСТ Р 71207—2024 есть даже вот такой пункт 8.3:
Следует использовать такой статический анализатор (набор анализаторов), чтобы на технических средствах разработчика ПО для поиска критических ошибок обеспечивался полный анализ ПО с используемыми заимствованными компонентами за время, не превышающее двое суток.
Страшно про двое суток? :) Правильно, что страшно. Могу только успокоить: если что — анализатор PVS-Studio укладывается в нормативы. Но стоит ли до такого доводить? И это ведь только анализ, а разбирать критические ошибки? Это не разовая история про внедрение анализатора, а регулярная в силу обновления библиотек.
Конечно, подход активного добавления в проект сторонних компонентов не повернуть вспять. Я к этому и не призываю, это непродуктивно. Однако я хочу, чтобы вы всё-таки знали о потенциальных проблемах, с которыми можете столкнуться.
Думаю, у вас есть процесс обзоров кода. Предлагаю вам добавить к нему процесс контроля (обсуждения) всех добавляемых библиотек. Пусть разработчик обоснует, что ему действительно нужна очередная библиотека. Возможно, зная про это на этапе разработке, он выберет другой путь решения задачи или использует функциональность другой, уже включённой в проект библиотеки. В общем, попробуйте сопротивляться добавлению в проект новых библиотек. Вдруг иногда у вас будет получаться :) Спасибо потом мне скажете.
В идеале, изредка полезно проводить аудит: используются ли все связанные с проектом библиотеки. Я видел, как при портировании большого C++ приложения с Win32 на Win64, выяснилось, что около 10 библиотек вообще никак не используются. При этом многие годы тратились ресурсы на их "содержание" в проекте. Провести такой аудит может быть непросто, но возможно, у вас получится его организовать.
0