/** @file

  Blob verifier library that uses SEV hashes table.  The hashes table holds the
  allowed hashes of the kernel, initrd, and cmdline blobs.

  Copyright (C) 2021, IBM Corporation

  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include <Library/BaseCryptLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/BlobVerifierLib.h>

/**
  The SEV Hashes table must be in encrypted memory and has the table
  and its entries described by

  <GUID>|UINT16 <len>|<data>

  With the whole table GUID being 9438d606-4f22-4cc9-b479-a793d411fd21

  The current possible table entries are for the kernel, the initrd
  and the cmdline:

  4de79437-abd2-427f-b835-d5b172d2045b  kernel
  44baf731-3a2f-4bd7-9af1-41e29169781d  initrd
  97d02dd8-bd20-4c94-aa78-e7714d36ab2a  cmdline

  The size of the entry is used to identify the hash, but the
  expectation is that it will be 32 bytes of SHA-256.
**/

#define SEV_HASH_TABLE_GUID \
  (GUID) { 0x9438d606, 0x4f22, 0x4cc9, { 0xb4, 0x79, 0xa7, 0x93, 0xd4, 0x11, 0xfd, 0x21 } }
#define SEV_KERNEL_HASH_GUID \
  (GUID) { 0x4de79437, 0xabd2, 0x427f, { 0xb8, 0x35, 0xd5, 0xb1, 0x72, 0xd2, 0x04, 0x5b } }
#define SEV_INITRD_HASH_GUID \
  (GUID) { 0x44baf731, 0x3a2f, 0x4bd7, { 0x9a, 0xf1, 0x41, 0xe2, 0x91, 0x69, 0x78, 0x1d } }
#define SEV_CMDLINE_HASH_GUID \
  (GUID) { 0x97d02dd8, 0xbd20, 0x4c94, { 0xaa, 0x78, 0xe7, 0x71, 0x4d, 0x36, 0xab, 0x2a } }

STATIC CONST EFI_GUID  mSevKernelHashGuid  = SEV_KERNEL_HASH_GUID;
STATIC CONST EFI_GUID  mSevInitrdHashGuid  = SEV_INITRD_HASH_GUID;
STATIC CONST EFI_GUID  mSevCmdlineHashGuid = SEV_CMDLINE_HASH_GUID;

#pragma pack (1)
typedef struct {
  GUID      Guid;
  UINT16    Len;
  UINT8     Data[];
} HASH_TABLE;
#pragma pack ()

STATIC HASH_TABLE  *mHashesTable;
STATIC UINT16      mHashesTableSize;

STATIC
CONST GUID *
FindBlobEntryGuid (
  IN  CONST CHAR16  *BlobName
  )
{
  if (StrCmp (BlobName, L"kernel") == 0) {
    return &mSevKernelHashGuid;
  } else if (StrCmp (BlobName, L"initrd") == 0) {
    return &mSevInitrdHashGuid;
  } else if (StrCmp (BlobName, L"cmdline") == 0) {
    return &mSevCmdlineHashGuid;
  } else {
    return NULL;
  }
}

