Хак компилятора Кена Томпсона все еще является угрозой?

156

Кен Томпсон Хак (1984)

Кен Томпсон описал метод повреждения двоичного файла компилятора (и другого скомпилированного программного обеспечения, такого как сценарий входа в систему * nix) в 1984 году. Мне было любопытно узнать, устраняет ли современная компиляция этот недостаток безопасности или нет.

Краткое описание:

Переписать код компилятора, чтобы он содержал 2 ошибки:

  • При компиляции своего двоичного файла компилятор должен компилировать эти недостатки
  • При компиляции какого-либо другого предварительно выбранного кода (функция входа в систему) он должен скомпилировать какой-нибудь произвольный бэкдор

Таким образом, компилятор работает нормально - когда он компилирует скрипт входа в систему или аналогичный, он может создать бэкдор безопасности, и когда он компилирует более новые версии себя в будущем, он сохраняет предыдущие недостатки - и недостатки будут существовать только в компиляторе двоичный файл, поэтому его крайне сложно обнаружить.

Вопросов:

Я не смог найти ответы на эти вопросы в Интернете:

  • Какое отношение это имеет к своевременной компиляции?
  • Скомпилированы ли функции, подобные программе, обрабатывающей логины в системе * nix, при их запуске?
  • Является ли это все еще действительной угрозой или с 1984 года произошли изменения в безопасности компиляции, которые мешают этому стать серьезной проблемой?
  • Это влияет на все языки?

Почему я хочу знать?

Я сталкивался с этим, выполняя домашнюю работу, и это показалось мне интересным, но у меня недостаточно опыта, чтобы конкретным образом понять, является ли это текущей проблемой или решенной.

Справочный материал

svick
источник
6
Стратегия Diverse Double Compiling - это достаточно надежный способ обнаружения присутствия встроенного компилятора RoTT.
dmckee
3
Я полагаю, что АНБ вложило много усилий в такую ​​атаку.
Пол М

Ответы:

110

Этот взлом должен быть понят в контексте. Он был опубликован в то время и в той культуре, где Unix, работавший на различном оборудовании, был доминирующей системой.

Что сделало нападение так страшно было , что C компилятор центральной части программного обеспечения для этих систем. Почти все в системе прошло через компилятор, когда он был впервые установлен (двоичные дистрибутивы были редки из-за разнородного оборудования). Все собирали вещи все время. Люди регулярно проверяли исходный код (им часто приходилось вносить коррективы, чтобы заставить его компилироваться вообще), поэтому использование закулисного входа компилятором казалось своего рода «совершенным преступлением», когда вас не поймали.

В настоящее время аппаратное обеспечение гораздо более совместимо, и поэтому компиляторы играют гораздо меньшую роль в повседневной работе системы. Скомпрометированный компилятор больше не самый страшный сценарий - руткиты и скомпрометированный BIOS еще сложнее обнаружить и избавиться от них.

Майкл Боргвардт
источник
27
Или, поскольку большинство людей ничего не компилируют из исходных текстов (скажем, в Windows), ваш средний троян будет достаточен :) (Я согласен, что скомпрометированный компилятор слишком избыточен)
Andres F.
16
@ArjunShankar: несвободный проприетарный двоичный компилятор не нуждается и не может иметь этот бэкдор. Этот бэкдор применяется только к компиляторам, которые вы сами компилируете из исходного кода.
Руах
12
За исключением десктопа, Unix и все его варианты по-прежнему являются доминирующей операционной системой.
Роб
7
@ruakh: может быть, я не понимаю вашего акцента на «это», но я не согласен. Если этот бэкдор был внедрен в компании, которая владеет несвободным проприетарным компилятором и использует этот компилятор для компиляции новых версий того же компилятора, этот бэкдор будет иметь гораздо худшее влияние, чем в исходном сценарии. Вам понадобится только один вектор атаки, чтобы заразить всех.
orithena
8
Представьте, что кто-то скомпрометировал сервер сборки Ubuntu и заменил компилятор, не меняя исходный код. Это может занять некоторое время, чтобы выяснить это, и к тому времени образы ubuntu будут распространяться среди людей со встроенным в них скомпрометированным компилятором (вместе с скомпрометированными сборками входа в систему или чем-то еще). Я думаю, что это все еще совершенно актуальная проблема.
Джимми Хоффа
74

