RAND_bytes() failed — это утверждение об одном локальном примитиве, а не об учётной записи, предварительно распределённом ключе или удалённом порте. Две совершенно разные неисправности дают одну и ту же строку и оставляют разные следы. Одна — криптографический бэкенд, который отказывается выдавать байты; другая — процесс, который не достигает источника энтропии, для которого построен его код. Чтобы их различить, нужны одно чтение очереди ошибок и один взгляд на представление файловой системы процесса, заключённого в тюрьму, и эта проверка должна предшествовать всему, что создаётся в /dev.

Что различает возвращаемое значение

Документированный контракт различает три исхода, а не два: 1 при успехе, -1 когда загруженный метод RAND не поддерживает запрос, и 0 при прочих сбоях, причём объяснение остаётся в очереди ошибок. Справочник RAND_bytes добавляет пункт, который легко пропустить: генератор по умолчанию получает начальное значение из операционной системы на поддерживаемых платформах, и когда это заполнение не удаётся, он остаётся в состоянии ошибки, а не возвращает предсказуемые байты.

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

Когда скрытый узел устройства — настоящая причина

Ограниченная служба видит другое дерево, чем оболочка, в которой вы печатаете. Тюрьмы chroot, образы контейнеров и ужесточённые файлы модулей регулярно предоставляют /dev, в котором меньше узлов, чем у хоста. Если сбойная реализация открывает /dev/urandom по имени пути, этой разницы достаточно, чтобы сломать запуск, хотя сам хост выглядит совершенно здоровым. Устройство, существующее вне тюрьмы, ничего не доказывает о том, что заключённому процессу разрешено открывать.

Не всякому случайному интерфейсу нужен путь. Системный вызов getrandom в Linux не принимает ни имени файла, ни файлового дескриптора, и его страница руководства отмечает, что это свойство полезно именно там, где chroot скрывает пути к устройствам.

Итак, объяснение отсутствующего узла — гипотеза об одной ветви кода, а не правило о случайных числах вообще. Оно верно, когда доказательства называют путь, и остаётся догадкой, когда доказательства называют что-то другое.

Сначала прочитайте очередь ошибок, затем представление процесса

Самый быстрый способ решить вопрос — прочитать то, что библиотека уже записала, и затем посмотреть прямо на представление внутри тюрьмы. Начните с сборки OpenSSL, которую служба действительно загрузила, потому что стеки поставщиков различаются между мажорными версиями и между дистрибутивами. Затем спросите systemd, какой процесс работает и какие ограничения к нему применяются. Только когда оба факта на столе, стоит перечислять узлы, которые видит этот процесс.

openssl version -a
systemctl show openvpn-server@lab -p MainPID -p PrivateDevices -p DevicePolicy
pid=$(systemctl show -p MainPID --value openvpn-server@lab)
sudo ls -l /proc/"$pid"/root/dev/urandom /proc/"$pid"/root/dev/random

Выполняйте эти чтения на том же процессе, а не на оболочке, которая случайно открыта. Сравнивать /dev хоста с представлением постороннего процесса — это способ дважды починить не ту зависимость.

Добавляйте зависимость только тогда, когда можете её назвать

Когда доказательства действительно называют путь, исправление невелико: сделайте этот единственный путь доступным внутри тюрьмы и оставьте всё остальное в покое. Bind-монтирование одного узла обычно достаточно, а mknod со стандартными мажорным и минорным номерами узла даёт тот же результат в установках, где монтирование невозможно. Расширение песочницы, выдача доступа ко всем символьным устройствам или отключение PrivateDevices меняют точечное исправление на постоянную регрессию безопасности.

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

Проверяйте генератор и туннель отдельно

Перезапуск — единственная проверка, которая использует ту же конфигурацию, тот же файл модуля и то же ограничение, что привели к сбою. Выполните его, затем читайте самые свежие записи журнала, а не выборку из прежнего запуска.

sudo systemctl restart openvpn-server@lab
sudo journalctl -u openvpn-server@lab --since -5min --no-pager | grep -iE 'rand|entropy|Initialization Sequence'

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

Похожие статьи