/**
  Verify blob from an external source.

  If a non-secure configuration is detected this function will enter a
  dead loop to prevent a boot.

  @param[in] BlobName           The name of the blob
  @param[in] Buf                The data of the blob
  @param[in] BufSize            The size of the blob in bytes
  @param[in] FetchStatus        The status of the previous blob fetch

  @retval EFI_SUCCESS           The blob was verified successfully or was not
                                found in the hash table.
  @retval EFI_ACCESS_DENIED     Kernel hashes not supported, but the boot
                                can continue safely.
**/
EFI_STATUS
EFIAPI
VerifyBlob (
  IN  CONST CHAR16  *BlobName,
  IN  CONST VOID    *Buf,
  IN  UINT32        BufSize,
  IN  EFI_STATUS    FetchStatus
  )
{
  CONST GUID  *Guid;
  INT32       Remaining;
  HASH_TABLE  *Entry;

  // Enter a dead loop if the fetching of this blob
  // failed. This prevents a malicious host from
  // circumventing the following checks.
  if (EFI_ERROR (FetchStatus)) {
    DEBUG ((
      DEBUG_ERROR,
      "%a: Fetching blob failed.\n",
      __func__
      ));

    CpuDeadLoop ();
  }

  if ((mHashesTable == NULL) || (mHashesTableSize == 0)) {
    DEBUG ((
      DEBUG_WARN,
      "%a: Verifier called but no hashes table discoverd in MEMFD\n",
      __func__
      ));
    return EFI_ACCESS_DENIED;
  }

  Guid = FindBlobEntryGuid (BlobName);
  if (Guid == NULL) {
    DEBUG ((
      DEBUG_ERROR,
      "%a: Unknown blob name \"%s\"\n",
      __func__,
      BlobName
      ));

    CpuDeadLoop ();
  }

  //
  // Remaining is INT32 to catch underflow in case Entry->Len has a
  // very high UINT16 value
  //
  for (Entry = mHashesTable, Remaining = mHashesTableSize;
       Remaining >= sizeof *Entry && Remaining >= Entry->Len;
       Remaining -= Entry->Len,
       Entry = (HASH_TABLE *)((UINT8 *)Entry + Entry->Len))
  {
    UINTN       EntrySize;
    EFI_STATUS  Status;
    UINT8       Hash[SHA256_DIGEST_SIZE];

    if (!CompareGuid (&Entry->Guid, Guid)) {
      continue;
    }

    DEBUG ((DEBUG_INFO, "%a: Found GUID %g in table\n", __func__, Guid));

    EntrySize = Entry->Len - sizeof Entry->Guid - sizeof Entry->Len;
    if (EntrySize != SHA256_DIGEST_SIZE) {
      DEBUG ((
        DEBUG_WARN,
        "%a: Hash has the wrong size %d != %d\n",
        __func__,
        EntrySize,
        SHA256_DIGEST_SIZE
        ));
      return EFI_ACCESS_DENIED;
    }

    //
    // Calculate the buffer's hash and verify that it is identical to the
    // expected hash table entry
    //
    Sha256HashAll (Buf, BufSize, Hash);

    if (CompareMem (Entry->Data, Hash, EntrySize) == 0) {
      Status = EFI_SUCCESS;
      DEBUG ((
        DEBUG_INFO,
        "%a: Hash comparison succeeded for \"%s\"\n",
        __func__,
        BlobName
        ));
    } else {
      Status = EFI_ACCESS_DENIED;
      DEBUG ((
        DEBUG_ERROR,
        "%a: Hash comparison failed for \"%s\"\n",
        __func__,
        BlobName
        ));

      CpuDeadLoop ();
    }

    return Status;
  }

  //
  // If the GUID is not in the hash table, execution can still continue.
  // This blob will not be measured, but at least one blob must be.
  //
  DEBUG ((
    DEBUG_ERROR,
    "%a: Hash GUID %g not found in table\n",
    __func__,
    Guid
    ));
  return EFI_SUCCESS;
}

/**
  Locate the SEV hashes table.

  This function always returns success, even if the table can't be found.  The
  subsequent VerifyBlob calls will fail if no table was found.

  @retval RETURN_SUCCESS   The hashes table is set up correctly, or there is no
                           hashes table
**/
RETURN_STATUS
EFIAPI
BlobVerifierLibSevHashesConstructor (
  VOID
  )
{
  HASH_TABLE  *Ptr;
  UINT32      Size;

  mHashesTable     = NULL;
  mHashesTableSize = 0;

  Ptr  = (void *)(UINTN)FixedPcdGet64 (PcdQemuHashTableBase);
  Size = FixedPcdGet32 (PcdQemuHashTableSize);

  if ((Ptr == NULL) || (Size < sizeof *Ptr) ||
      !CompareGuid (&Ptr->Guid, &SEV_HASH_TABLE_GUID) ||
      (Ptr->Len < sizeof *Ptr) || (Ptr->Len > Size))
  {
    return RETURN_SUCCESS;
  }

  DEBUG ((
    DEBUG_INFO,
    "%a: Found injected hashes table in secure location\n",
    __func__
    ));

  mHashesTable     = (HASH_TABLE *)Ptr->Data;
  mHashesTableSize = Ptr->Len - sizeof Ptr->Guid - sizeof Ptr->Len;

  DEBUG ((
    DEBUG_VERBOSE,
    "%a: mHashesTable=0x%p, Size=%u\n",
    __func__,
    mHashesTable,
    mHashesTableSize
    ));

  return RETURN_SUCCESS;
}
