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    | Based on code from: David Blackman                                   |
16    |                     Sebastiano Vigna <vigna@acm.org>                 |
17    +----------------------------------------------------------------------+
18 */
19 
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23 
24 #include "php.h"
25 #include "php_random.h"
26 #include "php_random_csprng.h"
27 
28 #include "Zend/zend_exceptions.h"
29 
splitmix64(uint64_t * seed)30 static inline uint64_t splitmix64(uint64_t *seed)
31 {
32 	uint64_t r;
33 
34 	r = (*seed += 0x9e3779b97f4a7c15ULL);
35 	r = (r ^ (r >> 30)) * 0xbf58476d1ce4e5b9ULL;
36 	r = (r ^ (r >> 27)) * 0x94d049bb133111ebULL;
37 	return (r ^ (r >> 31));
38 }
39 
rotl(const uint64_t x,int k)40 ZEND_ATTRIBUTE_CONST static inline uint64_t rotl(const uint64_t x, int k)
41 {
42 	return (x << k) | (x >> (64 - k));
43 }
44 
generate_state(php_random_status_state_xoshiro256starstar * s)45 static inline uint64_t generate_state(php_random_status_state_xoshiro256starstar *s)
46 {
47 	const uint64_t r = rotl(s->state[1] * 5, 7) * 9;
48 	const uint64_t t = s->state[1] << 17;
49 
50 	s->state[2] ^= s->state[0];
51 	s->state[3] ^= s->state[1];
52 	s->state[1] ^= s->state[2];
53 	s->state[0] ^= s->state[3];
54 
55 	s->state[2] ^= t;
56 
57 	s->state[3] = rotl(s->state[3], 45);
58 
59 	return r;
60 }
61 
jump(php_random_status_state_xoshiro256starstar * state,const uint64_t * jmp)62 static inline void jump(php_random_status_state_xoshiro256starstar *state, const uint64_t *jmp)
63 {
64 	uint64_t s0 = 0, s1 = 0, s2 = 0, s3 = 0;
65 
66 	for (uint32_t i = 0; i < 4; i++) {
67 		for (uint32_t j = 0; j < 64; j++) {
68 			if (jmp[i] & 1ULL << j) {
69 				s0 ^= state->state[0];
70 				s1 ^= state->state[1];
71 				s2 ^= state->state[2];
72 				s3 ^= state->state[3];
73 			}
74 
75 			generate_state(state);
76 		}
77 	}
78 
79 	state->state[0] = s0;
80 	state->state[1] = s1;
81 	state->state[2] = s2;
82 	state->state[3] = s3;
83 }
84 
seed256(php_random_status_state_xoshiro256starstar * s,uint64_t s0,uint64_t s1,uint64_t s2,uint64_t s3)85 static inline void seed256(php_random_status_state_xoshiro256starstar *s, uint64_t s0, uint64_t s1, uint64_t s2, uint64_t s3)
86 {
87 	s->state[0] = s0;
88 	s->state[1] = s1;
89 	s->state[2] = s2;
90 	s->state[3] = s3;
91 }
92 
seed64(php_random_status_state_xoshiro256starstar * state,uint64_t seed)93 static inline void seed64(php_random_status_state_xoshiro256starstar *state, uint64_t seed)
94 {
95 	uint64_t s[4];
96 
97 	s[0] = splitmix64(&seed);
98 	s[1] = splitmix64(&seed);
99 	s[2] = splitmix64(&seed);
100 	s[3] = splitmix64(&seed);
101 
102 	seed256(state, s[0], s[1], s[2], s[3]);
103 }
104 
seed(php_random_status * status,uint64_t seed)105 static void seed(php_random_status *status, uint64_t seed)
106 {
107 	seed64(status->state, seed);
108 }
109 
generate(php_random_status * status)110 static php_random_result generate(php_random_status *status)
111 {
112 	return (php_random_result){
113 		.size = sizeof(uint64_t),
114 		.result = generate_state(status->state),
115 	};
116 }
117 
range(php_random_status * status,zend_long min,zend_long max)118 static zend_long range(php_random_status *status, zend_long min, zend_long max)
119 {
120 	return php_random_range(&php_random_algo_xoshiro256starstar, status, min, max);
121 }
122 
serialize(php_random_status * status,HashTable * data)123 static bool serialize(php_random_status *status, HashTable *data)
124 {
125 	php_random_status_state_xoshiro256starstar *s = status->state;
126 	zval t;
127 
128 	for (uint32_t i = 0; i < 4; i++) {
129 		ZVAL_STR(&t, php_random_bin2hex_le(&s->state[i], sizeof(uint64_t)));
130 		zend_hash_next_index_insert(data, &t);
131 	}
132 
133 	return true;
134 }
135 
unserialize(php_random_status * status,HashTable * data)136 static bool unserialize(php_random_status *status, HashTable *data)
137 {
138 	php_random_status_state_xoshiro256starstar *s = status->state;
139 	zval *t;
140 
141 	/* Verify the expected number of elements, this implicitly ensures that no additional elements are present. */
142 	if (zend_hash_num_elements(data) != 4) {
143 		return false;
144 	}
145 
146 	for (uint32_t i = 0; i < 4; i++) {
147 		t = zend_hash_index_find(data, i);
148 		if (!t || Z_TYPE_P(t) != IS_STRING || Z_STRLEN_P(t) != (2 * sizeof(uint64_t))) {
149 			return false;
150 		}
151 		if (!php_random_hex2bin_le(Z_STR_P(t), &s->state[i])) {
152 			return false;
153 		}
154 	}
155 
156 	return true;
157 }
158 
159 const php_random_algo php_random_algo_xoshiro256starstar = {
160 	sizeof(php_random_status_state_xoshiro256starstar),
161 	seed,
162 	generate,
163 	range,
164 	serialize,
165 	unserialize
166 };
167 
php_random_xoshiro256starstar_jump(php_random_status_state_xoshiro256starstar * state)168 PHPAPI void php_random_xoshiro256starstar_jump(php_random_status_state_xoshiro256starstar *state)
169 {
170 	static const uint64_t jmp[] = {0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c};
171 	jump(state, jmp);
172 }
173 
php_random_xoshiro256starstar_jump_long(php_random_status_state_xoshiro256starstar * state)174 PHPAPI void php_random_xoshiro256starstar_jump_long(php_random_status_state_xoshiro256starstar *state)
175 {
176 	static const uint64_t jmp[] = {0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635};
177 	jump(state, jmp);
178 }
179 
180 /* {{{ Random\Engine\Xoshiro256StarStar::jump() */
PHP_METHOD(Random_Engine_Xoshiro256StarStar,jump)181 PHP_METHOD(Random_Engine_Xoshiro256StarStar, jump)
182 {
183 	php_random_engine *engine = Z_RANDOM_ENGINE_P(ZEND_THIS);
184 	php_random_status_state_xoshiro256starstar *state = engine->status->state;
185 
186 	ZEND_PARSE_PARAMETERS_NONE();
187 
188 	php_random_xoshiro256starstar_jump(state);
189 }
190 /* }}} */
191 
192 /* {{{ Random\Engine\Xoshiro256StarStar::jumpLong() */
PHP_METHOD(Random_Engine_Xoshiro256StarStar,jumpLong)193 PHP_METHOD(Random_Engine_Xoshiro256StarStar, jumpLong)
194 {
195 	php_random_engine *engine = Z_RANDOM_ENGINE_P(ZEND_THIS);
196 	php_random_status_state_xoshiro256starstar *state = engine->status->state;
197 
198 	ZEND_PARSE_PARAMETERS_NONE();
199 
200 	php_random_xoshiro256starstar_jump_long(state);
201 }
202 /* }}} */
203 
204 /* {{{ Random\Engine\Xoshiro256StarStar::__construct */
PHP_METHOD(Random_Engine_Xoshiro256StarStar,__construct)205 PHP_METHOD(Random_Engine_Xoshiro256StarStar, __construct)
206 {
207 	php_random_engine *engine = Z_RANDOM_ENGINE_P(ZEND_THIS);
208 	php_random_status_state_xoshiro256starstar *state = engine->status->state;
209 	zend_string *str_seed = NULL;
210 	zend_long int_seed = 0;
211 	bool seed_is_null = true;
212 
213 	ZEND_PARSE_PARAMETERS_START(0, 1)
214 		Z_PARAM_OPTIONAL;
215 		Z_PARAM_STR_OR_LONG_OR_NULL(str_seed, int_seed, seed_is_null);
216 	ZEND_PARSE_PARAMETERS_END();
217 
218 	if (seed_is_null) {
219 		uint64_t t[4];
220 
221 		do {
222 			if (php_random_bytes_throw(&t, sizeof(t)) == FAILURE) {
223 				zend_throw_exception(random_ce_Random_RandomException, "Failed to generate a random seed", 0);
224 				RETURN_THROWS();
225 			}
226 		} while (UNEXPECTED(t[0] == 0 && t[1] == 0 && t[2] == 0 && t[3] == 0));
227 
228 		seed256(state, t[0], t[1], t[2], t[3]);
229 	} else {
230 		if (str_seed) {
231 			/* char (byte: 8 bit) * 32 = 256 bits */
232 			if (ZSTR_LEN(str_seed) == 32) {
233 				uint64_t t[4];
234 
235 				/* Endianness safe copy */
236 				for (uint32_t i = 0; i < 4; i++) {
237 					t[i] = 0;
238 					for (uint32_t j = 0; j < 8; j++) {
239 						t[i] += ((uint64_t) (unsigned char) ZSTR_VAL(str_seed)[(i * 8) + j]) << (j * 8);
240 					}
241 				}
242 
243 				if (UNEXPECTED(t[0] == 0 && t[1] == 0 && t[2] == 0 && t[3] == 0)) {
244 					zend_argument_value_error(1, "must not consist entirely of NUL bytes");
245 					RETURN_THROWS();
246 				}
247 
248 				seed256(state, t[0], t[1], t[2], t[3]);
249 			} else {
250 				zend_argument_value_error(1, "must be a 32 byte (256 bit) string");
251 				RETURN_THROWS();
252 			}
253 		} else {
254 			seed64(state, (uint64_t) int_seed);
255 		}
256 	}
257 }
258 /* }}} */
259