Pull to refresh

Comments 26

Мне кажется, у Вас в конце ошибка.

if __name__ == '__main__':
    _test()
Блеск! Гонять тесты вычисления случайных чисел при импорте — это сильно.

Вычисления случайных чисел гоняются не при импорте, а когда интерпретатор запускается, как python random.py (или, если не ошибаюсь, python -m random будет работать в случае модуля, который не в cwd, а где-то в PYTHONPATH). Строка

if __name__ == "__main__":

говорит именно об этом. Никто и не хочет запускать тесты при импорте =)
Тьфу, точно! Настоящий виновник — строка
_inst = Random()
которую я убрал в комментарий. Обновляю статью. Спасибо, все интереснее оказалось.
Блеск! Гонять тесты вычисления случайных чисел при импорте — это сильно. Самое главное, надёжно. Вдруг /dev/urandom, шутник, будет возвращать не случайные числа — тесты не пройдут! Остается заметить, что import random делают Tornado, Twisted, uuid, и целая куча других библиотек, стандартных и не очень.

Тест будет выполнен не при иморте, а при выполнении этого модуля т.е. python random.py.
Теперь Вы тоже будете обновлять комментарии перед отправкой =)
С другой стороны: первые два комментатора прочитали пост до конца, что радует :)
… Или прочитали только начало и конец =D
Да, я уже исправил пост с указанием истинной причины. Посыпаю голову пеплом :)
Вдруг /dev/urandom, шутник, будет возвращать не случайные числа

А вдруг будет?
Помню на БСД 4.3 версии была такая проблема, линканул /dev/null перепутав местами что с куда.
В итоге везде где лился /dev/null файлы росли — /dev/null использовался в софте и софт залил кучу файлов смесью почтовых данных и мусора.
Как известно, первое устройство «медленное» и блокирующее, а второе «быстрое», и вопреки распространенному мнению, оба они криптостойкие источники (псевдо-)случайных чисел (http://www.2uo.de/myths-about-urandom/).


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

Вопрос — есть ли где-то перевод? Захотелось вот перевести и запостить на Хабр. Есть ли смысл?
Не знаю есть ли перевод, но он будет точно полезным!
Google по запросу «site:habrahabr.ru мифы urandom» не находит.

Ок, займусь в ближайшее время, наверное.
Я узнал у автора оригинала — он против перевода, но не против переосмысления на собственный лад с отсылкой к нему как к источнику.
Если в одном процессе файл будет закрыт, то он будет автоматически закрыт и в другом.
Это заявление не соответствует действительности. Закрытие дескриптора в одном процесе не приводит к закрытию дескрипторов в другом. Собственно, при использовании связки fork+exec необходимо перед exec специально пробегаются по всем открытым дескрипторам (кроме stdin, stdout и stderr), закрывая их. Так же рекомендую посмотреть схему с перенаправлением вывода с pipe+fork+exec.
Собсно вот. Можете запустить и проверить, никаких проблем в сишном коде с дескриптором, закрытым в другом процессе нет:
#include <stdio.h>
#include <unistd.h>

int main (int argc, char**argv)
{
        FILE* handle = fopen("/tmp/test.txt", "w");
        if(fork()==0)
        {
                sleep(2);
                fprintf(handle, "test\n");
                fclose(handle);
                printf("2 finished\n");
        }
        else
        {
                fclose(handle);
                printf("1 finished\n");
        }
        return 0;
}

Описываемый вами баг вызван тем, что где-то в недрах питона какой-то криворукий кодер решил в дочернем процессе сразу после вызова fork() принудительно закрывать вообще все файловые дескрипторы без разбору, что можно делать только в одном случае — перед вызовом exec, потому что иначе нельзя гарантировать, что закрытые дескрипторы никому не нужны. Так что проблема эта не в модуле random, а в том, как используется fork.
Спасибо! Я ошибся и стал грешить на fork, в то время как суть была не в нём, а в демонизации. Во всех учебниках пишут, что нужно в демоне закрывать дескрипторы. Даже PEP это утверждает (и делает, как выяснилось).

Проблема, всё же, в urandom, собсно сами разрабы об этом писали. Два раза)
Проблема всё же в том, что вместо использования функции daemon из libc (которая делает всё, что нужно для демонизации, а именно fork, setsid и закрытие stdin,stdout и stderr) начали городить свою кривую реализацию.
Всё правильно, перед exec-ом всё закрывают в целях безопасности, чтоб дескрипторы не утекали. PEP 446.
И тут чья-то светлая голова предлагает ускорить этот код. Как это возможно, спросите вы. Закешировав файловый объект, отвечает светлая голова.

