// dump-efivars.c -- EFI NVRAM variables dumper for QNX8 by Pierre-Marie Baty <pm@pmbaty.com>. Yup, from user space.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syspage.h>
#include <errno.h>
#include <ctype.h>
#define EFI_SYSTEM_TABLE_SIGNATURE "IBI SYST" // 0x5453595320494249
#define NVRAM_NVAR_ENTRY_SIGNATURE "NVAR" // 0x5241564e
#define NVRAM_NVAR_ENTRY_RUNTIME (1 << 0)
#define NVRAM_NVAR_ENTRY_ASCII_NAME (1 << 1)
#define NVRAM_NVAR_ENTRY_GUID (1 << 2)
#define NVRAM_NVAR_ENTRY_DATA_ONLY (1 << 3)
#define NVRAM_NVAR_ENTRY_EXT_HEADER (1 << 4)
#define NVRAM_NVAR_ENTRY_HW_ERROR_RECORD (1 << 5)
#define NVRAM_NVAR_ENTRY_AUTH_WRITE (1 << 6)
#define NVRAM_NVAR_ENTRY_VALID (1 << 7)
#define NVRAM_NVAR_ENTRY_EXT_CHECKSUM (1 << 0)
#define NVRAM_NVAR_ENTRY_EXT_AUTH_WRITE (1 << 4)
#define NVRAM_NVAR_ENTRY_EXT_TIME_BASED (1 << 5)
#define ROUND_UP(x,multiple) ((((x) + ((multiple) - 1)) / (multiple)) * (multiple))
typedef struct __attribute__((packed)) efi_table_header_s
{
uint64_t signature;
uint32_t revision;
uint32_t size;
uint32_t crc32;
uint32_t reserved;
} efi_table_header_t;
typedef struct __attribute__((packed)) efi_system_table_s // NOTE: entries are NATURAL SIZE INTEGERS AND POINTERS!
{
efi_table_header_t header;
size_t firmware_vendor_physptr; // CHAR16 *FirmwareVendor;
size_t firmware_revision; // normally uint32_t, but would break alignment
size_t console_in_handle;
size_t console_in_ptr; // EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn;
size_t console_out_handle;
size_t console_out_ptr; // EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut;
size_t standard_error_handle;
size_t standard_error_ptr; // EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr;
size_t runtime_services_ptr; // EFI_RUNTIME_SERVICES *RuntimeServices;
size_t boot_services_ptr; // EFI_BOOT_SERVICES *BootServices;
size_t configuration_entry_count;
size_t configuration_table_ptr; // EFI_CONFIGURATION_TABLE *ConfigurationTable;
} efi_system_table_t;
typedef struct __attribute__((packed)) nvar_entry_s
{
uint8_t magic[4]; // "NVAR"
uint16_t len; // size of entry, including header
uint8_t next[3]; // offset to next entry in list, or empty if latest in the store
uint8_t flags;
} nvar_entry_t;
// global variables
static efi_system_table_t *systable = NULL;
static size_t systable_physaddr = 0;
static uint32_t crc32_efi_table (uint8_t *data, size_t data_len)
{
// computes the CRC32 of an EFI table, skipping the relevant bytes on the fly as the UEFI specs say
static uint32_t table[256] = { 0 };
static int is_table_filled = 0;
const uint32_t polynomial = 0xedb88320;
size_t array_index;
size_t bit_index;
uint32_t remainder;
uint32_t byte;
uint32_t crc;
if (!is_table_filled) // someone could be lazy here and use a precomputed table. Whatever.
{
for (array_index = 0; array_index < 256; array_index++)
{
remainder = array_index;
for (bit_index = 0; bit_index < 8; bit_index++)
if (remainder & 1)
remainder = (remainder >> 1) ^ polynomial;
else
remainder = (remainder >> 1);
table[array_index] = remainder;
}
is_table_filled = 1;
}
crc = 0xffffffff;
for (array_index = 0; array_index < data_len; array_index++)
{
if ((array_index >= 16) && (array_index < 20))
byte = 0; // don't account for the table's claimed CRC when computing it
else
byte = data[array_index];
crc = table[byte ^ (crc & 0xff)] ^ (crc >> 8);
}
return (~crc);
}
int main (int argc, char **argv)
{
// program entrypoint
const size_t asinfo_count = SYSPAGE_ENTRY_SIZE (asinfo) / sizeof (struct asinfo_entry);
const struct asinfo_entry *asinfo_slots = SYSPAGE_ENTRY (asinfo);
const struct asinfo_entry *asinfo_slot;
const char *strings = SYSPAGE_ENTRY (strings)->data;
char nvar_name[256];
char flags_string[256];
uint32_t actual_crc;
nvar_entry_t *nvar;
char *outfile_pathname = NULL;
char *wanted_name = NULL;
void *region;
uint8_t *ptr;
int want_fulldata = 0;
int verbose_level = 0;
int match_found = 0;
int arg_index;
size_t attached_datalen;
size_t asinfo_idx;
size_t guid_idx;
size_t byte_idx;
size_t size;
FILE *fp;
// parse command-line arguments
for (arg_index = 1; arg_index < argc; arg_index++)
{
if ((strcmp (argv
[arg_index
], "-?") == 0) || (strcmp (argv
[arg_index
], "--help") == 0))
{
printf ("usage: dump-efivars [-v|-vv] [--fulldata] [VarName] [outfile]\n");
}
else if (strcmp (argv
[arg_index
], "-v") == 0)
verbose_level = 1;
else if (strcmp (argv
[arg_index
], "-vv") == 0)
verbose_level = 2;
else if (strcmp (argv
[arg_index
], "--fulldata") == 0)
want_fulldata = 1;
else if (wanted_name == NULL)
wanted_name = argv[arg_index];
else if (outfile_pathname == NULL)
outfile_pathname = argv[arg_index];
else
{
fprintf (stderr
, "error: unknown argument '%s'\n", argv
[arg_index
]);
fprintf (stderr
, "usage: dump-efivars [-v|-vv] [VarName] [outfile]\n");
}
}
// we got the start of the asinfo array through the syspage pointer in asinfo_slots
// now scan all accessible physical RAM regions that the firmware doesn't want allocated to applications
for (asinfo_idx = 0; asinfo_idx < asinfo_count; asinfo_idx++)
{
asinfo_slot = &asinfo_slots[asinfo_idx];
if (asinfo_slot->start == 0)
continue; // skip the first slot (ISA space) - there's no chance our stuff is in it
else if (strcmp (strings
+ asinfo_slot
->name
, "ram") != 0)
continue; // skip anything that's NOT non-allocatable RAM
// ask the kernel to map this physical region to a pointer in our virtual address space
size = asinfo_slot->end - asinfo_slot->start;
region = mmap (NULL, size, PROT_READ, MAP_PRIVATE | MAP_PHYS, NOFD, asinfo_slot->start);
if (region == NULL)
continue; // if this region can't be mapped (for whatever reason), skip it
// now scan this physical memory region for the EFI system table signature
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))
{
if (*((uint64_t *) ptr) != *((uint64_t *) EFI_SYSTEM_TABLE_SIGNATURE))
continue; // skip anything that doesn't look like an EFI system table signature
else if ((((efi_system_table_t *) ptr)->header.size < sizeof (efi_system_table_t)) || (((efi_system_table_t *) ptr)->header.size > 255))
continue; // if table is too short or claims more than 255 bytes, skip it
else if (ptr + ((efi_system_table_t *) ptr)->header.size >= (uint8_t *) region + size)
continue; // if table's claimed size doesn't fit in region, skip it
else if ((ptr[10] != 2) || (ptr[11] != 0))
continue; // if revision is NOT 2.xx, skip it
else if (((efi_system_table_t *) ptr)->header.reserved != 0)
continue; // if reserved field is not 0 as the specs say, skip it
actual_crc = crc32_efi_table (ptr, ((efi_system_table_t *) ptr)->header.size);
if (actual_crc != ((efi_system_table_t *) ptr)->header.crc32)
continue; // if CRCs mismatch, forget about this table and skip it
// CRC is valid: this is a usable UEFI system table
systable = (efi_system_table_t *) ptr;
systable_physaddr = asinfo_slot->start + ((size_t) ptr - (size_t) region);
break; // stop looking right here
}
if (systable != NULL)
break; // if we found the system table already, no need to scan other regions
munmap (region, size); // unmap this physical memory region after it's been scanned
}
// consistency check
if (systable == NULL)
{
fprintf (stderr
, "EFI system table not found\n");
exit (1); // if we found nothing, bail out
}
// in verbose mode, jump in joy and yell the world what we found
if (verbose_level > 1)
{
fprintf (stderr
, "Valid EFI header at Address %016zx\n", systable_physaddr
);
fprintf (stderr
, "---------------------------------------------\n");
fprintf (stderr
, "System: Table Structure size %08x revision %08x\n", systable
->header.
size, systable
->header.
revision);
fprintf (stderr
, "ConIn (%016zx) ConOut (%016zx) StdErr (%016zx)\n", systable
->console_in_ptr
, systable
->console_out_ptr
, systable
->standard_error_ptr
);
fprintf (stderr
, "Runtime Services %016zx\n", systable
->runtime_services_ptr
);
fprintf (stderr
, "Boot Services %016zx\n", systable
->boot_services_ptr
);
}
// the EFI header is in the memory region [asinfo_slot->start - asinfo_slot->end]
// This region will also contain the NVARs, so let's scan for them, brute-force way.
// Yes, this is awful. My mother definitely told me not to do that, but I couldn't help it 0:-*
// FIXME: do a proper hierarchical reconstruction and assign GUIDs and detached payloads etc...
for (ptr = region;;)
{
while ( ((size_t) ptr + sizeof (nvar_entry_t) < (size_t) region + size)
&& (*((uint32_t *) ptr) != *((uint32_t *) NVRAM_NVAR_ENTRY_SIGNATURE)))
ptr++; // look for the next "NVAR" tag (NOTE THAT THEY ARE STORED UNALIGNED!)
if ((size_t) ptr + sizeof (nvar_entry_t) >= (size_t) region + size)
break; // end of region reached
nvar = (nvar_entry_t *) ptr; // access it as a NVRAM UEFI variable
// translate NVAR flags
flags_string[0] = 0;
if (nvar
->flags
& NVRAM_NVAR_ENTRY_RUNTIME
) strcat (flags_string
, ", runtime");
if (nvar
->flags
& NVRAM_NVAR_ENTRY_ASCII_NAME
) strcat (flags_string
, ", ASCII name");
if (nvar
->flags
& NVRAM_NVAR_ENTRY_GUID
) strcat (flags_string
, ", GUID");
if (nvar
->flags
& NVRAM_NVAR_ENTRY_DATA_ONLY
) strcat (flags_string
, ", data only");
if (nvar
->flags
& NVRAM_NVAR_ENTRY_EXT_HEADER
) strcat (flags_string
, ", ext. hdr");
if (nvar
->flags
& NVRAM_NVAR_ENTRY_HW_ERROR_RECORD
) strcat (flags_string
, ", HW err rec");
if (nvar
->flags
& NVRAM_NVAR_ENTRY_AUTH_WRITE
) strcat (flags_string
, ", auth write");
if (nvar
->flags
& NVRAM_NVAR_ENTRY_VALID
) strcat (flags_string
, ", valid");
// skip header
ptr += sizeof (nvar_entry_t);
// read GUID index and advance
guid_idx = *ptr++;
// read name and advance
for (byte_idx = 0; ; byte_idx++)
{
nvar_name[byte_idx] = *ptr; // either ASCII or UTF-16LE, so read first byte
ptr += (nvar->flags & NVRAM_NVAR_ENTRY_ASCII_NAME ? 1 : 2);
if ((nvar_name
[byte_idx
] == 0) || !isalnum (nvar_name
[byte_idx
]))
break; // stop on end of name or invalid character
}
if ((byte_idx == 0) || (nvar_name[byte_idx] != 0))
continue; // if varname is empty or invalid, skip this NVAR
if ((wanted_name
!= NULL
) && (strcmp (nvar_name
, wanted_name
) != 0))
continue; // if this variable isn't the one we want, skip it
// if we shouldn't display this variable because it's invalid for the firmware itself, skip it
if (!(nvar->flags & NVRAM_NVAR_ENTRY_VALID))
continue; // there's no point in displaying junk data
match_found = 1; // remember we found a match
// measure attached data length
attached_datalen
= nvar
->len
- (sizeof (nvar_entry_t
) + 1 + (nvar
->flags
& NVRAM_NVAR_ENTRY_ASCII_NAME
? 1 : 2) * (strlen (nvar_name
) + 1));
// do we just want ONE variable AND its content dumped to a file ?
if ((wanted_name != NULL) && (outfile_pathname != NULL))
{
fp
= fopen (outfile_pathname
, "wb"); // open outfile if we have one
if (fp == NULL)
{
fprintf (stderr
, "error: can't open \"%s\" for writing: %s\n", outfile_pathname
, strerror (errno
));
}
fwrite (ptr
, 1, attached_datalen
, fp
); // dump NVAR data to file if that's what we want
fclose (fp
); // close the output file
}
else // print all variables, or just one to stdout
{
// in verbose mode, print NVAR size, 'next' field and attributes
if (verbose_level > 0)
fprintf (stdout
, "%04x %02x%02x%02x %02x ", nvar
->len
, nvar
->next
[0], nvar
->next
[1], nvar
->next
[2], nvar
->flags
);
// print NVAR name and data
fprintf (stdout
, "%s = ", nvar_name
);
if (want_fulldata || (attached_datalen < 48))
{
while ((size_t) ptr < (size_t) nvar + nvar->len)
fprintf (stdout
, "%02x", *ptr
++); // print data bytes if it's short enough, or if we requested it explicitly
}
else
fprintf (stdout
, "<%zd bytes>", attached_datalen
); // else just print data size
// now print flags string
if (verbose_level > 0)
fprintf (stdout
, "%s - GUID idx %zu", flags_string
, guid_idx
);
else if (nvar->flags & NVRAM_NVAR_ENTRY_AUTH_WRITE)
}
}
munmap (region, size); // unmap the physical region upon exit
// if we found nothing, print an error message
if ((wanted_name != NULL) && !match_found)
fprintf (stderr
, "No variable named \"%s\" found.\n", wanted_name
);
// and return with a relevant exit code
exit (match_found
? 0 : 1);
}