Целью этого выступления не было подчеркнуть уязвимость, которую необходимо устранить, или даже предложить теоретическую уязвимость, о которой нам необходимо знать.

Цель состояла в том, чтобы, когда речь заходит о безопасности, нам не хотелось бы никому доверять, но, к сожалению, это невозможно. Вы всегда должны доверять кому-то (отсюда и название: «Размышления о доверии»)


Даже если вы параноик, который шифрует свой настольный жесткий диск и отказывается запускать любое программное обеспечение, которое вы сами не компилировали, вам все равно нужно доверять своей операционной системе. И даже если вы сами компилируете операционную систему, вам все равно нужно доверять используемому вами компилятору. И даже если вы компилируете свой собственный компилятор, вы все равно должны доверять этому компилятору! И это даже не говоря о производителях оборудования!

Вы просто не можете сойти с рук, не доверяя никому . Это точка, которую он пытался донести.

BlueRaja - Дэнни Пфлугхофт
источник
2
Если у кого-то есть компилятор с открытым исходным кодом, чье поведение не зависит от какого-либо поведения, определенного или не определенного, компилирует его с помощью различных независимо разработанных компиляторов (доверенных или нет), а затем компилирует одну программу, используя все различные скомпилированные версии что с открытым исходным кодом, каждый компилятор должен производить точно такой же вывод. Если они это сделают, это предполагает, что единственный способ, которым троян мог бы быть в одном, был бы, если бы это было идентично во всех. Это кажется маловероятным. Одна из моих мозолей с большей частью .net, тем не менее, ...
суперкат
9
@supercat: Кажется, вы упускаете суть. Вы говорите, что взломанный Кеном Томпсоном способ обойти. Я говорю, что конкретный взлом, который он выбрал, не имеет значения; это был просто пример, чтобы продемонстрировать, что вы должны всегда доверять кому-то . Вот почему этот вопрос несколько бессмысленен - он полностью скучает по лесу за деревьями.
BlueRaja - Дэнни Пфлюгофт
9
@supercat: Весьма маловероятно, что разные компиляторы будут генерировать один и тот же байт-код для любой нетривиальной программы из-за различных проектных решений, оптимизаций и т. д. Это поднимает вопрос - как вы узнаете, что двоичные файлы идентичны?
Анкит Сони
1
@AnkitSoni: Мой ответ более детален. Подача надлежащим образом написанного компилятора / компоновщика с открытым исходным кодом через разные компиляторы должна давать разные исполняемые файлы, которые будут вести себя одинаково . Если исполняемые файлы на самом деле ведут себя одинаково, они будут выдавать тот же результат, если через них будет передан код для компилятора / компоновщика с открытым исходным кодом. Чтобы сравнить файлы, можно скопировать их на дискету и использовать античный компьютер для сравнения.
суперкат
2
Разве некоторые из этого разговора не будут означать, что для того, что вы тестировали, двоичные файлы / аппаратные средства вели себя как ожидалось? В нем все еще может быть что-то, что вы не проверяли и не знаете .
Барт Сильверстрим
53

нет

Атака, как первоначально описано, никогда не была угрозой. Хотя компилятор теоретически мог бы сделать это, для того, чтобы осуществить атаку, потребовалось бы запрограммировать компилятор на

  • Распознавать, когда компилируемый исходный код имеет компилятор, и
  • Выясните, как изменить произвольный исходный код, чтобы вставить в него хак.

Это влечет за собой выяснение того, как компилятор работает из своего исходного кода, чтобы он мог изменить его без поломок.

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

Это высокоуровневое самонаправленное программирование, сложная проблема ИИ (в прошлом я проверял, что на современном уровне техники генерируется код, который практически определяется его типами). Посмотрите: немногие люди могут даже сделать это; Вы должны были бы изучить язык программирования и понять основы кода в первую очередь.

Даже если проблема ИИ будет решена, люди заметят, если компиляция их крошечного компилятора приведет к созданию двоичного файла с огромной связанной библиотекой ИИ.

Аналогичная атака: начальное доверие

Однако обобщение атаки актуально. Основная проблема заключается в том, что ваша цепочка доверия должна начинаться где-то, и во многих доменах ее происхождение может подорвать всю цепочку труднообнаружимым способом.

