]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/dns_random.cc
Better (actual) fix for leak reported by Coverity.
[thirdparty/pdns.git] / pdns / dns_random.cc
CommitLineData
12471842
PL
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 */
870a0fe4
AT
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
4b3fbfd3 25#include <sys/types.h>
e97cb679
AT
26#include <sys/stat.h>
27#include <fcntl.h>
4b3fbfd3 28#include <unistd.h>
e97cb679
AT
29#include <stdlib.h>
30#include <string>
0b1143db 31#include "dns_random.hh"
e97cb679
AT
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)
1d2a703d 43#include <sys/random.h>
e97cb679 44#endif
4b3fbfd3 45
e97cb679
AT
46static 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;
4b3fbfd3 55
e97cb679 56static int urandom_fd = -1;
4b3fbfd3 57
e97cb679
AT
58#if defined(HAVE_KISS_RNG)
59/* KISS is intended for development use only */
60static unsigned int kiss_seed;
61static uint32_t kiss_z, kiss_w, kiss_jsr, kiss_jcong;
e06db596 62
e97cb679
AT
63static void
64kiss_init(unsigned int seed)
4b3fbfd3 65{
e97cb679 66 kiss_seed = seed;
ef2ea4bf 67 kiss_jsr = 0x5eed5eed; /* simply mustn't be 0 */
e97cb679
AT
68 kiss_z = 1 ^ (kiss_w = kiss_jcong = seed); /* w=z=0 is bad, see Rose */
69}
70
71static unsigned int
72kiss_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
05739b0e 84static void dns_random_setup(bool force=false)
e97cb679
AT
85{
86 string rdev;
87 string rng;
88 /* check if selection has been done */
05739b0e 89 if (chosen_rng > RNG_UNINITIALIZED && !force)
e97cb679
AT
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;
1d2a703d 134 g_log<<Logger::Warning<<"kiss rng should not be used in production environment"<<std::endl;
e97cb679
AT
135#endif
136 } else {
05739b0e 137 throw std::runtime_error("Unsupported rng '" + rng + "'");
ba6cbc80 138 }
4b3fbfd3 139
e97cb679
AT
140# if defined(HAVE_RANDOMBYTES_STIR)
141 if (chosen_rng == RNG_SODIUM) {
142 if (sodium_init() == -1)
05739b0e 143 throw std::runtime_error("Unable to initialize sodium crypto library");
e97cb679
AT
144 /* make sure it's set up */
145 randombytes_stir();
146 }
147# endif
4b3fbfd3 148
e97cb679
AT
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.
36a85861 154 if (getrandom(buf, sizeof(buf), 0) == -1 && errno != EINTR) {
a702a96c 155 g_log<<Logger::Warning<<"getrandom() failed: "<<stringerror()<<", falling back to " + rdev<<std::endl;
e97cb679
AT
156 chosen_rng = RNG_URANDOM;
157 }
158 }
159# endif
e22d9b4e 160
e97cb679
AT
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)
05739b0e 166 throw std::runtime_error("RAND_bytes not supported by current SSL engine");
e97cb679 167 if (ret == 0)
05739b0e 168 throw std::runtime_error("Openssl RNG was not seeded");
e97cb679
AT
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)
a2a81d42 175 throw std::runtime_error("Cannot open " + rdev + ": " + stringerror());
e97cb679
AT
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)
a2a81d42 182 throw std::runtime_error("Cannot open " + rdev + ": " + stringerror());
e97cb679
AT
183 if (read(urandom_fd, &seed, sizeof(seed)) < 0) {
184 (void)close(urandom_fd);
05739b0e 185 throw std::runtime_error("Cannot read random device");
e97cb679
AT
186 }
187 kiss_init(seed);
188 (void)close(urandom_fd);
189 }
190#endif
4b3fbfd3
BH
191}
192
05739b0e
RG
193void dns_random_init(const string& data __attribute__((unused)), bool force) {
194 dns_random_setup(force);
e97cb679
AT
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)
05739b0e 203 throw std::runtime_error("invalid seed");
e97cb679
AT
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);
e06a74d5 209#endif
4b3fbfd3
BH
210}
211
e97cb679 212/* Parts of this code come from arc4random_uniform */
a49c8752 213uint32_t dns_random(uint32_t upper_bound) {
e97cb679
AT
214 if (chosen_rng == RNG_UNINITIALIZED)
215 dns_random_setup();
4b3fbfd3 216
e97cb679
AT
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 */
764d0fbe 227#if (ULONG_MAX > 0xffffffffUL)
e97cb679
AT
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 }
4b3fbfd3 237#endif
e97cb679
AT
238
239 switch(chosen_rng) {
240 case RNG_UNINITIALIZED:
05739b0e 241 throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
e97cb679
AT
242 case RNG_SODIUM:
243#if defined(HAVE_RANDOMBYTES_STIR) && !defined(USE_URANDOM_ONLY)
a49c8752 244 return randombytes_uniform(upper_bound);
e97cb679 245#else
05739b0e 246 throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
e97cb679
AT
247#endif /* RND_SODIUM */
248 case RNG_OPENSSL: {
249#if defined(HAVE_RAND_BYTES) && !defined(USE_URANDOM_ONLY)
7c9e9190 250 uint32_t num = 0;
df6d5d8c 251 do {
e97cb679 252 if (RAND_bytes(reinterpret_cast<unsigned char*>(&num), sizeof(num)) < 1)
05739b0e 253 throw std::runtime_error("Openssl RNG was not seeded");
e97cb679 254 }
df6d5d8c
RG
255 while(num < min);
256
e97cb679
AT
257 return num % upper_bound;
258#else
05739b0e 259 throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
e97cb679
AT
260#endif /* RNG_OPENSSL */
261 }
262 case RNG_GETRANDOM: {
263#if defined(HAVE_GETRANDOM) && !defined(USE_URANDOM_ONLY)
7c9e9190 264 uint32_t num = 0;
df6d5d8c 265 do {
36a85861
RG
266 auto got = getrandom(&num, sizeof(num), 0);
267 if (got == -1 && errno == EINTR) {
268 continue;
269 }
270 if (got != sizeof(num)) {
a2a81d42 271 throw std::runtime_error("getrandom() failed: " + stringerror());
36a85861 272 }
e97cb679 273 }
df6d5d8c
RG
274 while(num < min);
275
e97cb679
AT
276 return num % upper_bound;
277#else
05739b0e 278 throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
e97cb679
AT
279#endif
280 }
281 case RNG_ARC4RANDOM:
282#if defined(HAVE_ARC4RANDOM) && !defined(USE_URANDOM_ONLY)
a49c8752 283 return arc4random_uniform(upper_bound);
e97cb679 284#else
05739b0e 285 throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
e97cb679
AT
286#endif
287 case RNG_URANDOM: {
7c9e9190 288 uint32_t num = 0;
144c9852 289 size_t attempts = 5;
df6d5d8c 290 do {
144c9852
RG
291 ssize_t got = read(urandom_fd, &num, sizeof(num));
292 if (got < 0) {
b973ba4d
RG
293 if (errno == EINTR) {
294 continue;
295 }
296
e97cb679 297 (void)close(urandom_fd);
05739b0e 298 throw std::runtime_error("Cannot read random device");
e97cb679 299 }
144c9852
RG
300 else if (static_cast<size_t>(got) != sizeof(num)) {
301 /* short read, let's retry */
302 if (attempts == 0) {
303 throw std::runtime_error("Too many short reads on random device");
304 }
305 attempts--;
306 continue;
307 }
e97cb679 308 }
df6d5d8c
RG
309 while(num < min);
310
e97cb679
AT
311 return num % upper_bound;
312 }
313#if defined(HAVE_KISS_RNG)
314 case RNG_KISS: {
7c9e9190 315 uint32_t num = 0;
df6d5d8c 316 do {
e97cb679 317 num = kiss_rand();
df6d5d8c
RG
318 }
319 while(num < min);
320
e97cb679
AT
321 return num % upper_bound;
322 }
323#endif
324 default:
05739b0e 325 throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
e97cb679
AT
326 };
327}
a410b176
PD
328
329uint16_t dns_random_uint16()
330{
331 return dns_random(0x10000);
332}