В конце прошлого года в вычислительном центре Университета Юты при очередной уборке нашли то, что уже многие считали безвозвратно утраченным: архивную магнитную ленту с исходным кодом 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». Это момент, когда операционная система и язык программирования слились в одну концепцию, определившую развитие индустрии. А обнаруженная в нём «реликтовая» уязвимость напоминает, что привычка экономить на проверках и надёжности всегда оборачивается проблемами — вопрос только в том, когда и с какими последствиями.