Пример, который можно легко осуществить в реальной жизни

Ваша операционная система, скажем Ubuntu Linux, обеспечивает безопасность (целостность) обновлений, сверяя загруженные пакеты обновлений с ключом подписи репозитория (используя криптографию с открытым ключом). Но это гарантирует подлинность обновлений только в том случае, если вы можете доказать, что ключ подписи принадлежит законному источнику.

Где вы взяли ключ подписи? Когда вы впервые загрузили дистрибутив операционной системы.

Вы должны верить, что источник вашей цепочки доверия, этот ключ подписи, не является злом.

Любой, кто может MiTM подключения к Интернету между вами и загрузкой Ubuntu сервером-это может быть ваш ISP, правительство , которое контролирует доступ в Интернете (например , Китай), или хостинг в Ubuntu провайдер может угнал этот процесс:

  • Определите, что вы загружаете образ компакт-диска Ubuntu. Это просто: посмотрите, что запрос идет к любому из (общедоступных) зеркал Ubuntu и запрашивает имя файла образа ISO.
  • Обслуживайте запрос со своего собственного сервера, предоставляя вам образ компакт-диска, содержащий открытый ключ злоумышленника и местоположение хранилища, а не Ubuntu.

Отныне вы будете получать свои обновления надежно с сервера злоумышленника. Обновления запускаются с правами root, поэтому злоумышленник имеет полный контроль.

Вы можете предотвратить атаку, убедившись в подлинности оригинала. Но для этого необходимо проверить загруженный образ компакт-диска с помощью хэша (на самом деле это делают немногие ), а сам хеш должен быть загружен безопасно, например, по протоколу HTTPS. И если ваш злоумышленник может добавить сертификат на ваш компьютер (обычно в корпоративной среде) или контролирует центр сертификации (например, Китай), даже HTTPS не обеспечивает защиту.

Механическая улитка
источник
47
Это неверно Компилятор должен только определить, когда он компилирует очень специфический исходный файл из своего собственного исходного кода с очень конкретным содержимым, а не когда он компилирует какой-либо компилятор вообще !!!
Каз
14
@Kaz - В какой-то момент изменения надстройки компилятора или программы входа в систему могут привести к тому, что они победят компилятор-распознаватель бэкдора / регистратор-логин, и последующие итерации потеряют бэкдор. Это аналогично случайной биологической мутации, дающей иммунитет к определенным заболеваниям.
Рассел Борогове
12
В первой половине вашего ответа есть проблема, которую описывает Каз, но вторая половина настолько хороша, что я все равно +1!
Руах
7
Злой компилятор, который распознает только свой собственный исходный код, легко построить, но на практике он относительно бесполезен - немногие, у кого уже есть двоичный код этого компилятора, будут использовать его для воссоздания указанного двоичного файла. Чтобы атака была успешной в течение более длительного периода времени, компилятору потребовалось бы больше интеллекта, чтобы исправлять новые версии своего собственного источника, таким образом сталкиваясь с проблемами, описанными в snswer.
user281377
5
Распознаватель для конкретного компилятора может быть довольно общим и вряд ли сломается перед лицом новой версии. Возьмем, к примеру, gcc - многие строки кода в gcc очень старые и не сильно изменились. Простые вещи, такие как имя, почти никогда не меняются. До того, как распознавание пойдет не так, скорее всего, введенный код сделает это. И на самом деле обе эти проблемы в значительной степени теоретические - на практике у автора вредоносного программного обеспечения не возникнет проблем с тем, чтобы идти в ногу с (медленными) темпами разработки компиляторов.
Имон Нербонн
25

Во-первых, моя любимая статья об этом хаке называется Strange Loops .

Этот конкретный взлом может, безусловно, (*) быть осуществлен сегодня в любом из основных проектов ОС с открытым исходным кодом, в частности, в Linux, * BSD и т.п. Я ожидаю, что это будет работать почти одинаково. Например, вы загружаете копию FreeBSD с эксплуатируемым компилятором для изменения openssh. С тех пор каждый раз, когда вы обновляете openssh или компилятор по источнику, вы продолжаете проблему. Предполагая, что злоумышленник воспользовался системой, использовавшейся для упаковки FreeBSD, в первую очередь (вероятно, поскольку само изображение повреждено или злоумышленник фактически является упаковщиком), то каждый раз, когда система перестраивает двоичные файлы FreeBSD, он будет снова сталкиваться с проблемой. Есть много способов, чтобы эта атака провалилась, но они принципиально не отличаются от того, как атака Кена могла быть неудачной (**). Мир действительно не сильно изменился.

