// 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);  
}