]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dns_random.cc
Merge pull request #14021 from Habbie/auth-lua-join-whitespace
[thirdparty/pdns.git] / pdns / dns_random.cc
1 /*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28 #include <unistd.h>
29 #include <stdlib.h>
30 #include <string>
31 #include "dns_random.hh"
32 #include "arguments.hh"
33 #include "logger.hh"
34 #include "boost/lexical_cast.hpp"
35
36 #if defined(HAVE_RANDOMBYTES_STIR)
37 #include <sodium.h>
38 #endif
39 #if defined(HAVE_RAND_BYTES)
40 #include <openssl/rand.h>
41 #endif
42 #if defined(HAVE_GETRANDOM)
43 #include <sys/random.h>
44 #endif
45
46 static enum DNS_RNG {
47 RNG_UNINITIALIZED = 0,
48 RNG_SODIUM,
49 RNG_OPENSSL,
50 RNG_GETRANDOM,
51 RNG_ARC4RANDOM,
52 RNG_URANDOM,
53 RNG_KISS,
54 } chosen_rng = RNG_UNINITIALIZED;
55
56 static int urandom_fd = -1;
57
58 #if defined(HAVE_KISS_RNG)
59 /* KISS is intended for development use only */
60 static unsigned int kiss_seed;
61 static uint32_t kiss_z, kiss_w, kiss_jsr, kiss_jcong;
62
63 static void
64 kiss_init(unsigned int seed)
65 {
66 kiss_seed = seed;
67 kiss_jsr = 0x5eed5eed; /* simply musn't be 0 */
68 kiss_z = 1 ^ (kiss_w = kiss_jcong = seed); /* w=z=0 is bad, see Rose */
69 }
70
71 static unsigned int
72 kiss_rand(void)
73 {
74 kiss_z = 36969 * (kiss_z&65535) + (kiss_z>>16);
75 kiss_w = 18000 * (kiss_w&65535) + (kiss_w>>16);
76 kiss_jcong = 69069 * kiss_jcong + 1234567;
77 kiss_jsr^=(kiss_jsr<<13); /* <<17, >>13 gives cycle length 2^28.2 max */
78 kiss_jsr^=(kiss_jsr>>17); /* <<13, >>17 gives maximal cycle length */
79 kiss_jsr^=(kiss_jsr<<5);
80 return (((kiss_z<<16) + kiss_w) ^ kiss_jcong) + kiss_jsr;
81 }
82 #endif
83
84 static void dns_random_setup(bool force=false)
85 {
86 string rdev;
87 string rng;
88 /* check if selection has been done */
89 if (chosen_rng > RNG_UNINITIALIZED && !force)
90 return;
91
92 /* XXX: A horrible hack to allow using dns_random in places where arguments are not available.
93 Forces /dev/urandom usage
94 */
95 #if defined(USE_URANDOM_ONLY)
96 chosen_rng = RNG_URANDOM;
97 rdev = "/dev/urandom";
98 #else
99 rng = ::arg()["rng"];
100 rdev = ::arg()["entropy-source"];
101 if (rng == "auto") {
102 # if defined(HAVE_GETRANDOM)
103 chosen_rng = RNG_GETRANDOM;
104 # elif defined(HAVE_ARC4RANDOM)
105 chosen_rng = RNG_ARC4RANDOM;
106 # elif defined(HAVE_RANDOMBYTES_STIR)
107 chosen_rng = RNG_SODIUM;
108 # elif defined(HAVE_RAND_BYTES)
109 chosen_rng = RNG_OPENSSL;
110 # else
111 chosen_rng = RNG_URANDOM;
112 # endif
113 # if defined(HAVE_RANDOMBYTES_STIR)
114 } else if (rng == "sodium") {
115 chosen_rng = RNG_SODIUM;
116 # endif
117 # if defined(HAVE_RAND_BYTES)
118 } else if (rng == "openssl") {
119 chosen_rng = RNG_OPENSSL;
120 # endif
121 # if defined(HAVE_GETRANDOM)
122 } else if (rng == "getrandom") {
123 chosen_rng = RNG_GETRANDOM;
124 # endif
125 # if defined(HAVE_ARC4RANDOM)
126 } else if (rng == "arc4random") {
127 chosen_rng = RNG_ARC4RANDOM;
128 # endif
129 } else if (rng == "urandom") {
130 chosen_rng = RNG_URANDOM;
131 #if defined(HAVE_KISS_RNG)
132 } else if (rng == "kiss") {
133 chosen_rng = RNG_KISS;
134 g_log<<Logger::Warning<<"kiss rng should not be used in production environment"<<std::endl;
135 #endif
136 } else {
137 throw std::runtime_error("Unsupported rng '" + rng + "'");
138 }
139
140 # if defined(HAVE_RANDOMBYTES_STIR)
141 if (chosen_rng == RNG_SODIUM) {
142 if (sodium_init() == -1)
143 throw std::runtime_error("Unable to initialize sodium crypto library");
144 /* make sure it's set up */
145 randombytes_stir();
146 }
147 # endif
148
149 # if defined(HAVE_GETRANDOM)
150 if (chosen_rng == RNG_GETRANDOM) {
151 char buf[1];
152 // some systems define getrandom but it does not really work, e.g. because it's
153 // not present in kernel.
154 if (getrandom(buf, sizeof(buf), 0) == -1) {
155 g_log<<Logger::Warning<<"getrandom() failed: "<<strerror(errno)<<", falling back to " + rdev<<std::endl;
156 chosen_rng = RNG_URANDOM;
157 }
158 }
159 # endif
160
161 # if defined(HAVE_RAND_BYTES)
162 if (chosen_rng == RNG_OPENSSL) {
163 int ret;
164 unsigned char buf[1];
165 if ((ret = RAND_bytes(buf, sizeof(buf))) == -1)
166 throw std::runtime_error("RAND_bytes not supported by current SSL engine");
167 if (ret == 0)
168 throw std::runtime_error("Openssl RNG was not seeded");
169 }
170 # endif
171 #endif /* USE_URANDOM_ONLY */
172 if (chosen_rng == RNG_URANDOM) {
173 urandom_fd = open(rdev.c_str(), O_RDONLY);
174 if (urandom_fd == -1)
175 throw std::runtime_error("Cannot open " + rdev + ": " + std::string(strerror(errno)));
176 }
177 #if defined(HAVE_KISS_RNG)
178 if (chosen_rng == RNG_KISS) {
179 unsigned int seed;
180 urandom_fd = open(rdev.c_str(), O_RDONLY);
181 if (urandom_fd == -1)
182 throw std::runtime_error("Cannot open " + rdev + ": " + std::string(strerror(errno)));
183 if (read(urandom_fd, &seed, sizeof(seed)) < 0) {
184 (void)close(urandom_fd);
185 throw std::runtime_error("Cannot read random device");
186 }
187 kiss_init(seed);
188 (void)close(urandom_fd);
189 }
190 #endif
191 }
192
193 void dns_random_init(const string& data __attribute__((unused)), bool force) {
194 dns_random_setup(force);
195 (void)dns_random(1);
196 // init should occur already in dns_random_setup
197 // this interface is only for KISS
198 #if defined(HAVE_KISS_RNG)
199 unsigned int seed;
200 if (chosen_rng != RNG_KISS)
201 return;
202 if (data.size() != 16)
203 throw std::runtime_error("invalid seed");
204 seed = (data[0] + (data[1]<<8) + (data[2]<<16) + (data[3]<<24)) ^
205 (data[4] + (data[5]<<8) + (data[6]<<16) + (data[7]<<24)) ^
206 (data[8] + (data[9]<<8) + (data[10]<<16) + (data[11]<<24)) ^
207 (data[12] + (data[13]<<8) + (data[14]<<16) + (data[15]<<24));
208 kiss_init(seed);
209 #endif
210 }
211
212 /* Parts of this code come from arc4random_uniform */
213 uint32_t dns_random(uint32_t upper_bound) {
214 if (chosen_rng == RNG_UNINITIALIZED)
215 dns_random_setup();
216
217 unsigned int min;
218 if (upper_bound < 2)
219 return 0;
220 /* To avoid "modulo bias" for some methods, calculate
221 minimum acceptable value for random number to improve
222 uniformity.
223
224 On applicable rngs, we loop until the rng spews out
225 value larger than min, and then take modulo out of that.
226 */
227 #if (ULONG_MAX > 0xffffffffUL)
228 min = 0x100000000UL % upper_bound;
229 #else
230 /* Calculate (2**32 % upper_bound) avoiding 64-bit math */
231 if (upper_bound > 0x80000000)
232 min = 1 + ~upper_bound; /* 2**32 - upper_bound */
233 else {
234 /* (2**32 - (x * 2)) % x == 2**32 % x when x <= 2**31 */
235 min = ((0xffffffff - (upper_bound * 2)) + 1) % upper_bound;
236 }
237 #endif
238
239 switch(chosen_rng) {
240 case RNG_UNINITIALIZED:
241 throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
242 case RNG_SODIUM:
243 #if defined(HAVE_RANDOMBYTES_STIR) && !defined(USE_URANDOM_ONLY)
244 return randombytes_uniform(upper_bound);
245 #else
246 throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
247 #endif /* RND_SODIUM */
248 case RNG_OPENSSL: {
249 #if defined(HAVE_RAND_BYTES) && !defined(USE_URANDOM_ONLY)
250 uint32_t num = 0;
251 do {
252 if (RAND_bytes(reinterpret_cast<unsigned char*>(&num), sizeof(num)) < 1)
253 throw std::runtime_error("Openssl RNG was not seeded");
254 }
255 while(num < min);
256
257 return num % upper_bound;
258 #else
259 throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
260 #endif /* RNG_OPENSSL */
261 }
262 case RNG_GETRANDOM: {
263 #if defined(HAVE_GETRANDOM) && !defined(USE_URANDOM_ONLY)
264 uint32_t num = 0;
265 do {
266 if (getrandom(&num, sizeof(num), 0) != sizeof(num))
267 throw std::runtime_error("getrandom() failed: " + std::string(strerror(errno)));
268 }
269 while(num < min);
270
271 return num % upper_bound;
272 #else
273 throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
274 #endif
275 }
276 case RNG_ARC4RANDOM:
277 #if defined(HAVE_ARC4RANDOM) && !defined(USE_URANDOM_ONLY)
278 return arc4random_uniform(upper_bound);
279 #else
280 throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
281 #endif
282 case RNG_URANDOM: {
283 uint32_t num = 0;
284 size_t attempts = 5;
285 do {
286 ssize_t got = read(urandom_fd, &num, sizeof(num));
287 if (got < 0) {
288 if (errno == EINTR) {
289 continue;
290 }
291
292 (void)close(urandom_fd);
293 throw std::runtime_error("Cannot read random device");
294 }
295 else if (static_cast<size_t>(got) != sizeof(num)) {
296 /* short read, let's retry */
297 if (attempts == 0) {
298 throw std::runtime_error("Too many short reads on random device");
299 }
300 attempts--;
301 continue;
302 }
303 }
304 while(num < min);
305
306 return num % upper_bound;
307 }
308 #if defined(HAVE_KISS_RNG)
309 case RNG_KISS: {
310 uint32_t num = 0;
311 do {
312 num = kiss_rand();
313 }
314 while(num < min);
315
316 return num % upper_bound;
317 }
318 #endif
319 default:
320 throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
321 };
322 }