xref: /DragonStub/apps/helper.c (revision f412fd2a1a248b546b7085648dece8d908077fab)
1 #include "efidef.h"
2 #include <efi.h>
3 #include <efilib.h>
4 #include <lib.h>
5 #include <dragonstub/dragonstub.h>
6 
7 enum efistub_event {
8 	EFISTUB_EVT_INITRD,
9 	EFISTUB_EVT_LOAD_OPTIONS,
10 	EFISTUB_EVT_COUNT,
11 };
12 
13 #define STR_WITH_SIZE(s) sizeof(s), s
14 
15 static const struct {
16 	u32 pcr_index;
17 	u32 event_id;
18 	u32 event_data_len;
19 	u8 event_data[52];
20 } events[] = {
21 	[EFISTUB_EVT_INITRD] = { 9, INITRD_EVENT_TAG_ID,
22 				 STR_WITH_SIZE("Linux initrd") },
23 	[EFISTUB_EVT_LOAD_OPTIONS] = { 9, LOAD_OPTIONS_EVENT_TAG_ID,
24 				       STR_WITH_SIZE(
25 					       "LOADED_IMAGE::LoadOptions") },
26 };
27 
28 static efi_status_t efi_measure_tagged_event(unsigned long load_addr,
29 					     unsigned long load_size,
30 					     enum efistub_event event)
31 {
32 	efi_guid_t tcg2_guid = EFI_TCG2_PROTOCOL_GUID;
33 	efi_tcg2_protocol_t *tcg2 = NULL;
34 	efi_status_t status;
35 
36 	efi_bs_call(LocateProtocol, &tcg2_guid, NULL, (void **)&tcg2);
37 	if (tcg2) {
38 		struct efi_measured_event {
39 			efi_tcg2_event_t event_data;
40 			efi_tcg2_tagged_event_t tagged_event;
41 			u8 tagged_event_data[];
42 		} * evt;
43 		int size = sizeof(*evt) + events[event].event_data_len;
44 
45 		status = efi_bs_call(AllocatePool, EfiLoaderData, size,
46 				     (void **)&evt);
47 		if (status != EFI_SUCCESS)
48 			goto fail;
49 
50 		evt->event_data = (struct efi_tcg2_event){
51 			.event_size = size,
52 			.event_header.header_size =
53 				sizeof(evt->event_data.event_header),
54 			.event_header.header_version =
55 				EFI_TCG2_EVENT_HEADER_VERSION,
56 			.event_header.pcr_index = events[event].pcr_index,
57 			.event_header.event_type = EV_EVENT_TAG,
58 		};
59 
60 		evt->tagged_event = (struct efi_tcg2_tagged_event){
61 			.tagged_event_id = events[event].event_id,
62 			.tagged_event_data_size = events[event].event_data_len,
63 		};
64 
65 		memcpy(evt->tagged_event_data, events[event].event_data,
66 		       events[event].event_data_len);
67 
68 		status = efi_call_proto(tcg2, hash_log_extend_event, 0,
69 					load_addr, load_size, &evt->event_data);
70 		efi_bs_call(FreePool, evt);
71 
72 		if (status != EFI_SUCCESS)
73 			goto fail;
74 		return EFI_SUCCESS;
75 	}
76 
77 	return EFI_UNSUPPORTED;
78 fail:
79 	efi_warn("Failed to measure data for event %d: 0x%lx\n", event, status);
80 	return status;
81 }
82 
83 /*
84  * At least some versions of Dell firmware pass the entire contents of the
85  * Boot#### variable, i.e. the EFI_LOAD_OPTION descriptor, rather than just the
86  * OptionalData field.
87  *
88  * Detect this case and extract OptionalData.
89  */
90 void efi_apply_loadoptions_quirk(const void **load_options,
91 				 u32 *load_options_size)
92 {
93 #ifndef CONFIG_X86
94 	return;
95 #else
96 	const efi_load_option_t *load_option = *load_options;
97 	efi_load_option_unpacked_t load_option_unpacked;
98 	if (!load_option)
99 		return;
100 	if (*load_options_size < sizeof(*load_option))
101 		return;
102 	if ((load_option->attributes & ~EFI_LOAD_OPTION_BOOT_MASK) != 0)
103 		return;
104 
105 	if (!efi_load_option_unpack(&load_option_unpacked, load_option,
106 				    *load_options_size))
107 		return;
108 
109 	efi_warn_once(FW_BUG "LoadOptions is an EFI_LOAD_OPTION descriptor\n");
110 	efi_warn_once(FW_BUG "Using OptionalData as a workaround\n");
111 
112 	*load_options = load_option_unpacked.optional_data;
113 	*load_options_size = load_option_unpacked.optional_data_size;
114 #endif
115 }
116 
117 /*
118  * Convert the unicode UEFI command line to ASCII to pass to kernel.
119  * Size of memory allocated return in *cmd_line_len.
120  * Returns NULL on error.
121  */
122 char *efi_convert_cmdline(EFI_LOADED_IMAGE *image, int *cmd_line_len)
123 {
124 	const efi_char16_t *options = efi_table_attr(image, LoadOptions);
125 	u32 options_size = efi_table_attr(image, LoadOptionsSize);
126 	int options_bytes = 0, safe_options_bytes = 0; /* UTF-8 bytes */
127 	unsigned long cmdline_addr = 0;
128 	const efi_char16_t *s2;
129 	bool in_quote = false;
130 	efi_status_t status;
131 	u32 options_chars;
132 
133 	if (options_size > 0)
134 		efi_measure_tagged_event((unsigned long)options, options_size,
135 					 EFISTUB_EVT_LOAD_OPTIONS);
136 
137 	efi_apply_loadoptions_quirk((const void **)&options, &options_size);
138 	options_chars = options_size / sizeof(efi_char16_t);
139 
140 	if (options) {
141 		s2 = options;
142 		while (options_bytes < COMMAND_LINE_SIZE && options_chars--) {
143 			efi_char16_t c = *s2++;
144 
145 			if (c < 0x80) {
146 				if (c == L'\0' || c == L'\n')
147 					break;
148 				if (c == L'"')
149 					in_quote = !in_quote;
150 				else if (!in_quote && isspace((char)c))
151 					safe_options_bytes = options_bytes;
152 
153 				options_bytes++;
154 				continue;
155 			}
156 
157 			/*
158 			 * Get the number of UTF-8 bytes corresponding to a
159 			 * UTF-16 character.
160 			 * The first part handles everything in the BMP.
161 			 */
162 			options_bytes += 2 + (c >= 0x800);
163 			/*
164 			 * Add one more byte for valid surrogate pairs. Invalid
165 			 * surrogates will be replaced with 0xfffd and take up
166 			 * only 3 bytes.
167 			 */
168 			if ((c & 0xfc00) == 0xd800) {
169 				/*
170 				 * If the very last word is a high surrogate,
171 				 * we must ignore it since we can't access the
172 				 * low surrogate.
173 				 */
174 				if (!options_chars) {
175 					options_bytes -= 3;
176 				} else if ((*s2 & 0xfc00) == 0xdc00) {
177 					options_bytes++;
178 					options_chars--;
179 					s2++;
180 				}
181 			}
182 		}
183 		if (options_bytes >= COMMAND_LINE_SIZE) {
184 			options_bytes = safe_options_bytes;
185 			efi_err("Command line is too long: truncated to %d bytes\n",
186 				options_bytes);
187 		}
188 	}
189 
190 	options_bytes++; /* NUL termination */
191 
192 	status = efi_bs_call(AllocatePool, EfiLoaderData, options_bytes,
193 			     (void **)&cmdline_addr);
194 	if (status != EFI_SUCCESS)
195 		return NULL;
196 
197 	snprintf((char *)cmdline_addr, options_bytes, "%.*ls",
198 		 options_bytes - 1, options);
199 
200 	*cmd_line_len = options_bytes;
201 	return (char *)cmdline_addr;
202 }
203