Конечно, аналогичные атаки могут быть столь же легко (или более легко) внедрены их владельцами в системы, такие как Java, iOS SDK, Windows или любая другая система. Определенные виды недостатков безопасности могут быть даже встроены в аппаратное обеспечение (особенно ослабление генерации случайных чисел).

(*) Но под «конечно» я подразумеваю «в принципе». Стоит ли ожидать, что такая дыра существует в какой-то конкретной системе? Нет. Я бы посчитал это маловероятным по разным практическим причинам. Со временем, по мере того, как код меняется и изменяется, вероятность того, что этот вид взлома вызовет странные ошибки, возрастает. И это повышает вероятность того, что это будет обнаружено. Менее изобретательные бэкдоры потребовали бы заговоров для поддержания. Конечно, мы точно знаем, что бэкдоры с «законным перехватом» были установлены в различных телекоммуникационных и сетевых системах, поэтому во многих случаях подобный сложный хак не нужен. Взлом установлен открыто.

Так что всегда глубоко в обороне.

(**) Предполагая, что атака Кена вообще когда-либо существовала. Он просто обсудил, как это можно сделать. Он не сказал, что на самом деле сделал это, насколько я знаю.

Роб Нейпир
источник
Относительно вашей второй сноски Кен сказал: «Построить, а не распространять».
8bittree
15

Это влияет на все языки?

Эта атака в основном затрагивает языки, которые являются хостингом. Это языки, на которых компилятор написан на самом языке. C, Squeak Smalltalk и интерпретатор PyPy Python будут затронуты этим. Perl, JavaScript и интерпретатор CPython Python не будут.

Какое отношение это имеет к своевременной компиляции?

Не очень много. Это самодостаточный характер компилятора, который позволяет хаку быть скрытым. Я не знаю ни одного JIT-компилятора. (Может быть LLVM?)

Скомпилированы ли функции, подобные программе, обрабатывающей логины в системе * nix, при их запуске?

Как правило, не. Но вопрос не в том, когда он компилируется, а в том , каким компилятором . Если программа входа в систему скомпилирована испорченным компилятором, она будет испорчена. Если он скомпилирован чистым компилятором, он будет чистым.

Является ли это все еще действительной угрозой или с 1984 года произошли изменения в безопасности компиляции, которые мешают этому стать серьезной проблемой?

Это все еще теоретическая угроза, но не очень вероятно.

Одна вещь, которую вы могли бы сделать, чтобы смягчить это, это использовать несколько компиляторов. Например, компилятор LLVM, который сам скомпилирован GCC, не пройдет через черный ход. Точно так же GCC, скомпилированный LLVM, не пройдет через черный ход. Так что, если вы беспокоитесь о такого рода атаках, то вы можете скомпилировать свой компилятор с другим типом компилятора. Это означает, что злой хакер (у вашего поставщика ОС?) Должен будет испортить оба компилятора, чтобы узнать друг друга; Гораздо более сложная проблема.

Шон Макмиллан
источник
Ваш последний абзац, строго говоря, не соответствует действительности. Теоретически, код может обнаружить компилируемый компилятор и соответствующим образом вывести черный ход. Это, конечно, нецелесообразно в реальном мире, но ничто не мешает этому. Но тогда первоначальная идея была не о реальных практических угрозах, а скорее об уроке доверия.
Стивен Бернап
Честная оценка. В конце концов, хакер несет бэкдор для входа в систему и мод для компилятора, так что он может нести мод для другого компилятора. Но это становится все менее вероятным.
Шон Макмиллан
Как раз вовремя сборник может быть удовольствием. Если какой-то код имеет некоторую уязвимость только тогда, когда конкретный фрагмент JIT-компилируется, он может остаться незамеченным. (просто чистая речь)
GameDeveloper
12

Для этого есть теоретический шанс. Однако существует способ проверить, не был ли скомпрометирован конкретный компилятор (с доступным исходным кодом), посредством двойной компиляции Дэвида А. Уилера .

