`PHPAPI` is defined in `php.h`. It appears that without the explicit include,
recent versions of Visual Studio Code’s intellisense (rightfully) no longer
detect `PHPAPI`. This will then lead to a misparsing of the file, because
`PHPAPI` is assumed to be the return type and the actual return type is assumed
to be the function name, thus expecting a semicolon after the actual return
type. This in turn colors the entire header in red due to the detected syntax
error(s), making development very hard / impossible.
This did not cause issues outside of the IDE use case, because apparently all
users of `php_random.h` include `php.h` before including `php_random.h`.
This effectively reverts #8984.
As discussed in #10327 which will enable the use of the getrandom(2) syscall on
NetBSD instead of relying on the userland arc4random_buf(), the CSPRNG should
prioritize security over speed [1] and history has shown that userland
implementations unavoidably fall short on the security side. In fact the glibc
implementation is a thin wrapper around the syscall due to security concerns
and thus does not provide any benefit over just calling getrandom(2) ourselves.
Even without any performance optimizations the CSPRNG should be plenty fast for
the vast majority of applications, because they often only need a few bytes of
randomness to generate a session ID. If speed is desired, the OO API offers
faster, but non-cryptographically secure engines.
If, for whatever reason, the random_fd has been assigned file descriptor `0` it
previously failed to close during module shutdown, thus leaking the descriptor.
* Fix pre-PHP 8.2 compatibility for php_mt_rand_range() with MT_RAND_PHP
As some left-over comments indicated:
> Legacy mode deliberately not inside php_mt_rand_range()
> to prevent other functions being affected
The broken scaler was only used for `php_mt_rand_common()`, not
`php_mt_rand_range()`. The former is only used for `mt_rand()`, whereas the
latter is used for `array_rand()` and others.
With the refactoring for the introduction of ext/random `php_mt_rand_common()`
and `php_mt_rand_range()` were accidentally unified, thus introducing a
behavioral change that was reported in FakerPHP/Faker#528.
This commit moves the checks for `MT_RAND_PHP` from the general-purpose
`range()` function back into `php_mt_rand_common()` and also into
`Randomizer::getInt()` for drop-in compatibility with `mt_rand()`.
* [ci skip] NEWS for `MT_RAND_PHP` compatibility
* Remove superfluous helper variable in Randomizer::getBytes()
* Reduce the scope of `result` in Randomizer::getBytes()
Co-authored-by: Tim Düsterhus <tim@bastelstu.be>
This reverts commit 94ee4f9834.
The commit was a bit too late to be included in PHP 8.2 RC1. Given it's a massive ABI break, we decide to postpone the change to PHP 8.3.
* Apply `var_dump()` in 02_engine/all_serialize_error.phpt
This ensures that an undetected serialization error is clear identifiable in the output.
* random: Validate that the arrays do not contain extra elements when unserializing
A simple check for CommonCrypto/CommonRandom.h does not work on earlier macOS.
Must also pull in sys/types.h for size_t, Availability.h for __OSX_AVAILABLE_STARTING,
and CommonCrypto/CommonCryptoError.h for CCCryptorStatus.
Closes GH-9479.
* Unify structure for ext/random's engine tests (2)
This makes adjustments that were missed in
2d6a883b3a.
* Add `engines.inc` for ext/random tests
* Unify structure for ext/random's randomizer tests
* Emit deprecation warnings when adding dynamic properties to classes during unserialization - this will become an Error in php 9.0.
(Adding dynamic properties in other contexts was already a deprecation warning - the use case of unserialization was overlooked)
* Throw an error when attempting to add a dynamic property to a `readonly` class when unserializing
* Add new serialization methods `__serialize`/`__unserialize` for SplFixedArray to avoid creating deprecated dynamic
properties that would then be added to the backing fixed-size array
* Don't add named dynamic/declared properties (e.g. $obj->foo) of SplFixedArray to the backing array when unserializing
* Update tests to declare properties or to expect the deprecation warning
* Add news entry
Co-authored-by: Tyson Andre <tysonandre775@hotmail.com>
This fixes an incompatibility when wrapping native 32-bit engines with a userland
engine. The latter always used the 64-bit range function which then used two
32-bit numbers from the underlying engine to fill the 64-bit range, whereas the
native implementation used only one.
Now the selection of the range variant only depends on the requested range. A
32-bit range uses the 32-bit variant (even for 64-bit engines), whereas a
larger range uses the 64-bit variant.
This was found in https://github.com/php/php-src/pull/9410#discussion_r953213000
* Fix rand_range32() for umax = UINT32_MAX
This was introduced in the commit that added the random extension:
4d8dd8d258.
Resolves GH-9415
* [ci skip] Rename `$r` to `$randomizer` in gh9415.phpt
* Make gh9415.phpt deterministic
* Make gh9415.phpt compatible with 32-bit
* Remove user_xoshiro128plusplus.phpt
This test does not exercise any of the code paths of the random extension and
thus is no value-add.
* Unify structure for ext/random's engine tests
RAND_RANGE_BADSCALING() invokes undefined behavior when (max - min) >
ZEND_LONG_MAX, because the intermediate `double` might not fit into
`zend_long`.
Fix this by inlining a fixed version of the macro into Mt19937's range()
function. Fixing the macro itself cannot be done in the general case, because
the types of the inputs are not known. Instead of replacing one possibly broken
version with another possibly broken version, the macro is simply left as is
and should be removed in a future version.
The fix itself is simple: Instead of storing the "offset" in a `zend_long`, we
use a `zend_ulong` which is capable of storing the resulting double by
construction. With this fix the implementation of this broken scaling is
effectively identical to the implementation of php_random_range from a data
type perspective, making it easy to verify the correctness.
It was further empirically verified that the broken macro and the fix return
the same results for all possible values of `r` for several distinct pairs of
(min, max).
Fixes GH-9190
Fixes GH-9191
* Add Random\Random{Error,Exception} and Random\BrokenRandomEngineError
* Throw BrokenRandomEngineError
* Throw RandomException on seeding failure
* Throw RandomException when CSPRNG fails
* Remove unused include from ext/random/engine_combinedlcg.c
* Remove unused include from ext/random/engine_secure.c
* Remove unused include from ext/random/random.c
* [ci skip] Add ext/random Exception hierarchy to NEWS
* [ci skip] Add the change of Exception for random_(int|bytes) to UPGRADING
* Fix PcgOneseq128XslRr64::__construct() definition in random.stub.php
The second parameter does not actually exist for a Oneseq PCG. It was removed
from the RFC before it went into voting.
* [ci skip] Add PcgOneseq128XslRr64 stub fix to NEWS
* Verify that the engine doesn't change in construct_twice.phpt
* Clean up the implementation of Randomizer::__construct()
Instead of manually checking whether the constructor was already called, we
rely on the `readonly` modifier of the `$engine` property.
Additionally use `object_init_ex()` instead of manually calling
`->create_object()`.
* Remove exception in Randomizer::shuffleBytes()
The only way that `php_binary_string_shuffle` fails is when the engine itself
fails. With the currently available list of engines we have:
- Mt19937 : Infallible.
- PcgOneseq128XslRr64: Infallible.
- Xoshiro256StarStar : Infallible.
- Secure : Practically infallible on modern systems.
Exception messages were cleaned up in GH-9169.
- User : Error when returning an empty string.
Error when seriously biased (range() fails).
And whatever Throwable the userland developer decides to use.
So the existing engines are either infallible or throw an Exception/Error with
a high quality message themselves, making this exception not a value-add and
possibly confusing.
* Remove exception in Randomizer::shuffleArray()
Same reasoning as in the previous commit applies.
* Remove exception in Randomizer::getInt()
Same reasoning as in the previous commit applies.
* Remove exception in Randomizer::nextInt()
Same reasoning as in the previous commit applies, except that it won't throw on
a seriously biased user engine, as `range()` is not used.
* Remove exception in Randomizer::getBytes()
Same reasoning as in the previous commit applies.
* Remove exception in Mt19937::generate()
This implementation is shared across all native engines. Thus the same
reasoning as the previous commits applies, except that the User engine does not
use this method. Thus is only applicable to the Secure engine, which is the
only fallible native engine.
* [ci skip] Add cleanup of Randomizer exceptions to NEWS
* Unify ext/random unserialize errors with ext/date
- Use `Error` instead of `Exception`.
- Adjust wording.
* Make `zend_read_property` silent in `Randomizer::__unserialize()`
Having:
> Error: Typed property Random\Randomizer::$engine must not be accessed before
> initialization
is not a value-add in this case.
* Insert the actual class name in the unserialization error of Engines
* Revert unserialization failure back to Exception from Error
see https://news-web.php.net/php.internals/118311
Since argument overloading is not safe for reflection, the method needed
to be split appropriately.
Co-authored-by: Tim Düsterhus <timwolla@googlemail.com>
Closes GH-9057.
Whenever ->last_unsafe is set to `true` an exception has been thrown. Thus we
can replace the check for `->last_unsafe` with a check for `EG(exception)`
which is a much more natural way to ommunicate an error up the chain.
* Use `php_random_bytes_throw()` in Secure engine's generate()
This exposes the underlying exception, improving debugging:
Fatal error: Uncaught Exception: Cannot open source device in php-src/test.php:5
Stack trace:
#0 php-src/test.php(5): Random\Engine\Secure->generate()
#1 {main}
Next RuntimeException: Random number generation failed in php-src/test.php:5
Stack trace:
#0 php-src/test.php(5): Random\Engine\Secure->generate()
#1 {main}
thrown in php-src/test.php on line 5
* Use `php_random_int_throw()` in Secure engine's range()
This exposes the underlying exception, improving debugging:
Exception: Cannot open source device in php-src/test.php:17
Stack trace:
#0 php-src/test.php(17): Random\Randomizer->getInt(1, 3)
#1 {main}
Next RuntimeException: Random number generation failed in php-src/test.php:17
Stack trace:
#0 php-src/test.php(17): Random\Randomizer->getInt(1, 3)
#1 {main}
* Throw exception when a user engine returns an empty string
This improves debugging, because the actual reason for the failure is available
as a previous Exception:
DomainException: The returned string must not be empty in php-src/test.php:17
Stack trace:
#0 php-src/test.php(17): Random\Randomizer->getBytes(123)
#1 {main}
Next RuntimeException: Random number generation failed in php-src/test.php:17
Stack trace:
#0 php-src/test.php(17): Random\Randomizer->getBytes(123)
#1 {main}
* Throw exception when the range selector fails to get acceptable numbers in 50 attempts
This improves debugging, because the actual reason for the failure is available
as a previous Exception:
RuntimeException: Failed to generate an acceptable random number in 50 attempts in php-src/test.php:17
Stack trace:
#0 php-src/test.php(17): Random\Randomizer->getInt(1, 3)
#1 {main}
Next RuntimeException: Random number generation failed in php-src/test.php:17
Stack trace:
#0 php-src/test.php(17): Random\Randomizer->getInt(1, 3)
#1 {main}
* Improve user_unsafe test
Select parameters for ->getInt() that will actually lead to unsafe behavior.
* Fix user_unsafe test
If an engine fails once it will be permanently poisoned by setting
`->last_unsafe`. This is undesirable for the test, because it skews the
results.
Fix this by creating a fresh engine for each "assertion".
* Remove duplication in user_unsafe.phpt
* Catch `Throwable` in user_unsafe.phpt
As we print the full stringified exception we implicitly assert the type of the
exception. No need to be overly specific in the catch block.
* Throw an error if an engine returns an empty string
* Throw an Error if range fails to find an acceptable number in 50 attempts
When Radomizer::__construct() was called with no arguments, Randomizer\Engine\Secure was implicitly instantiate and memory was leaking.
Co-authored-by: Tim Düsterhus <timwolla@googlemail.com>
The previous shifting logic is problematic for two reasons:
1. It invokes undefined behavior when the `->last_generated_size` is at least
as large as the target integer in `result`, because the shift is larger than
the target integer. This was reported in GH-9083.
2. It expands the returned bytes in a big-endian fashion: Earlier bytes are
shifting into the most-significant position. As all the other logic in the
random extension treats byte-strings as little-endian numbers this is
inconsistent.
By fixing the second issue, we can implicitly fix the first one: Instead of
shifting the existing bits by the number of "newly added" bits, we shift the
newly added bits by the number of existing bits. As we stop requesting new bits
once the total_size reached the size of the target integer we can be sure to
never invoke undefined behavior during shifting.
The get_int_user.phpt test was adjusted to verify the little-endian behavior.
It generates a single byte per call and we expect the first byte generated to
appear at the start of the resulting number.
see GH-9056 for a previous fix in the same area.
Fixes GH-9083 which reports the undefined behavior.
Resolves GH-9085 which was an alternative attempt to fix GH-9083.
* Fix shift in rand_range??()
The last generated size is in bytes, whereas the shift is in bits. Multiple the
generated size by 8 to correctly handle each byte once.
* Correctly handle user engines returning less than 4 bytes in rand_rangeXX()
We need to loop until we accumulate sufficient bytes, instead of just checking
once. The version in the rejection loop was already correct.
* Clean up some repetition in rand_rangeXX()
This fixes:
==374077== Use of uninitialised value of size 8
==374077== at 0x532B06: generate (engine_user.c:39)
==374077== by 0x533F71: zim_Random_Randomizer_getBytes (randomizer.c:152)
==374077== by 0x7F581D: ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER (zend_vm_execute.h:1885)
==374077== by 0x8725BE: execute_ex (zend_vm_execute.h:55930)
==374077== by 0x877DB4: zend_execute (zend_vm_execute.h:60253)
==374077== by 0x7B0FD4: zend_execute_scripts (zend.c:1770)
==374077== by 0x6F1647: php_execute_script (main.c:2535)
==374077== by 0x937DA4: do_cli (php_cli.c:964)
==374077== by 0x938C3A: main (php_cli.c:1333)
==374077==
==374077== Invalid read of size 8
==374077== at 0x532B06: generate (engine_user.c:39)
==374077== by 0x533F71: zim_Random_Randomizer_getBytes (randomizer.c:152)
==374077== by 0x7F581D: ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER (zend_vm_execute.h:1885)
==374077== by 0x8725BE: execute_ex (zend_vm_execute.h:55930)
==374077== by 0x877DB4: zend_execute (zend_vm_execute.h:60253)
==374077== by 0x7B0FD4: zend_execute_scripts (zend.c:1770)
==374077== by 0x6F1647: php_execute_script (main.c:2535)
==374077== by 0x937DA4: do_cli (php_cli.c:964)
==374077== by 0x938C3A: main (php_cli.c:1333)
==374077== Address 0x11 is not stack'd, malloc'd or (recently) free'd