Translate

Image of Operating System Concepts
Image of Android Wireless Application Development
Image of Modern Operating Systems (3rd Edition)
Image of Beginning Google Maps API 3

RNG Protocol Error in Lenovo ThinkPad Firmware

The UEFI specification defines a Random Number Generator protocol (RNG), which can be used to provide random numbers for use in nonces, key generators, signature schemes and more. This protocol was first introduced in version 2.4 of the specification.

A UEFI RNG service that implements this protocol takes an optional input value that identifies an RNG algorithm and provides a RNG value based on the input value and internal state, including the state of its entropy sources. When a Deterministic Random Bit Generator (DRBG) is used on the output of the raw entropy source, its security level must be at least 256 bits.

The UEFI specification allows you to select the source of the random number from a list of random-number algorithms or even define your own unique RNG using you own unique RNG GUID. Some of these are standard algorithms and are based on various NIST (National Institute of Standards and Technology) publications such as NIST SP 800-90 which provides guidelines for ensuring adequate range and randomness for software random number generators.

In the case of hardware random number generators, there is a default GUID (EFI_RNG_ALGORITHM_RAW) which gets the raw value from the hardware or NULL, which uses the driver’s default algorithm which can be hardware or software-based.

Here is a small UEFI shell utility that checks your firmware for available RNGs:

//
//  Copyright (c) 2016  Finnbarr P. Murphy.   All rights reserved.
//
//  Display UEFI RNG (Random Number Generator) algorithm information
//
//  License: BSD License
//

#include <Uefi.h>

#include <Uefi/UefiSpec.h>
#include <Guid/GlobalVariable.h>

#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/ShellLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/MemoryAllocationLib.h>

#include <Protocol/EfiShell.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/Rng.h>

#define UTILITY_VERSION L"0.1"
#undef DEBUG


//  from Microsoft document - not part of EDK2 at present!
#define EFI_RNG_SERVICE_BINDING_PROTOCOL_GUID \
    {0xe417a4a2, 0x0843, 0x4619, {0xbf, 0x11, 0x5c, 0xe8, 0x2a, 0xfc, 0xfc, 0x59}}

#define EFI_RNG_ALGORITHM_RAW_GUID \
    {0xe43176d7, 0xb6e8, 0x4827, {0xb7, 0x84, 0x7f, 0xfd, 0xc4, 0xb6, 0x85, 0x61 }}


EFI_GUID gEfiRngAlgorithmRawGuid = EFI_RNG_ALGORITHM_RAW_GUID;    
EFI_GUID gEfiRngAlgorithmSp80090Ctr256Guid = EFI_RNG_ALGORITHM_SP800_90_CTR_256_GUID;


VOID
PrintAlg(EFI_RNG_ALGORITHM *RngAlg) 
{
    Print (L"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x   ", RngAlg->Data1,
            RngAlg->Data2, RngAlg->Data3, RngAlg->Data4[0], RngAlg->Data4[1],
            RngAlg->Data4[2], RngAlg->Data4[3], RngAlg->Data4[4],
            RngAlg->Data4[5], RngAlg->Data4[6], RngAlg->Data4[7]);

    if (CompareGuid(RngAlg, &gEfiRngAlgorithmRawGuid)) {
         Print(L"Raw");
    }

    if (CompareGuid(RngAlg, &gEfiRngAlgorithmSp80090Ctr256Guid)) {
         Print(L"NIST SP800-90 AES-CTR-256");
    }

    // Add other algorithms here when implemented in EDK2
}


EFI_STATUS
PrintDeviceRng( UINTN Index,
                EFI_RNG_PROTOCOL *Rng)
{
    EFI_STATUS Status = EFI_SUCCESS;
    EFI_RNG_ALGORITHM RngAlgList[10];
    EFI_RNG_ALGORITHM *RngAlg;
    UINTN RngAlgCount = 0;
    UINTN RngAlgListSize = 0;
    UINT8 *Rand;
    UINTN RandSize = 32;

    Status = Rng->GetInfo( Rng, 
                           &RngAlgListSize,
                           RngAlgList);
    if (Status != EFI_BUFFER_TOO_SMALL) {
        Print (L"ERROR: Rng->GetInfo [%n]\n", Status);
        return Status;
    }

#ifdef DEBUG
    Print(L"Rng->GetInfo Size: %d %d\n", RngAlgListSize, sizeof(EFI_RNG_ALGORITHM));
#endif

    RngAlgCount = RngAlgListSize / sizeof(EFI_RNG_ALGORITHM);
    if (RngAlgCount == 0) {
        //
        // This is a hack for poor IBV firmware on Lenovo T450
        //
        Rand = AllocatePool(RandSize);
        if (Rand == NULL) {
            Print (L"ERROR: AllocatePool\n");
            return Status;
        }

        Status = Rng->GetRNG(Rng, NULL, RandSize, Rand);
        FreePool(Rand);
        if (EFI_ERROR (Status)) {
            Print (L"ERROR: GetRNG default failed [%d]", Status);
            return Status;
        }
        
        Print (L"Device: %d  Number of supported algorithms: %d\n", Index, 1);
        Print (L"\n    %d) ", 1);
        PrintAlg((EFI_RNG_ALGORITHM *)&gEfiRngAlgorithmRawGuid);
    } else {
        Print (L"Device: %d  Number of supported algorithms: %d\n", Index, RngAlgCount);

        Status = Rng->GetInfo (Rng, &RngAlgListSize, RngAlgList);
        if (EFI_ERROR (Status)) {
            Print (L"ERROR: GetInfo failed [%d]", Status);
            return Status;
        }

        for (int index = 0; index < RngAlgCount; index++) {
             RngAlg = (EFI_RNG_ALGORITHM *)(&RngAlgList[index]);
             Print (L"\n    %d) ", index);
             PrintAlg(RngAlg);
        }
    }
    
    Print (L"\n\n");

    return Status;
}