По сути, используйте как предполагаемый компилятор, так и другой независимо разработанный компилятор для компиляции источника подозрительного компилятора. Это дает SC SC и SC T . Теперь скомпилируйте подозрительный источник, используя оба этих двоичных файла. Если получающиеся двоичные файлы идентичны (за исключением множества вещей, которые могут вполне законно меняться, например, разные метки времени), подозрительный компилятор фактически не злоупотребляет доверием.

Vatine
источник
Либо этот, либо доверенный компилятор не так надежен, как думал пользователь. Но для двух независимых реализаций языка вероятность того, что они содержат один и тот же бэкдор, незначительна.
Дамиан Йеррик
Или же был скомпрометирован инструмент сравнения, который вы используете для их сравнения;)
iCodeSometime
@kennycoc Тем не менее, написание инструмента сравнения «эти два файла одинаково» не так уж и сложно (поскольку, учитывая ссылку на системный вызов, это должно быть выполнимо через 2-16 часов в двоичном машинном коде).
Ватин
3

Как специфическая атака, это такая же угроза, какой она была когда-либо, и которая почти не представляет угрозы.

Какое отношение это имеет к своевременной компиляции?

Не уверен, что вы подразумеваете под этим. Является ли JITter невосприимчивым к этому? Нет. Это более уязвимо? На самом деле, нет. Как разработчик, ВАШЕ приложение более уязвимо просто потому, что вы не можете подтвердить, что оно не было сделано. Обратите внимание, что ваше еще не разработанное приложение в основном защищено от этого и от всех практических вариантов, вам нужно беспокоиться только о компиляторе, который новее вашего кода.

Скомпилированы ли функции, подобные программе, обрабатывающей логины в системе * nix, при их запуске?

Это не совсем актуально.

Является ли это все еще действительной угрозой или с 1984 года произошли изменения в безопасности компиляции, которые мешают этому стать серьезной проблемой?

Там нет реальной безопасности компиляции, и не может быть. Это был действительно смысл его разговора, что в какой-то момент вам нужно кому-то доверять.

Это влияет на все языки?

Да. По сути, в какой-то момент ваши инструкции должны быть превращены во что-то, что компьютер отменяет, и этот перевод может быть выполнен неправильно.

jmoreno
источник
-2

У Дэвида Уилера есть хорошая статья: http://www.dwheeler.com/trusting-trust/

Меня больше волнуют аппаратные атаки. Я думаю, что нам нужен полностью инструментарий проектирования VLSI с исходным кодом FLOSS, который мы можем модифицировать и скомпилировать самостоятельно, который позволит нам создать микропроцессор, в который не будут вставлены бэкдоры. Инструменты должны также позволить нам понять назначение любого транзистора на чипе. Затем мы можем открыть образец готовых чипов и осмотреть их под микроскопом, убедившись, что они имеют ту же схему, что и инструменты, которые, как они сказали, должны были иметь.

Павел
источник
3
-1, большая часть вашего ответа не отвечает на вопрос.
-3

Системы, в которых конечные пользователи имеют доступ к исходному коду, являются теми, для которых вам придется скрывать этот тип атаки. Это были бы системы с открытым исходным кодом в современном мире. Проблема в том, что, хотя существует зависимость от одного компилятора для всех систем Linux, атака должна была попасть на серверы сборки для всех основных дистрибутивов Linux. Поскольку они не загружают двоичные файлы компилятора непосредственно для каждого выпуска компилятора, источник атаки должен был быть на их серверах сборки по крайней мере в одном предыдущем выпуске компилятора. Либо та, либо самая первая версия компилятора, которую они загрузили в виде двоичного файла, должны были быть скомпрометированы.

Дейл Галледж
источник
2
Ваш ответ царапает на поверхности вопроса, но на самом деле не касается того, что спрашивают.
-4

Если у кого-то есть исходный код для системы компилятора / сборки, вывод которой не должен зависеть ни от чего, кроме содержимого предоставленных исходных файлов, и если у вас есть несколько других компиляторов, и он знает, что они не все содержат одинаковый хак компилятора, можно убедитесь, что вы получаете исполняемый файл, который не зависит ни от чего, кроме исходного кода.

