summaryrefslogtreecommitdiffstats
path: root/drivers/firmware/efi/libstub/random.c
blob: 7109b8a2dcba81b3404b9d880e9c154ca61c5a6c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2016 Linaro Ltd;  <ard.biesheuvel@linaro.org>
 */

#include <linux/efi.h>
#include <asm/efi.h>

#include "efistub.h"

typedef union efi_rng_protocol efi_rng_protocol_t;

union efi_rng_protocol {
	struct {
		efi_status_t (__efiapi *get_info)(efi_rng_protocol_t *,
						  unsigned long *,
						  efi_guid_t *);
		efi_status_t (__efiapi *get_rng)(efi_rng_protocol_t *,
						 efi_guid_t *, unsigned long,
						 u8 *out);
	};
	struct {
		u32 get_info;
		u32 get_rng;
	} mixed_mode;
};

/**
 * efi_get_random_bytes() - fill a buffer with random bytes
 * @size:	size of the buffer
 * @out:	caller allocated buffer to receive the random bytes
 *
 * The call will fail if either the firmware does not implement the
 * EFI_RNG_PROTOCOL or there are not enough random bytes available to fill
 * the buffer.
 *
 * Return:	status code
 */
efi_status_t efi_get_random_bytes(unsigned long size, u8 *out)
{
	efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID;
	efi_status_t status;
	efi_rng_protocol_t *rng = NULL;

	status = efi_bs_call(locate_protocol, &rng_proto, NULL, (void **)&rng);
	if (status != EFI_SUCCESS)
		return status;

	return efi_call_proto(rng, get_rng, NULL, size, out);
}

/**
 * efi_random_get_seed() - provide random seed as configuration table
 *
 * The EFI_RNG_PROTOCOL is used to read random bytes. These random bytes are
 * saved as a configuration table which can be used as entropy by the kernel
 * for the initialization of its pseudo random number generator.
 *
 * If the EFI_RNG_PROTOCOL is not available or there are not enough random bytes
 * available, the configuration table will not be installed and an error code
 * will be returned.
 *
 * Return:	status code
 */
efi_status_t efi_random_get_seed(void)
{
	efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID;
	efi_guid_t rng_algo_raw = EFI_RNG_ALGORITHM_RAW;
	efi_guid_t rng_table_guid = LINUX_EFI_RANDOM_SEED_TABLE_GUID;
	struct linux_efi_random_seed *prev_seed, *seed = NULL;
	int prev_seed_size = 0, seed_size = EFI_RANDOM_SEED_SIZE;
	unsigned long nv_seed_size = 0, offset = 0;
	efi_rng_protocol_t *rng = NULL;
	efi_status_t status;

	status = efi_bs_call(locate_protocol, &rng_proto, NULL, (void **)&rng);
	if (status != EFI_SUCCESS)
		seed_size = 0;

	// Call GetVariable() with a zero length buffer to obtain the size
	get_efi_var(L"RandomSeed", &rng_table_guid, NULL, &nv_seed_size, NULL);
	if (!seed_size && !nv_seed_size)
		return status;

	seed_size += nv_seed_size;

	/*
	 * Check whether a seed was provided by a prior boot stage. In that
	 * case, instead of overwriting it, let's create a new buffer that can
	 * hold both, and concatenate the existing and the new seeds.
	 * Note that we should read the seed size with caution, in case the
	 * table got corrupted in memory somehow.
	 */
	prev_seed = get_efi_config_table(rng_table_guid);
	if (prev_seed && prev_seed->size <= 512U) {
		prev_seed_size = prev_seed->size;
		seed_size += prev_seed_size;
	}

	/*
	 * Use EFI_ACPI_RECLAIM_MEMORY here so that it is guaranteed that the
	 * allocation will survive a kexec reboot (although we refresh the seed
	 * beforehand)
	 */
	status = efi_bs_call(allocate_pool, EFI_ACPI_RECLAIM_MEMORY,
			     struct_size(seed, bits, seed_size),
			     (void **)&seed);
	if (status != EFI_SUCCESS) {
		efi_warn("Failed to allocate memory for RNG seed.\n");
		goto err_warn;
	}

	if (rng) {
		status = efi_call_proto(rng, get_rng, &rng_algo_raw,
					EFI_RANDOM_SEED_SIZE, seed->bits);

		if (status == EFI_UNSUPPORTED)
			/*
			 * Use whatever algorithm we have available if the raw algorithm
			 * is not implemented.
			 */
			status = efi_call_proto(rng, get_rng, NULL,
						EFI_RANDOM_SEED_SIZE, seed->bits);

		if (status == EFI_SUCCESS)
			offset = EFI_RANDOM_SEED_SIZE;
	}

	if (nv_seed_size) {
		status = get_efi_var(L"RandomSeed", &rng_table_guid, NULL,
				     &nv_seed_size, seed->bits + offset);

		if (status == EFI_SUCCESS)
			/*
			 * We delete the seed here, and /hope/ that this causes
			 * EFI to also zero out its representation on disk.
			 * This is somewhat idealistic, but overwriting the
			 * variable with zeros is likely just as fraught too.
			 * TODO: in the future, maybe we can hash it forward
			 * instead, and write a new seed.
			 */
			status = set_efi_var(L"RandomSeed", &rng_table_guid, 0,
					     0, NULL);

		if (status == EFI_SUCCESS)
			offset += nv_seed_size;
		else
			memzero_explicit(seed->bits + offset, nv_seed_size);
	}

	if (!offset)
		goto err_freepool;

	if (prev_seed_size) {
		memcpy(seed->bits + offset, prev_seed->bits, prev_seed_size);
		offset += prev_seed_size;
	}

	seed->size = offset;
	status = efi_bs_call(install_configuration_table, &rng_table_guid, seed);
	if (status != EFI_SUCCESS)
		goto err_freepool;

	if (prev_seed_size) {
		/* wipe and free the old seed if we managed to install the new one */
		memzero_explicit(prev_seed->bits, prev_seed_size);
		efi_bs_call(free_pool, prev_seed);
	}
	return EFI_SUCCESS;

err_freepool:
	memzero_explicit(seed, struct_size(seed, bits, seed_size));
	efi_bs_call(free_pool, seed);
	efi_warn("Failed to obtain seed from EFI_RNG_PROTOCOL or EFI variable\n");
err_warn:
	if (prev_seed)
		efi_warn("Retaining bootloader-supplied seed only");
	return status;
}