1 /*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | https://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Author: Go Kudo <zeriyoshi@php.net> |
14 +----------------------------------------------------------------------+
15 */
16
17 #ifdef HAVE_CONFIG_H
18 # include "config.h"
19 #endif
20
21 #include "php.h"
22 #include "php_random.h"
23
24 #include "ext/standard/php_array.h"
25 #include "ext/standard/php_string.h"
26
27 #include "Zend/zend_enum.h"
28 #include "Zend/zend_exceptions.h"
29
randomizer_common_init(php_random_randomizer * randomizer,zend_object * engine_object)30 static inline void randomizer_common_init(php_random_randomizer *randomizer, zend_object *engine_object) {
31 if (engine_object->ce->type == ZEND_INTERNAL_CLASS) {
32 /* Internal classes always php_random_engine struct */
33 php_random_engine *engine = php_random_engine_from_obj(engine_object);
34
35 /* Copy engine pointers */
36 randomizer->algo = engine->algo;
37 randomizer->status = engine->status;
38 } else {
39 /* Self allocation */
40 randomizer->status = php_random_status_alloc(&php_random_algo_user, false);
41 php_random_status_state_user *state = randomizer->status->state;
42 zend_string *mname;
43 zend_function *generate_method;
44
45 mname = ZSTR_INIT_LITERAL("generate", 0);
46 generate_method = zend_hash_find_ptr(&engine_object->ce->function_table, mname);
47 zend_string_release(mname);
48
49 /* Create compatible state */
50 state->object = engine_object;
51 state->generate_method = generate_method;
52
53 /* Copy common pointers */
54 randomizer->algo = &php_random_algo_user;
55
56 /* Mark self-allocated for memory management */
57 randomizer->is_userland_algo = true;
58 }
59 }
60
61 /* {{{ Random\Randomizer::__construct() */
PHP_METHOD(Random_Randomizer,__construct)62 PHP_METHOD(Random_Randomizer, __construct)
63 {
64 php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
65 zval engine;
66 zval *param_engine = NULL;
67
68 ZEND_PARSE_PARAMETERS_START(0, 1)
69 Z_PARAM_OPTIONAL
70 Z_PARAM_OBJECT_OF_CLASS_OR_NULL(param_engine, random_ce_Random_Engine);
71 ZEND_PARSE_PARAMETERS_END();
72
73 if (param_engine != NULL) {
74 ZVAL_COPY(&engine, param_engine);
75 } else {
76 /* Create default RNG instance */
77 object_init_ex(&engine, random_ce_Random_Engine_Secure);
78 }
79
80 zend_update_property(random_ce_Random_Randomizer, Z_OBJ_P(ZEND_THIS), "engine", strlen("engine"), &engine);
81
82 OBJ_RELEASE(Z_OBJ_P(&engine));
83
84 if (EG(exception)) {
85 RETURN_THROWS();
86 }
87
88 randomizer_common_init(randomizer, Z_OBJ_P(&engine));
89 }
90 /* }}} */
91
92 /* {{{ Generate a float in [0, 1) */
PHP_METHOD(Random_Randomizer,nextFloat)93 PHP_METHOD(Random_Randomizer, nextFloat)
94 {
95 php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
96 uint64_t result;
97 size_t total_size;
98
99 ZEND_PARSE_PARAMETERS_NONE();
100
101 result = 0;
102 total_size = 0;
103 do {
104 uint64_t r = randomizer->algo->generate(randomizer->status);
105 result = result | (r << (total_size * 8));
106 total_size += randomizer->status->last_generated_size;
107 if (EG(exception)) {
108 RETURN_THROWS();
109 }
110 } while (total_size < sizeof(uint64_t));
111
112 /* A double has 53 bits of precision, thus we must not
113 * use the full 64 bits of the uint64_t, because we would
114 * introduce a bias / rounding error.
115 */
116 #if DBL_MANT_DIG != 53
117 # error "Random_Randomizer::nextFloat(): Requires DBL_MANT_DIG == 53 to work."
118 #endif
119 const double step_size = 1.0 / (1ULL << 53);
120
121 /* Use the upper 53 bits, because some engine's lower bits
122 * are of lower quality.
123 */
124 result = (result >> 11);
125
126 RETURN_DOUBLE(step_size * result);
127 }
128 /* }}} */
129
130 /* {{{ Generates a random float within a configurable interval.
131 *
132 * This method uses the γ-section algorithm by Frédéric Goualard.
133 */
PHP_METHOD(Random_Randomizer,getFloat)134 PHP_METHOD(Random_Randomizer, getFloat)
135 {
136 php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
137 double min, max;
138 zend_object *bounds = NULL;
139 int bounds_type = 'C' + sizeof("ClosedOpen") - 1;
140
141 ZEND_PARSE_PARAMETERS_START(2, 3)
142 Z_PARAM_DOUBLE(min)
143 Z_PARAM_DOUBLE(max)
144 Z_PARAM_OPTIONAL
145 Z_PARAM_OBJ_OF_CLASS(bounds, random_ce_Random_IntervalBoundary);
146 ZEND_PARSE_PARAMETERS_END();
147
148 if (!zend_finite(min)) {
149 zend_argument_value_error(1, "must be finite");
150 RETURN_THROWS();
151 }
152
153 if (!zend_finite(max)) {
154 zend_argument_value_error(2, "must be finite");
155 RETURN_THROWS();
156 }
157
158 if (bounds) {
159 zval *case_name = zend_enum_fetch_case_name(bounds);
160 zend_string *bounds_name = Z_STR_P(case_name);
161
162 bounds_type = ZSTR_VAL(bounds_name)[0] + ZSTR_LEN(bounds_name);
163 }
164
165 switch (bounds_type) {
166 case 'C' + sizeof("ClosedOpen") - 1:
167 if (UNEXPECTED(max <= min)) {
168 zend_argument_value_error(2, "must be greater than argument #1 ($min)");
169 RETURN_THROWS();
170 }
171
172 RETURN_DOUBLE(php_random_gammasection_closed_open(randomizer->algo, randomizer->status, min, max));
173 case 'C' + sizeof("ClosedClosed") - 1:
174 if (UNEXPECTED(max < min)) {
175 zend_argument_value_error(2, "must be greater than or equal to argument #1 ($min)");
176 RETURN_THROWS();
177 }
178
179 RETURN_DOUBLE(php_random_gammasection_closed_closed(randomizer->algo, randomizer->status, min, max));
180 case 'O' + sizeof("OpenClosed") - 1:
181 if (UNEXPECTED(max <= min)) {
182 zend_argument_value_error(2, "must be greater than argument #1 ($min)");
183 RETURN_THROWS();
184 }
185
186 RETURN_DOUBLE(php_random_gammasection_open_closed(randomizer->algo, randomizer->status, min, max));
187 case 'O' + sizeof("OpenOpen") - 1:
188 if (UNEXPECTED(max <= min)) {
189 zend_argument_value_error(2, "must be greater than argument #1 ($min)");
190 RETURN_THROWS();
191 }
192
193 RETVAL_DOUBLE(php_random_gammasection_open_open(randomizer->algo, randomizer->status, min, max));
194
195 if (UNEXPECTED(isnan(Z_DVAL_P(return_value)))) {
196 zend_value_error("The given interval is empty, there are no floats between argument #1 ($min) and argument #2 ($max).");
197 RETURN_THROWS();
198 }
199
200 return;
201 default:
202 ZEND_UNREACHABLE();
203 }
204 }
205 /* }}} */
206
207 /* {{{ Generate positive random number */
PHP_METHOD(Random_Randomizer,nextInt)208 PHP_METHOD(Random_Randomizer, nextInt)
209 {
210 php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
211 uint64_t result;
212
213 ZEND_PARSE_PARAMETERS_NONE();
214
215 result = randomizer->algo->generate(randomizer->status);
216 if (EG(exception)) {
217 RETURN_THROWS();
218 }
219 if (randomizer->status->last_generated_size > sizeof(zend_long)) {
220 zend_throw_exception(random_ce_Random_RandomException, "Generated value exceeds size of int", 0);
221 RETURN_THROWS();
222 }
223
224 RETURN_LONG((zend_long) (result >> 1));
225 }
226 /* }}} */
227
228 /* {{{ Generate random number in range */
PHP_METHOD(Random_Randomizer,getInt)229 PHP_METHOD(Random_Randomizer, getInt)
230 {
231 php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
232 uint64_t result;
233 zend_long min, max;
234
235 ZEND_PARSE_PARAMETERS_START(2, 2)
236 Z_PARAM_LONG(min)
237 Z_PARAM_LONG(max)
238 ZEND_PARSE_PARAMETERS_END();
239
240 if (UNEXPECTED(max < min)) {
241 zend_argument_value_error(2, "must be greater than or equal to argument #1 ($min)");
242 RETURN_THROWS();
243 }
244
245 if (UNEXPECTED(
246 randomizer->algo->range == php_random_algo_mt19937.range
247 && ((php_random_status_state_mt19937 *) randomizer->status->state)->mode != MT_RAND_MT19937
248 )) {
249 uint64_t r = php_random_algo_mt19937.generate(randomizer->status) >> 1;
250
251 /* This is an inlined version of the RAND_RANGE_BADSCALING macro that does not invoke UB when encountering
252 * (max - min) > ZEND_LONG_MAX.
253 */
254 zend_ulong offset = (double) ( (double) max - min + 1.0) * (r / (PHP_MT_RAND_MAX + 1.0));
255
256 result = (zend_long) (offset + min);
257 } else {
258 result = randomizer->algo->range(randomizer->status, min, max);
259 }
260
261 if (EG(exception)) {
262 RETURN_THROWS();
263 }
264
265 RETURN_LONG((zend_long) result);
266 }
267 /* }}} */
268
269 /* {{{ Generate random bytes string in ordered length */
PHP_METHOD(Random_Randomizer,getBytes)270 PHP_METHOD(Random_Randomizer, getBytes)
271 {
272 php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
273 zend_string *retval;
274 zend_long length;
275 size_t total_size = 0;
276
277 ZEND_PARSE_PARAMETERS_START(1, 1)
278 Z_PARAM_LONG(length)
279 ZEND_PARSE_PARAMETERS_END();
280
281 if (length < 1) {
282 zend_argument_value_error(1, "must be greater than 0");
283 RETURN_THROWS();
284 }
285
286 retval = zend_string_alloc(length, 0);
287
288 while (total_size < length) {
289 uint64_t result = randomizer->algo->generate(randomizer->status);
290 if (EG(exception)) {
291 zend_string_free(retval);
292 RETURN_THROWS();
293 }
294 for (size_t i = 0; i < randomizer->status->last_generated_size; i++) {
295 ZSTR_VAL(retval)[total_size++] = (result >> (i * 8)) & 0xff;
296 if (total_size >= length) {
297 break;
298 }
299 }
300 }
301
302 ZSTR_VAL(retval)[length] = '\0';
303 RETURN_STR(retval);
304 }
305 /* }}} */
306
307 /* {{{ Shuffling array */
PHP_METHOD(Random_Randomizer,shuffleArray)308 PHP_METHOD(Random_Randomizer, shuffleArray)
309 {
310 php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
311 zval *array;
312
313 ZEND_PARSE_PARAMETERS_START(1, 1)
314 Z_PARAM_ARRAY(array)
315 ZEND_PARSE_PARAMETERS_END();
316
317 ZVAL_DUP(return_value, array);
318 if (!php_array_data_shuffle(randomizer->algo, randomizer->status, return_value)) {
319 RETURN_THROWS();
320 }
321 }
322 /* }}} */
323
324 /* {{{ Shuffling binary */
PHP_METHOD(Random_Randomizer,shuffleBytes)325 PHP_METHOD(Random_Randomizer, shuffleBytes)
326 {
327 php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
328 zend_string *bytes;
329
330 ZEND_PARSE_PARAMETERS_START(1, 1)
331 Z_PARAM_STR(bytes)
332 ZEND_PARSE_PARAMETERS_END();
333
334 if (ZSTR_LEN(bytes) < 2) {
335 RETURN_STR_COPY(bytes);
336 }
337
338 RETVAL_STRINGL(ZSTR_VAL(bytes), ZSTR_LEN(bytes));
339 if (!php_binary_string_shuffle(randomizer->algo, randomizer->status, Z_STRVAL_P(return_value), (zend_long) Z_STRLEN_P(return_value))) {
340 RETURN_THROWS();
341 }
342 }
343 /* }}} */
344
345 /* {{{ Pick keys */
PHP_METHOD(Random_Randomizer,pickArrayKeys)346 PHP_METHOD(Random_Randomizer, pickArrayKeys)
347 {
348 php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
349 zval *input, t;
350 zend_long num_req;
351
352 ZEND_PARSE_PARAMETERS_START(2, 2);
353 Z_PARAM_ARRAY(input)
354 Z_PARAM_LONG(num_req)
355 ZEND_PARSE_PARAMETERS_END();
356
357 if (!php_array_pick_keys(
358 randomizer->algo,
359 randomizer->status,
360 input,
361 num_req,
362 return_value,
363 false)
364 ) {
365 RETURN_THROWS();
366 }
367
368 /* Keep compatibility, But the result is always an array */
369 if (Z_TYPE_P(return_value) != IS_ARRAY) {
370 ZVAL_COPY_VALUE(&t, return_value);
371 array_init(return_value);
372 zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &t);
373 }
374 }
375 /* }}} */
376
377 /* {{{ Get Random Bytes for String */
PHP_METHOD(Random_Randomizer,getBytesFromString)378 PHP_METHOD(Random_Randomizer, getBytesFromString)
379 {
380 php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
381 zend_long length;
382 zend_string *source, *retval;
383 size_t total_size = 0;
384
385 ZEND_PARSE_PARAMETERS_START(2, 2);
386 Z_PARAM_STR(source)
387 Z_PARAM_LONG(length)
388 ZEND_PARSE_PARAMETERS_END();
389
390 const size_t source_length = ZSTR_LEN(source);
391 const size_t max_offset = source_length - 1;
392
393 if (source_length < 1) {
394 zend_argument_value_error(1, "cannot be empty");
395 RETURN_THROWS();
396 }
397
398 if (length < 1) {
399 zend_argument_value_error(2, "must be greater than 0");
400 RETURN_THROWS();
401 }
402
403 retval = zend_string_alloc(length, 0);
404
405 if (max_offset > 0xff) {
406 while (total_size < length) {
407 uint64_t offset = randomizer->algo->range(randomizer->status, 0, max_offset);
408
409 if (EG(exception)) {
410 zend_string_free(retval);
411 RETURN_THROWS();
412 }
413
414 ZSTR_VAL(retval)[total_size++] = ZSTR_VAL(source)[offset];
415 }
416 } else {
417 uint64_t mask = max_offset;
418 // Copy the top-most bit into all lower bits.
419 // Shifting by 4 is sufficient, because max_offset
420 // is guaranteed to fit in an 8-bit integer at this
421 // point.
422 mask |= mask >> 1;
423 mask |= mask >> 2;
424 mask |= mask >> 4;
425
426 int failures = 0;
427 while (total_size < length) {
428 uint64_t result = randomizer->algo->generate(randomizer->status);
429 if (EG(exception)) {
430 zend_string_free(retval);
431 RETURN_THROWS();
432 }
433
434 for (size_t i = 0; i < randomizer->status->last_generated_size; i++) {
435 uint64_t offset = (result >> (i * 8)) & mask;
436
437 if (offset > max_offset) {
438 if (++failures > PHP_RANDOM_RANGE_ATTEMPTS) {
439 zend_string_free(retval);
440 zend_throw_error(random_ce_Random_BrokenRandomEngineError, "Failed to generate an acceptable random number in %d attempts", PHP_RANDOM_RANGE_ATTEMPTS);
441 RETURN_THROWS();
442 }
443
444 continue;
445 }
446
447 failures = 0;
448
449 ZSTR_VAL(retval)[total_size++] = ZSTR_VAL(source)[offset];
450 if (total_size >= length) {
451 break;
452 }
453 }
454 }
455 }
456
457 ZSTR_VAL(retval)[length] = '\0';
458 RETURN_STR(retval);
459 }
460 /* }}} */
461
462 /* {{{ Random\Randomizer::__serialize() */
PHP_METHOD(Random_Randomizer,__serialize)463 PHP_METHOD(Random_Randomizer, __serialize)
464 {
465 php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
466 zval t;
467
468 ZEND_PARSE_PARAMETERS_NONE();
469
470 array_init(return_value);
471 ZVAL_ARR(&t, zend_std_get_properties(&randomizer->std));
472 Z_TRY_ADDREF(t);
473 zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &t);
474 }
475 /* }}} */
476
477 /* {{{ Random\Randomizer::__unserialize() */
PHP_METHOD(Random_Randomizer,__unserialize)478 PHP_METHOD(Random_Randomizer, __unserialize)
479 {
480 php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
481 HashTable *d;
482 zval *members_zv;
483 zval *zengine;
484
485 ZEND_PARSE_PARAMETERS_START(1, 1)
486 Z_PARAM_ARRAY_HT(d);
487 ZEND_PARSE_PARAMETERS_END();
488
489 /* Verify the expected number of elements, this implicitly ensures that no additional elements are present. */
490 if (zend_hash_num_elements(d) != 1) {
491 zend_throw_exception(NULL, "Invalid serialization data for Random\\Randomizer object", 0);
492 RETURN_THROWS();
493 }
494
495 members_zv = zend_hash_index_find(d, 0);
496 if (!members_zv || Z_TYPE_P(members_zv) != IS_ARRAY) {
497 zend_throw_exception(NULL, "Invalid serialization data for Random\\Randomizer object", 0);
498 RETURN_THROWS();
499 }
500 object_properties_load(&randomizer->std, Z_ARRVAL_P(members_zv));
501 if (EG(exception)) {
502 zend_throw_exception(NULL, "Invalid serialization data for Random\\Randomizer object", 0);
503 RETURN_THROWS();
504 }
505
506 zengine = zend_read_property(randomizer->std.ce, &randomizer->std, "engine", strlen("engine"), 1, NULL);
507 if (Z_TYPE_P(zengine) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(zengine), random_ce_Random_Engine)) {
508 zend_throw_exception(NULL, "Invalid serialization data for Random\\Randomizer object", 0);
509 RETURN_THROWS();
510 }
511
512 randomizer_common_init(randomizer, Z_OBJ_P(zengine));
513 }
514 /* }}} */
515