Unixv4: как найденный архив раскрыл рождение ядра unix на c и уязвимость su

В конце прошлого года в вычислительном центре Университета Юты при очередной уборке нашли то, что уже многие считали безвозвратно утраченным: архивную магнитную ленту с исходным кодом UNIX версии 4 (UNIXv4). Это одна из ключевых вех в истории операционных систем — именно в этой версии ядро Unix окончательно было переписано на язык C и перестало быть чисто ассемблерным.

Как развивался ранний Unix: где тут v3 и где «впервые C»

Путаница вокруг того, в какой версии Unix впервые появился язык C, понятна: упоминаются и UNIXv3, и UNIXv4, и оба связаны с C.

Хронология была такой:

- ранние версии Unix для PDP-11 имели ядро, в основном написанное на ассемблере;
- в UNIXv3 начали использовать C в системе: на этом языке появились утилиты и части инфраструктуры, а также был внедрён важнейший механизм — неименованные каналы (pipe);
- в UNIXv4 разработчики пошли дальше: ядро, ранее написанное на ассемблере, было переписано на C почти целиком.

То есть:

- впервые язык C серьёзно вошёл в Unix в версии v3 — в пользовательских программах и окружении;
- впервые ядро Unix было реализовано на C именно в версии v4.

Ядро на C в начале 1970‑х — это радикальный шаг. Тогда считалось, что «настоящие» операционные системы пишутся на ассемблере ради максимальной производительности и контроля над железом. Команда Bell Labs рискнула и выбрала более высокий уровень абстракции — в обмен на переносимость и ускорение разработки.

Кто и как написал UNIXv4

UNIXv4 была создана в 1973 году для мини-ЭВМ PDP‑11/45. Ключевые фигуры:

- Кен Томпсон — переписал ядро Unix с ассемблера на C;
- Деннис Ритчи — автор языка C, отвечал за драйверы устройств и системные компоненты.

Именно связка «Unix + C» позже стала фундаментом для целого семейства систем: от коммерческих Unix до BSD и, в перспективе, до современных Unix-подобных ОС.

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

Как удалось прочитать архив с ленты

Просто найти магнитную ленту — полдела. Куда сложнее — извлечь с неё данные спустя десятилетия.

В конце декабря 2025 года сотрудники музея истории вычислительной техники смогли:

1. подобрать и восстановить подходящее оборудование для чтения старых лент;
2. оцифровать аналоговый сигнал с помощью специализированного инструментария readtape;
3. декодировать формат архива, используя наработки, которые ранее применялись при восстановлении исторического кода — в частности, старых версий программ и редакторов тех лет, таких как ранний Emacs Джеймса Гослинга.

После успешного чтения содержимое архива привели в порядок: структурировали исходники, добавили недостающие метаданные, подготовили сборочные сценарии. В результате код UNIXv4 сейчас существует в форме, пригодной для компиляции и запуска в эмуляторе PDP‑11.

Что именно нашли на ленте

Архив оказался значительно богаче, чем ожидалось. Помимо исходников самой ОС UNIXv4, на ленте обнаружили:

- один из ранних компиляторов языка C;
- интерпретатор языка SNOBOL;
- вспомогательные файлы и утилиты эпохи PDP‑11.

Объём исходного кода UNIXv4 впечатляет для начала 1970‑х:

- всего около 61 тысячи строк;
- из них примерно 27 тысяч строк на C;
- около 33 тысяч строк — ассемблер для PDP‑11;
- около тысячи строк — заголовочные файлы и прочая инфраструктура.

То есть переход на C был серьёзным, но ассемблер по‑прежнему активно использовался там, где требовался максимальный контроль над железом.

Историческая уязвимость в утилите su

Особый интерес вызвала классическая утилита su, входившая в состав UNIXv4. Её задача была привычной: позволить пользователю получить права суперпользователя (root) при вводе правильного пароля.

Характеристики той реализации:

- размер кода — менее 50 строк;
- бинарник устанавливался с флагом setuid-root;
- при успешной аутентификации запускался `/bin/sh` с правами root.

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

Сегодня это считается классической и давно описанной ошибкой, но в начале 1970‑х такой код не воспринимался как нечто из ряда вон выходящее.

Как тогда относились к переполнениям буфера

Ситуацию прокомментировал 93‑летний Дуглас Макилрой — один из ключевых участников ранней команды Unix в Bell Labs. Это тот самый человек, который:

- предложил концепцию неименованных каналов (pipe);
- создал целый ряд классических утилит: echo, spell, diff, sort, join, tr.

Его взгляд на проблему переполнения буфера в те годы показателен. До появления червя Морриса в 1988 году:

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

Логика была примерно такой: пользователь сидит за терминалом, вводит команду вручную; если он введёт аномально длинную строку, это воспринималось как некорректное поведение, последствия которого не смертельны — программа упадёт, и ладно.

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

Почему уязвимости долго не считались опасными

Важно понимать контекст:

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

В такой обстановке:

- основной проблемой считалась стабильность и функциональность, а не безопасность;
- приоритет был у производительности и простоты реализации;
- защита от злоумышленников часто сводилась к организационным мерам: физический доступ, учётные записи, политика использования.

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

Моррисов червь и переосмысление безопасности

Поворотным моментом стал червь Морриса в 1988 году. Он распространился по сетям Unix‑машин, используя в том числе переполнения буфера и некорректную обработку строк, и вывел из строя значительную часть подключённых систем.

Именно после этого инцидента:

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

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

Что изменилось с тех пор

С одной стороны, многое действительно осталось прежним: язык C по‑прежнему позволяет легко допустить переполнение буфера, а подобные ошибки регулярно обнаруживаются в современном ПО.

С другой — изменился сам подход к качеству и безопасности:

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

Кроме того:

- пользователи и компании чувствительны к утечкам данных и взломам;
- разработчики и компании несут репутационные и финансовые риски;
- регуляторы во многих странах требуют соблюдения норм безопасности.

То, что в 1973‑м считалось допустимой оптимизацией — «сэкономим пару строк, всё равно это ввод вручную» — сейчас уже воспринимается как грубейшее нарушение базовой гигиены кода.

Почему восстановление UNIXv4 важно сегодня

Обнаружение и оцифровка кода UNIXv4 важны не только как милый исторический эпизод.

Это даёт:

1. Живой учебный материал. Можно увидеть, как реально писали операционные системы на заре Unix‑эпохи: структуру ядра, взаимодействие с железом, стили кода Томпсона и Ритчи.
2. Понимание эволюции языка C. Ранний код показывает, как использовали C до стандартизации, какие приёмы тогда считались нормой.
3. Исторический контекст безопасности. Уязвимость в su — пример того, как изменилось мышление: от «программа просто падает» до «это критический вектор атаки».
4. Возможность воспроизведения среды. Запуск UNIXv4 в эмуляторе PDP‑11 позволяет испытать систему такой, какой её видели первые пользователи Unix.

Почему переход ядра на C был революцией

Переписывание ядра с ассемблера на C в UNIXv4 было не просто технической заменой синтаксиса.

Это дало:

- переносимость. Ассемблер привязывает код к конкретному процессору; C позволил перенести Unix на другие архитектуры без переписывания всего ядра;
- ускорение разработки. Высокоуровневый язык упрощает сопровождение, экспериментирование, рефакторинг;
- формирование «культуры Unix». Ядро на C, утилиты на C, компилятор C — всё это сформировало целостную экосистему, где один и тот же язык использовался повсюду.

Сегодня это кажется очевидным, но тогда это было рискованным и смелым решением, заложившим основу для доминирования Unix‑подобных систем на десятилетия вперёд.

Чему может научить нас старый код

История с восстановлением UNIXv4 и обнаружением уязвимости в su даёт несколько уроков:

- ошибки бессмертны. Переполнения буфера из 1970‑х в том же виде встречаются в свежем коде, если не выстраивать процессы контроля качества;
- контекст важен. То, что когда‑то казалось разумным компромиссом, в новых условиях становится недопустимой практикой;
- инструменты меняются, но ответственность остаётся. Тогда не было статического анализа, фреймворков безопасности и целых курсов по secure coding, но сегодня оправдать подобный код уже нельзя;
- изучение истории — это инвестиция в будущее. Понимая, как возникали и развивались фундаментальные технологии, проще оценивать последствия архитектурных решений сейчас.

UNIXv4 — это не просто «старый код на C для PDP‑11». Это момент, когда операционная система и язык программирования слились в одну концепцию, определившую развитие индустрии. А обнаруженная в нём «реликтовая» уязвимость напоминает, что привычка экономить на проверках и надёжности всегда оборачивается проблемами — вопрос только в том, когда и с какими последствиями.

Прокрутить вверх