Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Абьюзим random_bytes(). Фёдор Кулаков, разработ...

Абьюзим random_bytes(). Фёдор Кулаков, разработчик Lamoda Tech

Доклад с митапа Пыхап от канала Пых и Lamoda Tech

Наш unit-тест, проверяющий random_int(0, 999) !== random_int(0, 999), упал три раза за неделю. Вероятность такого события? Да, крайне мала. Но в чём же причина? Поговорим о CSPRNG в современных версиях PHP, вспомним, как работает random_int. Разберёмся, почему arc4random_buf() не виноват в ваших падающих тестах (и как это доказать). В конце раскроем имя подозреваемого и очень удивимся.

Avatar for Lamoda Tech

Lamoda Tech

June 24, 2025
Tweet

More Decks by Lamoda Tech

Other Decks in Technology

Transcript

  1. Как две строки unit-тестов заставили меня читать исходники ядра php*

    * и почему arc4random_buf() не виноват, что тесты падают
  2. 6

  3. И так трижды за неделю Шанс уронить тест три раза

    подряд - один на миллиард. Хотя бы трижды на 50 запусков - один на 50 000 7 P(K ≥ 3) = 1 − P(K = 0) − P(K = 1) − P(K = 2) ≈ 0.00002
  4. PHP < 5.3 – rand() и mt_rand(). rand() - стандартная

    реализация LCG из stdlib.h. Вызвав srand(42), можно задать собственный seed. Xn+1 = (a * Xn + с) (modm) 19
  5. mt_rand() 1. Инициализация состояния 624 чисел из seed 2. Берем

    одно из 624 чисел, сдвигаем счетчик 22
  6. mt_rand() 1. Инициализация состояния 624 чисел из seed 2. Берем

    одно из 624 чисел, сдвигаем счетчик 3. Если числа закончились, перемешиваем - Twist 23
  7. mt_rand() 1. Инициализация состояния 624 чисел из seed 2. Берем

    одно из 624 чисел, сдвигаем счетчик 3. Если числа закончились, перемешиваем - Twist 4. Финальная “закалка” выбранного числа 24
  8. mt_rand() 1. Инициализация состояния 624 чисел из seed 2. Берем

    одно из 624 чисел, сдвигаем счетчик 3. Если числа закончились, перемешиваем - Twist 4. Финальная “закалка” выбранного числа 5. Выдаем результат 25
  9. mt_rand() 1. Инициализация состояния 624 чисел из seed 2. Берем

    одно из 624 чисел, сдвигаем счетчик 3. Если числа закончились, перемешиваем - Twist 4. Финальная “закалка” выбранного числа 5. Выдаем результат 6. Повторяемся через каждые 2^19937−1 ≈ 10^6001 вызовов 26
  10. 10^6001 - это много? 1. 10^16 - число муравьев на

    планете 2. 10^87 - число элементарных частиц в наблюдаемой вселенной 29
  11. 10^6001 - это много? 1. 10^16 - число муравьев на

    планете 2. 10^87 - число элементарных частиц в наблюдаемой вселенной 3. 10^100 - гугол 30
  12. 10^6001 - это много? 1. 10^16 - число муравьев на

    планете 2. 10^87 - число элементарных частиц в наблюдаемой вселенной 3. 10^100 - гугол 4. 10^6001 31
  13. 34 PHP 5.6 - random_bytes(). Туда же random_int() в php

    7.0. CSPRNG, подходит для любых задач.
  14. 38 PHP 8.0+ – Randomizer Engine\Xoshiro256StarStar - XOR, shift, rotate.

    Engine\PcgOneseq128XslRr64 - PCG Family Engine\Mt19937 - для тех, кто скучает по mt_rand()
  15. 39 PHP 8.0+ – Randomizer Engine\Xoshiro256StarStar - XOR, shift, rotate.

    Engine\PcgOneseq128XslRr64 - PCG Family Engine\Mt19937 - для тех, кто скучает по mt_rand() Engine\Secure - CSPRNG
  16. Что делает random_int()? 1. Сбор энтропии 2. Агрегация в пул

    энтропии – /dev/random, /dev/urandom. 3. ←—————— ВЫ НАХОДИТЕСЬ ЗДЕСЬ 43
  17. Что делает random_int()? 1. Сбор энтропии 2. Агрегация в пул

    энтропии – /dev/random, /dev/urandom. 3. ←—————— ВЫ НАХОДИТЕСЬ ЗДЕСЬ 4. Вызывает getrandom(), забирает N байт энтропии 44
  18. Что делает random_int()? 1. Сбор энтропии 2. Агрегация в пул

    энтропии – /dev/random, /dev/urandom. 3. ←—————— ВЫ НАХОДИТЕСЬ ЗДЕСЬ 4. Вызывает getrandom(), забирает N байт энтропии 5. Сбрасывает состояние 45
  19. Что делает random_int()? 1. Сбор энтропии 2. Агрегация в пул

    энтропии – /dev/random, /dev/urandom. 3. ←—————— ВЫ НАХОДИТЕСЬ ЗДЕСЬ 4. Вызывает getrandom(), забирает N байт энтропии 5. Сбрасывает состояние 6. Преобразовывает байты к целому числу 46
  20. 48 Почему random_int()? Нет алгоритма как такового на стороне PHP.

    Случайность берется из операционной системы, а там в свою очередь - из реального мира.
  21. Отключаем на хосте 54 ASLR - address space layout randomization.

    echo "kernel.randomize_va_space = 0" > /etc/sysctl.d/01-disable-aslr.conf
  22. Отключаем на хосте (естественно, в VM докера, через orb shell):

    55 ASLR - address space layout randomization. echo "kernel.randomize_va_space = 0" > /etc/sysctl.d/01-disable-aslr.conf
  23. 60

  24. 61

  25. 62

  26. 63

  27. 64

  28. 65

  29. 66 Частота выпадения чисел от 0 до 999 (100 000

    000 итераций) PHP 8.2, LD_PRELOAD 100M
  30. 67 P(K ≥ 3) = 1 − P(K = 0)

    − P(K = 1) − P(K = 2) ≈ 0.0005, 1 из 2000
  31. 68

  32. 1. Тесты разбиты на группы 2. Flaky-тесты есть у всех

    3. Упавшие тесты запускаем снова 69
  33. 70