1 /*
2  *  EFI utils
3  *
4  *  Copyright (c) 2017 Rob Clark
5  *
6  *  SPDX-License-Identifier:     GPL-2.0+
7  */
8 
9 #include <malloc.h>
10 #include <charset.h>
11 #include <efi_loader.h>
12 
13 #define READ_ONLY BIT(31)
14 
15 /*
16  * Mapping between EFI variables and u-boot variables:
17  *
18  *   efi_$guid_$varname = {attributes}(type)value
19  *
20  * For example:
21  *
22  *   efi_8be4df61-93ca-11d2-aa0d-00e098032b8c_OsIndicationsSupported=
23  *      "{ro,boot,run}(blob)0000000000000000"
24  *   efi_8be4df61-93ca-11d2-aa0d-00e098032b8c_BootOrder=
25  *      "(blob)00010000"
26  *
27  * The attributes are a comma separated list of these possible
28  * attributes:
29  *
30  *   + ro   - read-only
31  *   + boot - boot-services access
32  *   + run  - runtime access
33  *
34  * NOTE: with current implementation, no variables are available after
35  * ExitBootServices, and all are persisted (if possible).
36  *
37  * If not specified, the attributes default to "{boot}".
38  *
39  * The required type is one of:
40  *
41  *   + utf8 - raw utf8 string
42  *   + blob - arbitrary length hex string
43  *
44  * Maybe a utf16 type would be useful to for a string value to be auto
45  * converted to utf16?
46  */
47 
48 #define MAX_VAR_NAME 31
49 #define MAX_NATIVE_VAR_NAME \
50 	(strlen("efi_xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx_") + \
51 		(MAX_VAR_NAME * MAX_UTF8_PER_UTF16))
52 
53 static int hex(unsigned char ch)
54 {
55 	if (ch >= 'a' && ch <= 'f')
56 		return ch-'a'+10;
57 	if (ch >= '0' && ch <= '9')
58 		return ch-'0';
59 	if (ch >= 'A' && ch <= 'F')
60 		return ch-'A'+10;
61 	return -1;
62 }
63 
64 static const char *hex2mem(u8 *mem, const char *hexstr, int count)
65 {
66 	memset(mem, 0, count/2);
67 
68 	do {
69 		int nibble;
70 
71 		*mem = 0;
72 
73 		if (!count || !*hexstr)
74 			break;
75 
76 		nibble = hex(*hexstr);
77 		if (nibble < 0)
78 			break;
79 
80 		*mem = nibble;
81 		count--;
82 		hexstr++;
83 
84 		if (!count || !*hexstr)
85 			break;
86 
87 		nibble = hex(*hexstr);
88 		if (nibble < 0)
89 			break;
90 
91 		*mem = (*mem << 4) | nibble;
92 		count--;
93 		hexstr++;
94 		mem++;
95 
96 	} while (1);
97 
98 	if (*hexstr)
99 		return hexstr;
100 
101 	return NULL;
102 }
103 
104 static char *mem2hex(char *hexstr, const u8 *mem, int count)
105 {
106 	static const char hexchars[] = "0123456789abcdef";
107 
108 	while (count-- > 0) {
109 		u8 ch = *mem++;
110 		*hexstr++ = hexchars[ch >> 4];
111 		*hexstr++ = hexchars[ch & 0xf];
112 	}
113 
114 	return hexstr;
115 }
116 
117 static efi_status_t efi_to_native(char *native, s16 *variable_name,
118 		efi_guid_t *vendor)
119 {
120 	size_t len;
121 
122 	len = utf16_strlen((u16 *)variable_name);
123 	if (len >= MAX_VAR_NAME)
124 		return EFI_DEVICE_ERROR;
125 
126 	native += sprintf(native, "efi_%pUl_", vendor);
127 	native  = (char *)utf16_to_utf8((u8 *)native, (u16 *)variable_name, len);
128 	*native = '\0';
129 
130 	return EFI_SUCCESS;
131 }
132 
133 static const char *prefix(const char *str, const char *prefix)
134 {
135 	size_t n = strlen(prefix);
136 	if (!strncmp(prefix, str, n))
137 		return str + n;
138 	return NULL;
139 }
140 
141 /* parse attributes part of variable value, if present: */
142 static const char *parse_attr(const char *str, u32 *attrp)
143 {
144 	u32 attr = 0;
145 	char sep = '{';
146 
147 	if (*str != '{') {
148 		*attrp = EFI_VARIABLE_BOOTSERVICE_ACCESS;
149 		return str;
150 	}
151 
152 	while (*str == sep) {
153 		const char *s;
154 
155 		str++;
156 
157 		if ((s = prefix(str, "ro"))) {
158 			attr |= READ_ONLY;
159 		} else if ((s = prefix(str, "boot"))) {
160 			attr |= EFI_VARIABLE_BOOTSERVICE_ACCESS;
161 		} else if ((s = prefix(str, "run"))) {
162 			attr |= EFI_VARIABLE_RUNTIME_ACCESS;
163 		} else {
164 			printf("invalid attribute: %s\n", str);
165 			break;
166 		}
167 
168 		str = s;
169 		sep = ',';
170 	}
171 
172 	str++;
173 
174 	*attrp = attr;
175 
176 	return str;
177 }
178 
179 /* http://wiki.phoenix.com/wiki/index.php/EFI_RUNTIME_SERVICES#GetVariable.28.29 */
180 efi_status_t EFIAPI efi_get_variable(s16 *variable_name,
181 		efi_guid_t *vendor, u32 *attributes,
182 		unsigned long *data_size, void *data)
183 {
184 	char native_name[MAX_NATIVE_VAR_NAME + 1];
185 	efi_status_t ret;
186 	unsigned long in_size;
187 	const char *val, *s;
188 	u32 attr;
189 
190 	EFI_ENTRY("\"%ls\" %pUl %p %p %p", variable_name, vendor, attributes,
191 		  data_size, data);
192 
193 	if (!variable_name || !vendor || !data_size)
194 		return EFI_EXIT(EFI_INVALID_PARAMETER);
195 
196 	ret = efi_to_native(native_name, variable_name, vendor);
197 	if (ret)
198 		return EFI_EXIT(ret);
199 
200 	debug("%s: get '%s'\n", __func__, native_name);
201 
202 	val = env_get(native_name);
203 	if (!val)
204 		return EFI_EXIT(EFI_NOT_FOUND);
205 
206 	val = parse_attr(val, &attr);
207 
208 	in_size = *data_size;
209 
210 	if ((s = prefix(val, "(blob)"))) {
211 		unsigned len = strlen(s);
212 
213 		/* two characters per byte: */
214 		len = DIV_ROUND_UP(len, 2);
215 		*data_size = len;
216 
217 		if (in_size < len)
218 			return EFI_EXIT(EFI_BUFFER_TOO_SMALL);
219 
220 		if (!data)
221 			return EFI_EXIT(EFI_INVALID_PARAMETER);
222 
223 		if (hex2mem(data, s, len * 2))
224 			return EFI_EXIT(EFI_DEVICE_ERROR);
225 
226 		debug("%s: got value: \"%s\"\n", __func__, s);
227 	} else if ((s = prefix(val, "(utf8)"))) {
228 		unsigned len = strlen(s) + 1;
229 
230 		*data_size = len;
231 
232 		if (in_size < len)
233 			return EFI_EXIT(EFI_BUFFER_TOO_SMALL);
234 
235 		if (!data)
236 			return EFI_EXIT(EFI_INVALID_PARAMETER);
237 
238 		memcpy(data, s, len);
239 		((char *)data)[len] = '\0';
240 
241 		debug("%s: got value: \"%s\"\n", __func__, (char *)data);
242 	} else {
243 		debug("%s: invalid value: '%s'\n", __func__, val);
244 		return EFI_EXIT(EFI_DEVICE_ERROR);
245 	}
246 
247 	if (attributes)
248 		*attributes = attr & EFI_VARIABLE_MASK;
249 
250 	return EFI_EXIT(EFI_SUCCESS);
251 }
252 
253 /* http://wiki.phoenix.com/wiki/index.php/EFI_RUNTIME_SERVICES#GetNextVariableName.28.29 */
254 efi_status_t EFIAPI efi_get_next_variable(
255 		unsigned long *variable_name_size,
256 		s16 *variable_name, efi_guid_t *vendor)
257 {
258 	EFI_ENTRY("%p \"%ls\" %pUl", variable_name_size, variable_name, vendor);
259 
260 	return EFI_EXIT(EFI_DEVICE_ERROR);
261 }
262 
263 /* http://wiki.phoenix.com/wiki/index.php/EFI_RUNTIME_SERVICES#SetVariable.28.29 */
264 efi_status_t EFIAPI efi_set_variable(s16 *variable_name,
265 		efi_guid_t *vendor, u32 attributes,
266 		unsigned long data_size, void *data)
267 {
268 	char native_name[MAX_NATIVE_VAR_NAME + 1];
269 	efi_status_t ret = EFI_SUCCESS;
270 	char *val, *s;
271 	u32 attr;
272 
273 	EFI_ENTRY("\"%ls\" %pUl %x %lu %p", variable_name, vendor, attributes,
274 		  data_size, data);
275 
276 	if (!variable_name || !vendor)
277 		return EFI_EXIT(EFI_INVALID_PARAMETER);
278 
279 	ret = efi_to_native(native_name, variable_name, vendor);
280 	if (ret)
281 		return EFI_EXIT(ret);
282 
283 #define ACCESS_ATTR (EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS)
284 
285 	if ((data_size == 0) || !(attributes & ACCESS_ATTR)) {
286 		/* delete the variable: */
287 		env_set(native_name, NULL);
288 		return EFI_EXIT(EFI_SUCCESS);
289 	}
290 
291 	val = env_get(native_name);
292 	if (val) {
293 		parse_attr(val, &attr);
294 
295 		if (attr & READ_ONLY)
296 			return EFI_EXIT(EFI_WRITE_PROTECTED);
297 	}
298 
299 	val = malloc(2 * data_size + strlen("{ro,run,boot}(blob)") + 1);
300 	if (!val)
301 		return EFI_EXIT(EFI_OUT_OF_RESOURCES);
302 
303 	s = val;
304 
305 	/* store attributes: */
306 	attributes &= (EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS);
307 	s += sprintf(s, "{");
308 	while (attributes) {
309 		u32 attr = 1 << (ffs(attributes) - 1);
310 
311 		if (attr == EFI_VARIABLE_BOOTSERVICE_ACCESS)
312 			s += sprintf(s, "boot");
313 		else if (attr == EFI_VARIABLE_RUNTIME_ACCESS)
314 			s += sprintf(s, "run");
315 
316 		attributes &= ~attr;
317 		if (attributes)
318 			s += sprintf(s, ",");
319 	}
320 	s += sprintf(s, "}");
321 
322 	/* store payload: */
323 	s += sprintf(s, "(blob)");
324 	s = mem2hex(s, data, data_size);
325 	*s = '\0';
326 
327 	debug("%s: setting: %s=%s\n", __func__, native_name, val);
328 
329 	if (env_set(native_name, val))
330 		ret = EFI_DEVICE_ERROR;
331 
332 	free(val);
333 
334 	return EFI_EXIT(ret);
335 }
336