[php-src] Issue #21731: ext/random: Random\Engine\Xoshiro256StarStar::__unserialize() accepts all-zero state

[email protected] (iliaal)
Newsgroups php.bugs
Message-ID <[email protected]>
Issue: https://github.com/php/php-src/issues/21731
Author: iliaal

## Description

`Random\Engine\Xoshiro256StarStar::__construct()` rejects a seed that would leave the internal state all zero, because xoshiro256** with zero state produces `0` on every call forever:

~~~c
if (UNEXPECTED(t[0] == 0 && t[1] == 0 && t[2] == 0 && t[3] == 0)) {
    zend_argument_value_error(1, "must not consist entirely of NUL bytes");
    RETURN_THROWS();
}
~~~

`__unserialize()` doesn't check the same invariant. If a caller feeds this payload through `__unserialize()`, the engine lands in the forbidden state, and everything downstream returns zero: `generate()`, `Randomizer::getInt()`, `Randomizer::getBytes()`, `Randomizer::nextFloat()`, and every other method that consumes the engine.

## Reproduction

~~~php
<?php

// Constructor guard works as intended:
try {
    new Random\Engine\Xoshiro256StarStar(str_repeat("\0", 32));
} catch (\ValueError $e) {
    echo $e->getMessage(), "\n";
    // Random\Engine\Xoshiro256StarStar::__construct():
    // Argument #1 ($seed) must not consist entirely of NUL bytes
}

// __unserialize() bypasses it:
$engine = new Random\Engine\Xoshiro256StarStar(42);
$engine->__unserialize([
    [],
    ['0000000000000000', '0000000000000000', '0000000000000000', '0000000000000000'],
]);

$r = new Random\Randomizer($engine);
var_dump($r->getInt(1, 1_000_000));   // int(1)
var_dump(bin2hex($r->getBytes(16)));  // "00000000000000000000000000000000"
var_dump($r->nextFloat());            // float(0)
~~~

## Expected

`__unserialize()` should reject the all-zero state the same way `__construct()` does. Returning `false` from the `unserialize` callback in `engine_xoshiro256starstar.c` is enough; the Mt19937-aliased `__unserialize()` wrapper turns that into the standard "Invalid serialization data for ... object" exception.

Without the guard, a bad serialized payload (hand-crafted, or round-tripped from an attacker-controlled source) produces a Randomizer that silently returns 0 from every method.

## PHP Version

master (PHP 8.6.0-dev). The `unserialize` callback has shipped without the zero-state check since Xoshiro256StarStar landed, so 8.3, 8.4, and 8.5 are affected too.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.