Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | pmbaty | 1 | // dump-efivars.c -- EFI NVRAM variables dumper for QNX8 by Pierre-Marie Baty <pm@pmbaty.com>. Yup, from user space. |
2 | |||
3 | #include <stdint.h> |
||
4 | #include <stdio.h> |
||
5 | #include <stdlib.h> |
||
6 | #include <string.h> |
||
7 | #include <sys/mman.h> |
||
8 | #include <sys/syspage.h> |
||
9 | #include <errno.h> |
||
10 | #include <ctype.h> |
||
11 | |||
12 | #define EFI_SYSTEM_TABLE_SIGNATURE "IBI SYST" // 0x5453595320494249 |
||
13 | #define NVRAM_NVAR_ENTRY_SIGNATURE "NVAR" // 0x5241564e |
||
14 | |||
15 | #define NVRAM_NVAR_ENTRY_RUNTIME (1 << 0) |
||
16 | #define NVRAM_NVAR_ENTRY_ASCII_NAME (1 << 1) |
||
17 | #define NVRAM_NVAR_ENTRY_GUID (1 << 2) |
||
18 | #define NVRAM_NVAR_ENTRY_DATA_ONLY (1 << 3) |
||
19 | #define NVRAM_NVAR_ENTRY_EXT_HEADER (1 << 4) |
||
20 | #define NVRAM_NVAR_ENTRY_HW_ERROR_RECORD (1 << 5) |
||
21 | #define NVRAM_NVAR_ENTRY_AUTH_WRITE (1 << 6) |
||
22 | #define NVRAM_NVAR_ENTRY_VALID (1 << 7) |
||
23 | |||
24 | #define NVRAM_NVAR_ENTRY_EXT_CHECKSUM (1 << 0) |
||
25 | #define NVRAM_NVAR_ENTRY_EXT_AUTH_WRITE (1 << 4) |
||
26 | #define NVRAM_NVAR_ENTRY_EXT_TIME_BASED (1 << 5) |
||
27 | |||
28 | #define ROUND_UP(x,multiple) ((((x) + ((multiple) - 1)) / (multiple)) * (multiple)) |
||
29 | |||
30 | |||
31 | typedef struct __attribute__((packed)) efi_table_header_s |
||
32 | { |
||
33 | uint64_t signature; |
||
34 | uint32_t revision; |
||
35 | uint32_t size; |
||
36 | uint32_t crc32; |
||
37 | uint32_t reserved; |
||
38 | } efi_table_header_t; |
||
39 | |||
40 | |||
41 | typedef struct __attribute__((packed)) efi_system_table_s // NOTE: entries are NATURAL SIZE INTEGERS AND POINTERS! |
||
42 | { |
||
43 | efi_table_header_t header; |
||
44 | size_t firmware_vendor_physptr; // CHAR16 *FirmwareVendor; |
||
45 | size_t firmware_revision; // normally uint32_t, but would break alignment |
||
46 | size_t console_in_handle; |
||
47 | size_t console_in_ptr; // EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn; |
||
48 | size_t console_out_handle; |
||
49 | size_t console_out_ptr; // EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut; |
||
50 | size_t standard_error_handle; |
||
51 | size_t standard_error_ptr; // EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr; |
||
52 | size_t runtime_services_ptr; // EFI_RUNTIME_SERVICES *RuntimeServices; |
||
53 | size_t boot_services_ptr; // EFI_BOOT_SERVICES *BootServices; |
||
54 | size_t configuration_entry_count; |
||
55 | size_t configuration_table_ptr; // EFI_CONFIGURATION_TABLE *ConfigurationTable; |
||
56 | } efi_system_table_t; |
||
57 | |||
58 | |||
59 | typedef struct __attribute__((packed)) nvar_entry_s |
||
60 | { |
||
61 | uint8_t magic[4]; // "NVAR" |
||
62 | uint16_t len; // size of entry, including header |
||
63 | uint8_t next[3]; // offset to next entry in list, or empty if latest in the store |
||
64 | uint8_t flags; |
||
65 | } nvar_entry_t; |
||
66 | |||
67 | |||
68 | // global variables |
||
69 | static efi_system_table_t *systable = NULL; |
||
70 | static size_t systable_physaddr = 0; |
||
71 | |||
72 | |||
73 | static uint32_t crc32_efi_table (uint8_t *data, size_t data_len) |
||
74 | { |
||
75 | // computes the CRC32 of an EFI table, skipping the relevant bytes on the fly as the UEFI specs say |
||
76 | |||
77 | static uint32_t table[256] = { 0 }; |
||
78 | static int is_table_filled = 0; |
||
79 | const uint32_t polynomial = 0xedb88320; |
||
80 | |||
81 | size_t array_index; |
||
82 | size_t bit_index; |
||
83 | uint32_t remainder; |
||
84 | uint32_t byte; |
||
85 | uint32_t crc; |
||
86 | |||
87 | if (!is_table_filled) // someone could be lazy here and use a precomputed table. Whatever. |
||
88 | { |
||
89 | for (array_index = 0; array_index < 256; array_index++) |
||
90 | { |
||
91 | remainder = array_index; |
||
92 | for (bit_index = 0; bit_index < 8; bit_index++) |
||
93 | if (remainder & 1) |
||
94 | remainder = (remainder >> 1) ^ polynomial; |
||
95 | else |
||
96 | remainder = (remainder >> 1); |
||
97 | table[array_index] = remainder; |
||
98 | } |
||
99 | is_table_filled = 1; |
||
100 | } |
||
101 | |||
102 | crc = 0xffffffff; |
||
103 | for (array_index = 0; array_index < data_len; array_index++) |
||
104 | { |
||
105 | if ((array_index >= 16) && (array_index < 20)) |
||
106 | byte = 0; // don't account for the table's claimed CRC when computing it |
||
107 | else |
||
108 | byte = data[array_index]; |
||
109 | crc = table[byte ^ (crc & 0xff)] ^ (crc >> 8); |
||
110 | } |
||
111 | |||
112 | return (~crc); |
||
113 | } |
||
114 | |||
115 | |||
116 | int main (int argc, char **argv) |
||
117 | { |
||
118 | // program entrypoint |
||
119 | |||
120 | const size_t asinfo_count = SYSPAGE_ENTRY_SIZE (asinfo) / sizeof (struct asinfo_entry); |
||
121 | const struct asinfo_entry *asinfo_slots = SYSPAGE_ENTRY (asinfo); |
||
122 | const struct asinfo_entry *asinfo_slot; |
||
123 | const char *strings = SYSPAGE_ENTRY (strings)->data; |
||
124 | char nvar_name[256]; |
||
125 | char flags_string[256]; |
||
126 | uint32_t actual_crc; |
||
127 | nvar_entry_t *nvar; |
||
128 | char *outfile_pathname = NULL; |
||
129 | char *wanted_name = NULL; |
||
130 | void *region; |
||
131 | uint8_t *ptr; |
||
132 | int want_fulldata = 0; |
||
133 | int verbose_level = 0; |
||
134 | int match_found = 0; |
||
135 | int arg_index; |
||
136 | size_t attached_datalen; |
||
137 | size_t asinfo_idx; |
||
138 | size_t guid_idx; |
||
139 | size_t byte_idx; |
||
140 | size_t size; |
||
141 | FILE *fp; |
||
142 | |||
143 | // parse command-line arguments |
||
144 | for (arg_index = 1; arg_index < argc; arg_index++) |
||
145 | { |
||
146 | if ((strcmp (argv[arg_index], "-?") == 0) || (strcmp (argv[arg_index], "--help") == 0)) |
||
147 | { |
||
148 | printf ("usage: dump-efivars [-v|-vv] [--fulldata] [VarName] [outfile]\n"); |
||
149 | exit (0); |
||
150 | } |
||
151 | else if (strcmp (argv[arg_index], "-v") == 0) |
||
152 | verbose_level = 1; |
||
153 | else if (strcmp (argv[arg_index], "-vv") == 0) |
||
154 | verbose_level = 2; |
||
155 | else if (strcmp (argv[arg_index], "--fulldata") == 0) |
||
156 | want_fulldata = 1; |
||
157 | else if (wanted_name == NULL) |
||
158 | wanted_name = argv[arg_index]; |
||
159 | else if (outfile_pathname == NULL) |
||
160 | outfile_pathname = argv[arg_index]; |
||
161 | else |
||
162 | { |
||
163 | fprintf (stderr, "error: unknown argument '%s'\n", argv[arg_index]); |
||
164 | fprintf (stderr, "usage: dump-efivars [-v|-vv] [VarName] [outfile]\n"); |
||
165 | exit (1); |
||
166 | } |
||
167 | } |
||
168 | |||
169 | // we got the start of the asinfo array through the syspage pointer in asinfo_slots |
||
170 | // now scan all accessible physical RAM regions that the firmware doesn't want allocated to applications |
||
171 | for (asinfo_idx = 0; asinfo_idx < asinfo_count; asinfo_idx++) |
||
172 | { |
||
173 | asinfo_slot = &asinfo_slots[asinfo_idx]; |
||
174 | if (asinfo_slot->start == 0) |
||
175 | continue; // skip the first slot (ISA space) - there's no chance our stuff is in it |
||
176 | else if (strcmp (strings + asinfo_slot->name, "ram") != 0) |
||
177 | continue; // skip anything that's NOT non-allocatable RAM |
||
178 | |||
179 | // ask the kernel to map this physical region to a pointer in our virtual address space |
||
180 | size = asinfo_slot->end - asinfo_slot->start; |
||
181 | region = mmap (NULL, size, PROT_READ, MAP_PRIVATE | MAP_PHYS, NOFD, asinfo_slot->start); |
||
182 | if (region == NULL) |
||
183 | continue; // if this region can't be mapped (for whatever reason), skip it |
||
184 | |||
185 | // now scan this physical memory region for the EFI system table signature |
||
186 | for (ptr = (uint8_t *) ROUND_UP ((size_t) region, sizeof (size_t)); (size_t) ptr + sizeof (efi_system_table_t) < (size_t) region + size; ptr += sizeof (size_t)) |
||
187 | { |
||
188 | if (*((uint64_t *) ptr) != *((uint64_t *) EFI_SYSTEM_TABLE_SIGNATURE)) |
||
189 | continue; // skip anything that doesn't look like an EFI system table signature |
||
190 | else if ((((efi_system_table_t *) ptr)->header.size < sizeof (efi_system_table_t)) || (((efi_system_table_t *) ptr)->header.size > 255)) |
||
191 | continue; // if table is too short or claims more than 255 bytes, skip it |
||
192 | else if (ptr + ((efi_system_table_t *) ptr)->header.size >= (uint8_t *) region + size) |
||
193 | continue; // if table's claimed size doesn't fit in region, skip it |
||
194 | else if ((ptr[10] != 2) || (ptr[11] != 0)) |
||
195 | continue; // if revision is NOT 2.xx, skip it |
||
196 | else if (((efi_system_table_t *) ptr)->header.reserved != 0) |
||
197 | continue; // if reserved field is not 0 as the specs say, skip it |
||
198 | |||
199 | actual_crc = crc32_efi_table (ptr, ((efi_system_table_t *) ptr)->header.size); |
||
200 | if (actual_crc != ((efi_system_table_t *) ptr)->header.crc32) |
||
201 | continue; // if CRCs mismatch, forget about this table and skip it |
||
202 | |||
203 | // CRC is valid: this is a usable UEFI system table |
||
204 | systable = (efi_system_table_t *) ptr; |
||
205 | systable_physaddr = asinfo_slot->start + ((size_t) ptr - (size_t) region); |
||
206 | break; // stop looking right here |
||
207 | } |
||
208 | |||
209 | if (systable != NULL) |
||
210 | break; // if we found the system table already, no need to scan other regions |
||
211 | munmap (region, size); // unmap this physical memory region after it's been scanned |
||
212 | } |
||
213 | |||
214 | // consistency check |
||
215 | if (systable == NULL) |
||
216 | { |
||
217 | fprintf (stderr, "EFI system table not found\n"); |
||
218 | exit (1); // if we found nothing, bail out |
||
219 | } |
||
220 | |||
221 | // in verbose mode, jump in joy and yell the world what we found |
||
222 | if (verbose_level > 1) |
||
223 | { |
||
224 | fprintf (stderr, "Valid EFI header at Address %016zx\n", systable_physaddr); |
||
225 | fprintf (stderr, "---------------------------------------------\n"); |
||
226 | fprintf (stderr, "System: Table Structure size %08x revision %08x\n", systable->header.size, systable->header.revision); |
||
227 | fprintf (stderr, "ConIn (%016zx) ConOut (%016zx) StdErr (%016zx)\n", systable->console_in_ptr, systable->console_out_ptr, systable->standard_error_ptr); |
||
228 | fprintf (stderr, "Runtime Services %016zx\n", systable->runtime_services_ptr); |
||
229 | fprintf (stderr, "Boot Services %016zx\n", systable->boot_services_ptr); |
||
230 | } |
||
231 | |||
232 | // the EFI header is in the memory region [asinfo_slot->start - asinfo_slot->end] |
||
233 | // This region will also contain the NVARs, so let's scan for them, brute-force way. |
||
234 | // Yes, this is awful. My mother definitely told me not to do that, but I couldn't help it 0:-* |
||
235 | // FIXME: do a proper hierarchical reconstruction and assign GUIDs and detached payloads etc... |
||
236 | for (ptr = region;;) |
||
237 | { |
||
238 | while ( ((size_t) ptr + sizeof (nvar_entry_t) < (size_t) region + size) |
||
239 | && (*((uint32_t *) ptr) != *((uint32_t *) NVRAM_NVAR_ENTRY_SIGNATURE))) |
||
240 | ptr++; // look for the next "NVAR" tag (NOTE THAT THEY ARE STORED UNALIGNED!) |
||
241 | |||
242 | if ((size_t) ptr + sizeof (nvar_entry_t) >= (size_t) region + size) |
||
243 | break; // end of region reached |
||
244 | |||
245 | nvar = (nvar_entry_t *) ptr; // access it as a NVRAM UEFI variable |
||
246 | |||
247 | // translate NVAR flags |
||
248 | flags_string[0] = 0; |
||
249 | if (nvar->flags & NVRAM_NVAR_ENTRY_RUNTIME) strcat (flags_string, ", runtime"); |
||
250 | if (nvar->flags & NVRAM_NVAR_ENTRY_ASCII_NAME) strcat (flags_string, ", ASCII name"); |
||
251 | if (nvar->flags & NVRAM_NVAR_ENTRY_GUID) strcat (flags_string, ", GUID"); |
||
252 | if (nvar->flags & NVRAM_NVAR_ENTRY_DATA_ONLY) strcat (flags_string, ", data only"); |
||
253 | if (nvar->flags & NVRAM_NVAR_ENTRY_EXT_HEADER) strcat (flags_string, ", ext. hdr"); |
||
254 | if (nvar->flags & NVRAM_NVAR_ENTRY_HW_ERROR_RECORD) strcat (flags_string, ", HW err rec"); |
||
255 | if (nvar->flags & NVRAM_NVAR_ENTRY_AUTH_WRITE) strcat (flags_string, ", auth write"); |
||
256 | if (nvar->flags & NVRAM_NVAR_ENTRY_VALID) strcat (flags_string, ", valid"); |
||
257 | |||
258 | // skip header |
||
259 | ptr += sizeof (nvar_entry_t); |
||
260 | |||
261 | // read GUID index and advance |
||
262 | guid_idx = *ptr++; |
||
263 | |||
264 | // read name and advance |
||
265 | for (byte_idx = 0; ; byte_idx++) |
||
266 | { |
||
267 | nvar_name[byte_idx] = *ptr; // either ASCII or UTF-16LE, so read first byte |
||
268 | ptr += (nvar->flags & NVRAM_NVAR_ENTRY_ASCII_NAME ? 1 : 2); |
||
269 | if ((nvar_name[byte_idx] == 0) || !isalnum (nvar_name[byte_idx])) |
||
270 | break; // stop on end of name or invalid character |
||
271 | } |
||
272 | if ((byte_idx == 0) || (nvar_name[byte_idx] != 0)) |
||
273 | continue; // if varname is empty or invalid, skip this NVAR |
||
274 | |||
275 | if ((wanted_name != NULL) && (strcmp (nvar_name, wanted_name) != 0)) |
||
276 | continue; // if this variable isn't the one we want, skip it |
||
277 | |||
278 | // if we shouldn't display this variable because it's invalid for the firmware itself, skip it |
||
279 | if (!(nvar->flags & NVRAM_NVAR_ENTRY_VALID)) |
||
280 | continue; // there's no point in displaying junk data |
||
281 | |||
282 | match_found = 1; // remember we found a match |
||
283 | |||
284 | // measure attached data length |
||
285 | attached_datalen = nvar->len - (sizeof (nvar_entry_t) + 1 + (nvar->flags & NVRAM_NVAR_ENTRY_ASCII_NAME ? 1 : 2) * (strlen (nvar_name) + 1)); |
||
286 | |||
287 | // do we just want ONE variable AND its content dumped to a file ? |
||
288 | if ((wanted_name != NULL) && (outfile_pathname != NULL)) |
||
289 | { |
||
290 | fp = fopen (outfile_pathname, "wb"); // open outfile if we have one |
||
291 | if (fp == NULL) |
||
292 | { |
||
293 | fprintf (stderr, "error: can't open \"%s\" for writing: %s\n", outfile_pathname, strerror (errno)); |
||
294 | exit (1); |
||
295 | } |
||
296 | fwrite (ptr, 1, attached_datalen, fp); // dump NVAR data to file if that's what we want |
||
297 | fclose (fp); // close the output file |
||
298 | } |
||
299 | else // print all variables, or just one to stdout |
||
300 | { |
||
301 | // in verbose mode, print NVAR size, 'next' field and attributes |
||
302 | if (verbose_level > 0) |
||
303 | fprintf (stdout, "%04x %02x%02x%02x %02x ", nvar->len, nvar->next[0], nvar->next[1], nvar->next[2], nvar->flags); |
||
304 | |||
305 | // print NVAR name and data |
||
306 | fprintf (stdout, "%s = ", nvar_name); |
||
307 | if (want_fulldata || (attached_datalen < 48)) |
||
308 | { |
||
309 | fprintf (stdout, "["); |
||
310 | while ((size_t) ptr < (size_t) nvar + nvar->len) |
||
311 | fprintf (stdout, "%02x", *ptr++); // print data bytes if it's short enough, or if we requested it explicitly |
||
312 | fprintf (stdout, "]"); |
||
313 | } |
||
314 | else |
||
315 | fprintf (stdout, "<%zd bytes>", attached_datalen); // else just print data size |
||
316 | |||
317 | // now print flags string |
||
318 | if (verbose_level > 0) |
||
319 | fprintf (stdout, "%s - GUID idx %zu", flags_string, guid_idx); |
||
320 | else if (nvar->flags & NVRAM_NVAR_ENTRY_AUTH_WRITE) |
||
321 | fprintf (stdout, " (LOCKED)"); |
||
322 | |||
323 | fprintf (stdout, "\n"); |
||
324 | } |
||
325 | } |
||
326 | |||
327 | munmap (region, size); // unmap the physical region upon exit |
||
328 | |||
329 | // if we found nothing, print an error message |
||
330 | if ((wanted_name != NULL) && !match_found) |
||
331 | fprintf (stderr, "No variable named \"%s\" found.\n", wanted_name); |
||
332 | |||
333 | // and return with a relevant exit code |
||
334 | exit (match_found ? 0 : 1); |
||
335 | } |