Предположим, что у каждого есть исходный код для пакета компилятора / компоновщика (скажем, Groucho Suite), написанного таким образом, что его вывод не будет зависеть ни от каких-либо неопределенных поведений, ни от чего-либо, кроме содержимого входных исходных файлов, и один компилирует / связывает этот код с различными независимо создаваемыми пакетами компиляторов / компоновщиков (например, Harpo Suite, Chico Suite и Zeppo Suite), получая различный набор execctable для каждого (назовите их G-Harpo, G-Chico и G-Zeppo). Для этих исполняемых файлов не должно быть неожиданным, что они содержат разные последовательности инструкций, но они должны быть функционально идентичными. Однако доказать, что они функционально идентичны во всех случаях, было бы неразрешимой проблемой.

К счастью, в таком доказательстве нет необходимости, если использовать полученные исполняемые файлы только для одной цели: снова скомпилировать пакет Groucho. Если кто-то компилирует пакет Groucho, используя G-Harpo (получая GG-Harpo), G-Chico (GG-Chico) и G-Zeppo (GG-Zeppo), то все три результирующих файла, GG-Harpo, GG-Chico и GG-Zeppo, все байты должны быть идентичны. Если файлы совпадают, это будет означать, что любой «вирус компилятора», который существует в любом из них, должен существовать одинаково во всех из них (поскольку все три файла являются побайтовыми, они не могут отличаться в любом поведении. путь).

В зависимости от возраста и происхождения других компиляторов, возможно, будет возможно гарантировать, что такой вирус не может правдоподобно существовать в них. Например, если использовать старый Macintosh для загрузки компилятора, который был написан с нуля в 2007 году, через версию MPW, написанную в 1980-х годах, компиляторы 1980-х не знали бы, куда вставить вирус в компилятор 2007 года. Возможно, сегодня компилятор может выполнить достаточно сложный анализ кода, чтобы понять это, но уровень вычислений, требуемый для такого анализа, намного превысил бы уровень вычислений, необходимых для простой компиляции кода, и не мог бы остаться незамеченным на рынке, где скорость компиляции была основным пунктом продажи.

Я бы сказал, что если кто-то работает с инструментами компиляции, где байты в исполняемом файле, который должен быть создан, не должны зависеть ни от чего, кроме содержимого представленных исходных файлов, то можно добиться достаточно хорошего иммунитета от Томпсона. Вирус К сожалению, по какой-то причине недетерминизм в компиляции представляется нормальным в некоторых средах. Я признаю, что в многопроцессорной системе компилятор может работать быстрее, если ему разрешено варьировать определенные аспекты генерации кода в зависимости от того, какой из двух потоков завершает часть работы первым.

С другой стороны, я не уверен, что вижу причину, по которой компиляторы / компоновщики не должны предоставлять режим «канонического вывода», где вывод зависит только от исходных файлов и «даты компиляции», которая может быть переопределена пользователем , Даже если компиляция кода в таком режиме занимает вдвое больше времени, чем обычная компиляция, я бы предположил, что было бы весьма полезно иметь возможность воссоздать любую «релизную сборку», байт за байтом, полностью из исходных материалов, даже если это означало, что сборка релиза займет больше времени, чем «нормальная сборка».

Supercat
источник
2
-1. Я не понимаю, как ваш ответ затрагивает основные аспекты вопроса.
@ GlenH7: Многие более старые инструменты компиляции будут последовательно выводить бит-идентичный вывод при вводе бит-идентичного ввода [вне таких вещей, как TIME , которые можно настроить, чтобы сообщить «официальное» время компиляции]. Используя такие инструменты, можно довольно хорошо защитить от вирусов компилятора. Тот факт, что некоторые популярные среды разработки не позволяют «детерминистически» компилировать код, означает, что методы, которые могли бы защитить от вирусов в старых инструментах, нельзя эффективно использовать с более новыми.
суперкат
Вы пробовали это? 1. Ведите свою диссертацию. 2. Используйте более короткие абзацы. 3. Проясните разницу между «функционально идентичными» (результат первого этапа) и «битовыми идентификаторами» (результат второго), возможно, со списком всех созданных двоичных файлов компилятора и их отношений друг с другом. 4. Цитировать статью Дэвида А. Уилера о DDC.
Дамиан Йеррик