Збій RAND_bytes у OpenVPN: помилка бібліотеки або відсутній вузол
Збій RAND_bytes може означати відхилене джерело ентропії або вузол, прихований в'язницею. Прочитайте чергу помилок і подання процесу, перш ніж створювати пристрої.
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'
Шукайте в цьому виводі дві окремі речі: зникнення повідомлення про випадкове джерело і досягнення тунелем звичайної позначки запуску. Генератор, що знову працює, прибирає один локальний збій і нічого не каже про ідентичність сервера, облікові дані чи маршрутизацію. Перевірте це тією самою клієнтською конфігурацією, яка раніше зазнала невдачі, щоб результат був порівнянним, а не просто кращим.