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