VOID
Usage(CHAR16 *Str)
{
    Print(L"Usage: %s [-V|--version]\n", Str);
}


INTN
EFIAPI
ShellAppMain(UINTN Argc, CHAR16 **Argv)
{
    EFI_GUID gEfiRngProtocolGuid = EFI_RNG_PROTOCOL_GUID;
    EFI_GUID gEfiRngServiceProtocolGuid = EFI_RNG_SERVICE_BINDING_PROTOCOL_GUID;
    EFI_STATUS Status = EFI_SUCCESS;
    EFI_RNG_PROTOCOL *Rng;
    EFI_HANDLE *HandleBuffer;
    UINTN HandleCount = 0;


    if (Argc == 2) {
        if (!StrCmp(Argv[1], L"--version") || 
            !StrCmp(Argv[1], L"-V")) {
            Print(L"Version: %s\n", UTILITY_VERSION);
            return Status;
        }
        if (!StrCmp(Argv[1], L"--help") ||
            !StrCmp(Argv[1], L"-h") ||
            !StrCmp(Argv[1], L"-?")) {
            Usage(Argv[0]);
            return Status;
        }
    }

    // catchall for all other cases
    if (Argc > 1) {
        Usage(Argv[0]);
        return Status;
    }

    // Try locating EFI_RNG_SERVICE_BINDING handles - probably will not find
    Status = gBS->LocateHandleBuffer( ByProtocol,
                                      &gEfiRngServiceProtocolGuid,
                                      NULL,
                                      &HandleCount,
                                      &HandleBuffer);
    if (EFI_ERROR (Status)) {
        Print(L"No EFI_RNG_SERVICE_BINDING_PROTOCOL handles found");
    } else {
        Print(L"RNG service binding protocol handles found [%d]", HandleCount);
    }
    Print(L"\n\n");


    // Try locating EFI_RNG_PROTOCOL handles
    Status = gBS->LocateHandleBuffer( ByProtocol,
                                      &gEfiRngProtocolGuid,
                                      NULL,
                                      &HandleCount,
                                      &HandleBuffer);
    if (EFI_ERROR (Status) || HandleCount == 0 ) {
        Print(L"ERROR: No EFI_RNG_PROTOCOL handles found\n");
        return Status;
    }

#ifdef DEBUG
    Print(L"RNG protocol found [%d]\n", HandleCount);
#endif

    for (int Index = 0; Index < HandleCount; Index++) {
        Status = gBS->HandleProtocol( HandleBuffer[Index],
                                      &gEfiRngProtocolGuid,
                                      (VOID*) &Rng);
        if (!EFI_ERROR (Status)) {
            PrintDeviceRng(Index, Rng);
        } else {
            Print(L"ERROR: HandleProtocol [%d]\n", Status);
        }
    }
    FreePool(HandleBuffer);

    return EFI_SUCCESS;
} 

Here is the corresponding UDK2015 .inf build file:

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = ShowRNG 
  FILE_GUID                      = 4ea88c52-7491-4dfd-0055-747010f3ce51
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib
  VALID_ARCHITECTURES            = X64

[Sources]
  ShowRNG.c

[Packages]
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec 

[LibraryClasses]
  ShellCEntryLib   
  ShellLib
  BaseLib
  BaseMemoryLib
  UefiLib
  
[Protocols]
  
[BuildOptions]

[Pcd]


I built the utility on a 64-bit Fedora 24 platform using GCC and UDK2015. I have not tried building a 32-bit utility nor have I build it using Visual Studio or other development frameworks – so do not be surprised if you have modify either the code or the build recipe in these cases.

I tested the utility on a Lenovo T450 using firmware version JBET60WW (1.24) and was surprised to find that the firmware did not appear to support any RNGs as evidenced by the zero RNG algorithm count returned. However, by explicitly, testing for the default RNG if the count was zero, it was possible to determine that the T450 did in fact at least support the default RNG.

Perhaps, I am not parsing the UEFI specification correctly but I would expect the RNG count returned by GetInfo to include the default RNG. Interestingly, when I build and load the UDK2015 test RNG DXE driver which contains a reference counter mode DRBG (Deterministic Random Bit Generator) conforming to NIST SP 800-90a, the algorithm count returned by GetInfo jumps to 2. This leads me to suspect that their is a bug in the firmware w.r.t. to the RNG protocol implementation.

Please let me know if I am incorrect in my assumptions or observations.

Leave a Reply