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 | } |