xref: /PHP-8.2/ext/random/randomizer.c (revision 350883db)
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_exceptions.h"
28 
randomizer_common_init(php_random_randomizer * randomizer,zend_object * engine_object)29 static inline void randomizer_common_init(php_random_randomizer *randomizer, zend_object *engine_object) {
30 	if (engine_object->ce->type == ZEND_INTERNAL_CLASS) {
31 		/* Internal classes always php_random_engine struct */
32 		php_random_engine *engine = php_random_engine_from_obj(engine_object);
33 
34 		/* Copy engine pointers */
35 		randomizer->algo = engine->algo;
36 		randomizer->status = engine->status;
37 	} else {
38 		/* Self allocation */
39 		randomizer->status = php_random_status_alloc(&php_random_algo_user, false);
40 		php_random_status_state_user *state = randomizer->status->state;
41 		zend_string *mname;
42 		zend_function *generate_method;
43 
44 		mname = zend_string_init("generate", strlen("generate"), 0);
45 		generate_method = zend_hash_find_ptr(&engine_object->ce->function_table, mname);
46 		zend_string_release(mname);
47 
48 		/* Create compatible state */
49 		state->object = engine_object;
50 		state->generate_method = generate_method;
51 
52 		/* Copy common pointers */
53 		randomizer->algo = &php_random_algo_user;
54 
55 		/* Mark self-allocated for memory management */
56 		randomizer->is_userland_algo = true;
57 	}
58 }
59 
60 /* {{{ Random\Randomizer::__construct() */
PHP_METHOD(Random_Randomizer,__construct)61 PHP_METHOD(Random_Randomizer, __construct)
62 {
63 	php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
64 	zval engine;
65 	zval *param_engine = NULL;
66 
67 	ZEND_PARSE_PARAMETERS_START(0, 1)
68 		Z_PARAM_OPTIONAL
69 		Z_PARAM_OBJECT_OF_CLASS_OR_NULL(param_engine, random_ce_Random_Engine);
70 	ZEND_PARSE_PARAMETERS_END();
71 
72 	if (param_engine != NULL) {
73 		ZVAL_COPY(&engine, param_engine);
74 	} else {
75 		/* Create default RNG instance */
76 		object_init_ex(&engine, random_ce_Random_Engine_Secure);
77 	}
78 
79 	zend_update_property(random_ce_Random_Randomizer, Z_OBJ_P(ZEND_THIS), "engine", strlen("engine"), &engine);
80 
81 	OBJ_RELEASE(Z_OBJ_P(&engine));
82 
83 	if (EG(exception)) {
84 		RETURN_THROWS();
85 	}
86 
87 	randomizer_common_init(randomizer, Z_OBJ_P(&engine));
88 }
89 /* }}} */
90 
91 /* {{{ Generate positive random number */
PHP_METHOD(Random_Randomizer,nextInt)92 PHP_METHOD(Random_Randomizer, nextInt)
93 {
94 	php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
95 	uint64_t result;
96 
97 	ZEND_PARSE_PARAMETERS_NONE();
98 
99 	result = randomizer->algo->generate(randomizer->status);
100 	if (EG(exception)) {
101 		RETURN_THROWS();
102 	}
103 	if (randomizer->status->last_generated_size > sizeof(zend_long)) {
104 		zend_throw_exception(random_ce_Random_RandomException, "Generated value exceeds size of int", 0);
105 		RETURN_THROWS();
106 	}
107 
108 	RETURN_LONG((zend_long) (result >> 1));
109 }
110 /* }}} */
111 
112 /* {{{ Generate random number in range */
PHP_METHOD(Random_Randomizer,getInt)113 PHP_METHOD(Random_Randomizer, getInt)
114 {
115 	php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
116 	uint64_t result;
117 	zend_long min, max;
118 
119 	ZEND_PARSE_PARAMETERS_START(2, 2)
120 		Z_PARAM_LONG(min)
121 		Z_PARAM_LONG(max)
122 	ZEND_PARSE_PARAMETERS_END();
123 
124 	if (UNEXPECTED(max < min)) {
125 		zend_argument_value_error(2, "must be greater than or equal to argument #1 ($min)");
126 		RETURN_THROWS();
127 	}
128 
129 	if (UNEXPECTED(
130 		randomizer->algo->range == php_random_algo_mt19937.range
131 		&& ((php_random_status_state_mt19937 *) randomizer->status->state)->mode != MT_RAND_MT19937
132 	)) {
133 		uint64_t r = php_random_algo_mt19937.generate(randomizer->status) >> 1;
134 
135 		/* This is an inlined version of the RAND_RANGE_BADSCALING macro that does not invoke UB when encountering
136 		 * (max - min) > ZEND_LONG_MAX.
137 		 */
138 		zend_ulong offset = (double) ( (double) max - min + 1.0) * (r / (PHP_MT_RAND_MAX + 1.0));
139 
140 		result = (zend_long) (offset + min);
141 	} else {
142 		result = randomizer->algo->range(randomizer->status, min, max);
143 	}
144 
145 	if (EG(exception)) {
146 		RETURN_THROWS();
147 	}
148 
149 	RETURN_LONG((zend_long) result);
150 }
151 /* }}} */
152 
153 /* {{{ Generate random bytes string in ordered length */
PHP_METHOD(Random_Randomizer,getBytes)154 PHP_METHOD(Random_Randomizer, getBytes)
155 {
156 	php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
157 	zend_string *retval;
158 	zend_long length;
159 	size_t total_size = 0;
160 
161 	ZEND_PARSE_PARAMETERS_START(1, 1)
162 		Z_PARAM_LONG(length)
163 	ZEND_PARSE_PARAMETERS_END();
164 
165 	if (length < 1) {
166 		zend_argument_value_error(1, "must be greater than 0");
167 		RETURN_THROWS();
168 	}
169 
170 	retval = zend_string_alloc(length, 0);
171 
172 	while (total_size < length) {
173 		uint64_t result = randomizer->algo->generate(randomizer->status);
174 		if (EG(exception)) {
175 			zend_string_free(retval);
176 			RETURN_THROWS();
177 		}
178 		for (size_t i = 0; i < randomizer->status->last_generated_size; i++) {
179 			ZSTR_VAL(retval)[total_size++] = (result >> (i * 8)) & 0xff;
180 			if (total_size >= length) {
181 				break;
182 			}
183 		}
184 	}
185 
186 	ZSTR_VAL(retval)[length] = '\0';
187 	RETURN_STR(retval);
188 }
189 /* }}} */
190 
191 /* {{{ Shuffling array */
PHP_METHOD(Random_Randomizer,shuffleArray)192 PHP_METHOD(Random_Randomizer, shuffleArray)
193 {
194 	php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
195 	zval *array;
196 
197 	ZEND_PARSE_PARAMETERS_START(1, 1)
198 		Z_PARAM_ARRAY(array)
199 	ZEND_PARSE_PARAMETERS_END();
200 
201 	ZVAL_DUP(return_value, array);
202 	if (!php_array_data_shuffle(randomizer->algo, randomizer->status, return_value)) {
203 		RETURN_THROWS();
204 	}
205 }
206 /* }}} */
207 
208 /* {{{ Shuffling binary */
PHP_METHOD(Random_Randomizer,shuffleBytes)209 PHP_METHOD(Random_Randomizer, shuffleBytes)
210 {
211 	php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
212 	zend_string *bytes;
213 
214 	ZEND_PARSE_PARAMETERS_START(1, 1)
215 		Z_PARAM_STR(bytes)
216 	ZEND_PARSE_PARAMETERS_END();
217 
218 	if (ZSTR_LEN(bytes) < 2) {
219 		RETURN_STR_COPY(bytes);
220 	}
221 
222 	RETVAL_STRINGL(ZSTR_VAL(bytes), ZSTR_LEN(bytes));
223 	if (!php_binary_string_shuffle(randomizer->algo, randomizer->status, Z_STRVAL_P(return_value), (zend_long) Z_STRLEN_P(return_value))) {
224 		RETURN_THROWS();
225 	}
226 }
227 /* }}} */
228 
229 /* {{{ Pick keys */
PHP_METHOD(Random_Randomizer,pickArrayKeys)230 PHP_METHOD(Random_Randomizer, pickArrayKeys)
231 {
232 	php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
233 	zval *input, t;
234 	zend_long num_req;
235 
236 	ZEND_PARSE_PARAMETERS_START(2, 2);
237 		Z_PARAM_ARRAY(input)
238 		Z_PARAM_LONG(num_req)
239 	ZEND_PARSE_PARAMETERS_END();
240 
241 	if (!php_array_pick_keys(
242 		randomizer->algo,
243 		randomizer->status,
244 		input,
245 		num_req,
246 		return_value,
247 		false)
248 	) {
249 		RETURN_THROWS();
250 	}
251 
252 	/* Keep compatibility, But the result is always an array */
253 	if (Z_TYPE_P(return_value) != IS_ARRAY) {
254 		ZVAL_COPY_VALUE(&t, return_value);
255 		array_init(return_value);
256 		zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &t);
257 	}
258 }
259 /* }}} */
260 
261 /* {{{ Random\Randomizer::__serialize() */
PHP_METHOD(Random_Randomizer,__serialize)262 PHP_METHOD(Random_Randomizer, __serialize)
263 {
264 	php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
265 	zval t;
266 
267 	ZEND_PARSE_PARAMETERS_NONE();
268 
269 	array_init(return_value);
270 	ZVAL_ARR(&t, zend_std_get_properties(&randomizer->std));
271 	Z_TRY_ADDREF(t);
272 	zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &t);
273 }
274 /* }}} */
275 
276 /* {{{ Random\Randomizer::__unserialize() */
PHP_METHOD(Random_Randomizer,__unserialize)277 PHP_METHOD(Random_Randomizer, __unserialize)
278 {
279 	php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
280 	HashTable *d;
281 	zval *members_zv;
282 	zval *zengine;
283 
284 	ZEND_PARSE_PARAMETERS_START(1, 1)
285 		Z_PARAM_ARRAY_HT(d);
286 	ZEND_PARSE_PARAMETERS_END();
287 
288 	/* Verify the expected number of elements, this implicitly ensures that no additional elements are present. */
289 	if (zend_hash_num_elements(d) != 1) {
290 		zend_throw_exception(NULL, "Invalid serialization data for Random\\Randomizer object", 0);
291 		RETURN_THROWS();
292 	}
293 
294 	members_zv = zend_hash_index_find(d, 0);
295 	if (!members_zv || Z_TYPE_P(members_zv) != IS_ARRAY) {
296 		zend_throw_exception(NULL, "Invalid serialization data for Random\\Randomizer object", 0);
297 		RETURN_THROWS();
298 	}
299 	object_properties_load(&randomizer->std, Z_ARRVAL_P(members_zv));
300 	if (EG(exception)) {
301 		zend_throw_exception(NULL, "Invalid serialization data for Random\\Randomizer object", 0);
302 		RETURN_THROWS();
303 	}
304 
305 	zengine = zend_read_property(randomizer->std.ce, &randomizer->std, "engine", strlen("engine"), 1, NULL);
306 	if (Z_TYPE_P(zengine) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(zengine), random_ce_Random_Engine)) {
307 		zend_throw_exception(NULL, "Invalid serialization data for Random\\Randomizer object", 0);
308 		RETURN_THROWS();
309 	}
310 
311 	randomizer_common_init(randomizer, Z_OBJ_P(zengine));
312 }
313 /* }}} */
314