В современных ОС открытие файла является относительно дорогой операцией. В Linux может быть не такой дорогой, как в Windows, но довольно дорогой. Переоткрытие файла — зло.

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

Во первых, системный вызов read() на закрытом дескрипторе не может вызвать падение процесса. Он вернет EBADF, и это правильно. Падать может библиотека буфферизированого чтения.
Проверять или не проверять работоспособность дескриптора перед каждым чтением — вопрос идеологии. Можно тихонько исправить (переоткрыть), но оставив источник проблемы, либо не проверять и упасть пораньше, но дав возможность обнаружить что где-то что-то сделано неправильно.

Во вторых, если не использовать библиотеку буфферизированного чтения, то стоит буфферизировать его (чтение из urandom) самостоятельно. Каждый раз при запросе нескольких случайных байт делать syscall read() — это много лишней работы. И в обоих случаях нужно учитывать, что после форка обязательно нужно сбросить буффер, хотя бы для одного из процессов (логично делать это для child). Получается, при каждом вызове os.urandom нужно проверить свой PID, и если обнаружится, что имел место fork() — переоткрыть дескриптор (автоматически сбросив буффер). Ни в одном из вариантов реализации, описанных в статье, я этого не вижу.
Не пойдет ли буферизация во вред безопасности? Забуферизированный мегабайт может месяцами висеть в памяти.
Обычно используют куда меньше размеры, от нескольких килобайт до нескольких десятков килобайт. Надо быть ну очень крутым хакером-криптографом, чтобы не только получить доступ к этой памяти, но извлечь какую-то пользу из этого.
Зато использование буферизации сильно ускорит интенсивное получение случайных чисел мелкими порциями, по сравнению с отсутствием оной.
Можно тихонько исправить (переоткрыть)
Нельзя. Вообще нельзя использовать дескриптор после закрытия. С тем же номером может быть открыт уже другой файл. Надо чинить fork.
Под «можно» имелось ввиду что есть такой вариант развития событий, а не то что так разрешается делать.
Согласен, что вмешиваться в закешированные файловый дескрипторы это лишнее (в данном случае закрывать их), результат может быть непредсказуем.

К счастью, люди смогли убедить царя в обратном, и, наконец, в июле кеширование /dev/urandom убрали — прошло более полугода. Обращаю внимание на то, как это сделали: в коде нет ни ссылки на номер бага, ни указания на причины патча, ни, в конце концов, просто поясняющего комментария. Работает, и хорошо.


??? Смотрим вашу же ссылку: hg.python.org/cpython/rev/c5888413412b

commit message:
bug #1177468: don't cache /dev/urandom file descriptor in os.urandom [#1177468]


/Misc/NEWS:
Bug #1177468: Don't cache the /dev/urandom file descriptor for os.urandom,
as this can cause problems with apps closing all file descriptors.
выходит Half-Life 2 CPython 2.4, добавляя такие привычные всем фичи как декораторы функций, множества (set), обратный порядок обхода (reversed) и list comprehensions, которые по ссылке названы generator expressions.

list comprehensions и generator expressions — разные вещи. Первые создают в памяти список с данными, вторые возвращают функцию-генератор для вычисления элементов.
Sign up to leave a comment.

Articles