xref: /netbsd/sys/arch/amd64/stand/prekern/prng.c (revision b8d0760c)
1 /*	$NetBSD: prng.c,v 1.5 2021/05/04 21:13:38 khorben Exp $	*/
2 
3 /*
4  * Copyright (c) 2017-2020 The NetBSD Foundation, Inc. All rights reserved.
5  *
6  * This code is derived from software contributed to The NetBSD Foundation
7  * by Maxime Villard.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
19  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
22  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include "prekern.h"
32 #include <sys/sha1.h>
33 #include <sys/sha2.h>
34 
35 #define _KERNEL
36 #include <machine/bootinfo.h>
37 #undef _KERNEL
38 
39 #define CPUID_SEF_RDSEED	__BIT(18)
40 #define CPUID2_RDRAND	0x40000000
41 static bool has_rdrand = false;
42 static bool has_rdseed = false;
43 
44 #define RND_SAVEWORDS	128
45 typedef struct {
46 	uint32_t entropy;
47 	uint8_t data[RND_SAVEWORDS * sizeof(uint32_t)];
48 	uint8_t digest[SHA1_DIGEST_LENGTH];
49 } rndsave_t;
50 
51 #define RNGSTATE_SIZE	(SHA512_DIGEST_LENGTH / 2)
52 #define RNGDATA_SIZE	(SHA512_DIGEST_LENGTH / 2)
53 struct {
54 	uint8_t state[RNGSTATE_SIZE];
55 	uint8_t data[RNGDATA_SIZE];
56 	size_t nused;
57 } rng;
58 
59 static struct btinfo_common *
prng_lookup_bootinfo(int type)60 prng_lookup_bootinfo(int type)
61 {
62 	extern struct bootinfo bootinfo;
63 	struct btinfo_common *bic;
64 	bool found;
65 	int i;
66 
67 	bic = (struct btinfo_common *)(bootinfo.bi_data);
68 	found = false;
69 	for (i = 0; i < bootinfo.bi_nentries && !found; i++) {
70 		if (bic->type == type)
71 			found = true;
72 		else
73 			bic = (struct btinfo_common *)
74 			    ((uint8_t *)bic + bic->len);
75 	}
76 	return found ? bic : NULL;
77 }
78 
79 static void
prng_get_entropy_file(SHA512_CTX * ctx)80 prng_get_entropy_file(SHA512_CTX *ctx)
81 {
82 	struct bi_modulelist_entry *bi, *bimax;
83 	struct btinfo_modulelist *biml;
84 	uint8_t digest[SHA1_DIGEST_LENGTH];
85 	rndsave_t *rndsave;
86 	SHA1_CTX sig;
87 	size_t count = 0;
88 
89 	biml =
90 	    (struct btinfo_modulelist *)prng_lookup_bootinfo(BTINFO_MODULELIST);
91 	if (biml == NULL) {
92 		return;
93 	}
94 
95 	bi = (struct bi_modulelist_entry *)((uint8_t *)biml + sizeof(*biml));
96 	bimax = bi + biml->num;
97 	for (; bi < bimax; bi++) {
98 		if (bi->type != BI_MODULE_RND) {
99 			continue;
100 		}
101 		if (bi->len != sizeof(rndsave_t)) {
102 			print_state(STATE_WARNING,
103 					"size mismatch in entropy file");
104 			continue;
105 		}
106 		rndsave = (rndsave_t *)(vaddr_t)bi->base;
107 
108 		/* check the signature */
109 		SHA1Init(&sig);
110 		SHA1Update(&sig, (uint8_t *)&rndsave->entropy,
111 		    sizeof(rndsave->entropy));
112 		SHA1Update(&sig, rndsave->data, sizeof(rndsave->data));
113 		SHA1Final(digest, &sig);
114 		if (memcmp(digest, rndsave->digest, sizeof(digest))) {
115 			print_state(STATE_WARNING,
116 					"bad SHA1 checksum in entropy file");
117 			continue;
118 		}
119 
120 		SHA512_Update(ctx, rndsave->data, sizeof(rndsave->data));
121 		count++;
122 	}
123 	if (count == 0)
124 		print_state(STATE_WARNING, "No entropy file could be loaded");
125 }
126 
127 /*
128  * Add 32 bytes of rdseed/rdrand and 8 bytes of rdtsc to the context.
129  */
130 static void
prng_get_entropy_data(SHA512_CTX * ctx)131 prng_get_entropy_data(SHA512_CTX *ctx)
132 {
133 	uint64_t buf[8], val;
134 	size_t i;
135 
136 	if (has_rdseed) {
137 		for (i = 0; i < 8; i++) {
138 			if (rdseed(&buf[i]) == -1) {
139 				break;
140 			}
141 		}
142 		SHA512_Update(ctx, (uint8_t *)buf, i * sizeof(uint64_t));
143 	} else if (has_rdrand) {
144 		for (i = 0; i < 8; i++) {
145 			if (rdrand(&buf[i]) == -1) {
146 				break;
147 			}
148 		}
149 		SHA512_Update(ctx, (uint8_t *)buf, i * sizeof(uint64_t));
150 	}
151 
152 	val = rdtsc();
153 	SHA512_Update(ctx, (uint8_t *)&val, sizeof(val));
154 }
155 
156 void
prng_init(void)157 prng_init(void)
158 {
159 	extern int cpuid_level;
160 	uint8_t digest[SHA512_DIGEST_LENGTH];
161 	SHA512_CTX ctx;
162 	u_int descs[4];
163 
164 	memset(&rng, 0, sizeof(rng));
165 
166 	/* detect cpu features */
167 	if (cpuid_level >= 0x07) {
168 		cpuid(0x07, 0x00, descs);
169 		has_rdseed = (descs[1] & CPUID_SEF_RDSEED) != 0;
170 	}
171 	if (cpuid_level >= 0x01) {
172 		cpuid(0x01, 0x00, descs);
173 		has_rdrand = (descs[2] & CPUID2_RDRAND) != 0;
174 	}
175 	if (!has_rdseed && !has_rdrand)
176 		print_state(STATE_WARNING, "No CPU entropy feature detected");
177 
178 	SHA512_Init(&ctx);
179 	prng_get_entropy_file(&ctx);
180 	prng_get_entropy_data(&ctx);
181 	SHA512_Final(digest, &ctx);
182 
183 	memcpy(rng.state, digest, RNGSTATE_SIZE);
184 	memcpy(rng.data, digest + RNGSTATE_SIZE, RNGDATA_SIZE);
185 }
186 
187 static void
prng_round(void)188 prng_round(void)
189 {
190 	uint8_t digest[SHA512_DIGEST_LENGTH];
191 	SHA512_CTX ctx;
192 
193 	SHA512_Init(&ctx);
194 	SHA512_Update(&ctx, rng.state, RNGSTATE_SIZE);
195 	prng_get_entropy_data(&ctx);
196 	SHA512_Final(digest, &ctx);
197 
198 	memcpy(rng.state, digest, RNGSTATE_SIZE);
199 	memcpy(rng.data, digest + RNGSTATE_SIZE, RNGDATA_SIZE);
200 
201 	rng.nused = 0;
202 }
203 
204 void
prng_get_rand(void * buf,size_t sz)205 prng_get_rand(void *buf, size_t sz)
206 {
207 	uint8_t *ptr = (uint8_t *)buf;
208 	size_t consumed;
209 
210 	ASSERT(sz <= RNGDATA_SIZE);
211 	if (rng.nused + sz > RNGDATA_SIZE) {
212 		/* Fill what can be */
213 		consumed = RNGDATA_SIZE - rng.nused;
214 		memcpy(ptr, &rng.data[rng.nused], consumed);
215 
216 		/* Go through another round */
217 		prng_round();
218 
219 		/* Fill the rest */
220 		memcpy(ptr + consumed, &rng.data[rng.nused],
221 		    sz - consumed);
222 
223 		rng.nused += (sz - consumed);
224 	} else {
225 		memcpy(ptr, &rng.data[rng.nused], sz);
226 		rng.nused += sz;
227 	}
228 }
229