1 /* $NetBSD: sys_getrandom.c,v 1.2 2021/12/28 13:22:43 riastradh Exp $ */ 2 3 /*- 4 * Copyright (c) 2020 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Taylor R. Campbell. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* 33 * getrandom() system call 34 */ 35 36 #include <sys/cdefs.h> 37 __KERNEL_RCSID(0, "$NetBSD: sys_getrandom.c,v 1.2 2021/12/28 13:22:43 riastradh Exp $"); 38 39 #include <sys/types.h> 40 #include <sys/param.h> 41 42 #include <sys/atomic.h> 43 #include <sys/cprng.h> 44 #include <sys/entropy.h> 45 #include <sys/kmem.h> 46 #include <sys/lwp.h> 47 #include <sys/proc.h> 48 #include <sys/random.h> 49 #include <sys/sched.h> 50 #include <sys/signalvar.h> 51 #include <sys/syscallargs.h> 52 #include <sys/uio.h> 53 54 #include <crypto/nist_hash_drbg/nist_hash_drbg.h> 55 56 #define RANDOM_BUFSIZE 512 57 58 int 59 dogetrandom(struct uio *uio, unsigned int flags) 60 { 61 uint8_t seed[NIST_HASH_DRBG_SEEDLEN_BYTES] = {0}; 62 struct nist_hash_drbg drbg; 63 uint8_t *buf; 64 int extractflags = 0; 65 int error; 66 67 KASSERT((flags & ~(GRND_RANDOM|GRND_INSECURE|GRND_NONBLOCK)) == 0); 68 KASSERT((flags & (GRND_RANDOM|GRND_INSECURE)) != 69 (GRND_RANDOM|GRND_INSECURE)); 70 71 /* Get a buffer for transfers. */ 72 buf = kmem_alloc(RANDOM_BUFSIZE, KM_SLEEP); 73 74 /* 75 * Fast path: for short reads other than from /dev/random, if 76 * seeded or if INSECURE, just draw from per-CPU cprng_strong. 77 */ 78 if (uio->uio_resid <= RANDOM_BUFSIZE && 79 !ISSET(flags, GRND_RANDOM) && 80 (entropy_ready() || ISSET(flags, GRND_INSECURE))) { 81 /* Generate data and transfer it out. */ 82 cprng_strong(user_cprng, buf, uio->uio_resid, 0); 83 error = uiomove(buf, uio->uio_resid, uio); 84 goto out; 85 } 86 87 /* 88 * Try to get a seed from the entropy pool. Fail if we would 89 * block. If GRND_INSECURE, always return something even if it 90 * is partial entropy; if !GRND_INSECURE, set ENTROPY_HARDFAIL 91 * in order to tell entropy_extract not to bother drawing 92 * anything from a partial pool if we can't get full entropy. 93 */ 94 if (!ISSET(flags, GRND_NONBLOCK) && !ISSET(flags, GRND_INSECURE)) 95 extractflags |= ENTROPY_WAIT|ENTROPY_SIG; 96 if (!ISSET(flags, GRND_INSECURE)) 97 extractflags |= ENTROPY_HARDFAIL; 98 error = entropy_extract(seed, sizeof seed, extractflags); 99 if (error && !ISSET(flags, GRND_INSECURE)) 100 goto out; 101 102 /* Instantiate the DRBG. */ 103 if (nist_hash_drbg_instantiate(&drbg, seed, sizeof seed, NULL, 0, 104 NULL, 0)) 105 panic("nist_hash_drbg_instantiate"); 106 107 /* Promptly zero the seed. */ 108 explicit_memset(seed, 0, sizeof seed); 109 110 /* Generate data. */ 111 error = 0; 112 while (uio->uio_resid) { 113 size_t n = MIN(uio->uio_resid, RANDOM_BUFSIZE); 114 115 /* 116 * Clamp /dev/random output to the entropy capacity and 117 * seed size. Programs can't rely on long reads. 118 */ 119 if (ISSET(flags, GRND_RANDOM)) { 120 n = MIN(n, ENTROPY_CAPACITY); 121 n = MIN(n, sizeof seed); 122 /* 123 * Guarantee never to return more than one 124 * buffer in this case to minimize bookkeeping. 125 */ 126 CTASSERT(ENTROPY_CAPACITY <= RANDOM_BUFSIZE); 127 CTASSERT(sizeof seed <= RANDOM_BUFSIZE); 128 } 129 130 /* 131 * Try to generate a block of data, but if we've hit 132 * the DRBG reseed interval, reseed. 133 */ 134 if (nist_hash_drbg_generate(&drbg, buf, n, NULL, 0)) { 135 /* 136 * Get a fresh seed without blocking -- we have 137 * already generated some output so it is not 138 * useful to block. This can fail only if the 139 * request is obscenely large, so it is OK for 140 * either /dev/random or /dev/urandom to fail: 141 * we make no promises about gigabyte-sized 142 * reads happening all at once. 143 */ 144 error = entropy_extract(seed, sizeof seed, 145 ENTROPY_HARDFAIL); 146 if (error) 147 break; 148 149 /* Reseed and try again. */ 150 if (nist_hash_drbg_reseed(&drbg, seed, sizeof seed, 151 NULL, 0)) 152 panic("nist_hash_drbg_reseed"); 153 154 /* Promptly zero the seed. */ 155 explicit_memset(seed, 0, sizeof seed); 156 157 /* If it fails now, that's a bug. */ 158 if (nist_hash_drbg_generate(&drbg, buf, n, NULL, 0)) 159 panic("nist_hash_drbg_generate"); 160 } 161 162 /* Transfer n bytes out. */ 163 error = uiomove(buf, n, uio); 164 if (error) 165 break; 166 167 /* 168 * If this is /dev/random, stop here, return what we 169 * have, and force the next read to reseed. Programs 170 * can't rely on /dev/random for long reads. 171 */ 172 if (ISSET(flags, GRND_RANDOM)) { 173 error = 0; 174 break; 175 } 176 177 /* Now's a good time to yield if needed. */ 178 preempt_point(); 179 180 /* Check for interruption after at least 256 bytes. */ 181 CTASSERT(RANDOM_BUFSIZE >= 256); 182 if (__predict_false(curlwp->l_flag & LW_PENDSIG) && 183 sigispending(curlwp, 0)) { 184 error = EINTR; 185 break; 186 } 187 } 188 189 out: /* Zero the buffer and free it. */ 190 explicit_memset(buf, 0, RANDOM_BUFSIZE); 191 kmem_free(buf, RANDOM_BUFSIZE); 192 193 return error; 194 } 195 196 int 197 sys_getrandom(struct lwp *l, const struct sys_getrandom_args *uap, 198 register_t *retval) 199 { 200 /* { 201 syscallarg(void *) buf; 202 syscallarg(size_t) buflen; 203 syscallarg(unsigned) flags; 204 } */ 205 void *buf = SCARG(uap, buf); 206 size_t buflen = SCARG(uap, buflen); 207 int flags = SCARG(uap, flags); 208 int error; 209 210 /* Set up an iov and uio to read into the user's buffer. */ 211 struct iovec iov = { .iov_base = buf, .iov_len = buflen }; 212 struct uio uio = { 213 .uio_iov = &iov, 214 .uio_iovcnt = 1, 215 .uio_offset = 0, 216 .uio_resid = buflen, 217 .uio_rw = UIO_READ, 218 .uio_vmspace = curproc->p_vmspace, 219 }; 220 221 /* Validate the flags. */ 222 if (flags & ~(GRND_RANDOM|GRND_INSECURE|GRND_NONBLOCK)) { 223 /* Unknown flags. */ 224 error = EINVAL; 225 goto out; 226 } 227 if ((flags & (GRND_RANDOM|GRND_INSECURE)) == 228 (GRND_RANDOM|GRND_INSECURE)) { 229 /* Nonsensical combination. */ 230 error = EINVAL; 231 goto out; 232 } 233 234 /* Do it. */ 235 error = dogetrandom(&uio, flags); 236 237 out: /* 238 * If we transferred anything, return the number of bytes 239 * transferred and suppress error; otherwise return the error. 240 */ 241 *retval = buflen - uio.uio_resid; 242 if (*retval) 243 error = 0; 244 return error; 245 } 246