/* $Id: efisignaturedb.cpp $ */ /** @file * IPRT - EFI signature database helpers. */ /* * Copyright (C) 2021-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included * in the VirtualBox distribution, in which case the provisions of the * CDDL are applicable instead of those of the GPL. * * You may elect to license modified versions of this file under the * terms and conditions of either the GPL or the CDDL or both. * * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP RTLOGGROUP_DEFAULT #include #include #include #include #include #include #include #include /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * EFI signature entry. */ typedef struct RTEFISIGNATURE { /** List node. */ RTLISTNODE NdLst; /** The signature owner. */ RTUUID UuidOwner; /** Size of the signature data in bytes. */ uint32_t cbSignature; /** The signature data (variable in size). */ RT_FLEXIBLE_ARRAY_EXTENSION uint8_t abSignature[RT_FLEXIBLE_ARRAY]; } RTEFISIGNATURE; /** Pointer to a EFI signature entry. */ typedef RTEFISIGNATURE *PRTEFISIGNATURE; /** Pointer to a const EFI signature entry. */ typedef const RTEFISIGNATURE *PCRTEFISIGNATURE; /** * The EFI signature database instance data. */ typedef struct RTEFISIGDBINT { /** List head of the various signature types. */ RTLISTANCHOR aLstSigTypes[RTEFISIGTYPE_FIRST_INVALID]; } RTEFISIGDBINT; /** Pointer to the EFI signature database instance data. */ typedef RTEFISIGDBINT *PRTEFISIGDBINT; /** * Signature type descriptor. */ typedef struct RTEFISIGDBDESC { /** The EFI GUID identifying the signature type. */ EFI_GUID GuidSignatureType; /** The additional signature header for this signature type. */ uint32_t cbSigHdr; /** Size of the signature data (including EFI_SIGNATURE_DATA), * can be 0 size varies with each signature (X.509 for example). */ uint32_t cbSig; /** The internal signature type enum. */ RTEFISIGTYPE enmSigType; /** Human readable string of the signature type. */ const char *pszName; } RTEFISIGDBDESC; /** Pointer to a signature type descriptor. */ typedef RTEFISIGDBDESC *PRTEFISIGDBDESC; /** Pointer to a const signature type descriptor. */ typedef const RTEFISIGDBDESC *PCRTEFISIGDBDESC; /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** * Mapping of EFI signature GUIDs to their IPRT signature type equivalent. */ static const RTEFISIGDBDESC g_aGuid2SigTypeMapping[] = { { EFI_NULL_GUID, 0, 0, RTEFISIGTYPE_INVALID, "INVALID" }, { EFI_SIGNATURE_TYPE_GUID_SHA256, 0, EFI_SIGNATURE_TYPE_SZ_SHA256, RTEFISIGTYPE_SHA256, "SHA-256" }, { EFI_SIGNATURE_TYPE_GUID_RSA2048, 0, EFI_SIGNATURE_TYPE_SZ_RSA2048, RTEFISIGTYPE_RSA2048, "RSA-2048" }, { EFI_SIGNATURE_TYPE_GUID_RSA2048_SHA256, 0, EFI_SIGNATURE_TYPE_SZ_RSA2048_SHA256, RTEFISIGTYPE_RSA2048_SHA256, "RSA-2048/SHA-256" }, { EFI_SIGNATURE_TYPE_GUID_SHA1, 0, EFI_SIGNATURE_TYPE_SZ_SHA1, RTEFISIGTYPE_SHA1, "SHA-1" }, { EFI_SIGNATURE_TYPE_GUID_RSA2048_SHA1, 0, EFI_SIGNATURE_TYPE_SZ_RSA2048_SHA1, RTEFISIGTYPE_RSA2048_SHA1, "RSA-2048/SHA-1" }, { EFI_SIGNATURE_TYPE_GUID_X509, 0, 0, RTEFISIGTYPE_X509, "X.509" } }; /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ /** * Returns the internal siganture type descriptor for the given EFI GUID. * * @returns Pointer to the descriptor if found or NULL if not. * @param pGuid The EFI signature type GUID to look for. */ static PCRTEFISIGDBDESC rtEfiSigDbGetDescByGuid(PCEFI_GUID pGuid) { for (uint32_t i = 0; i < RT_ELEMENTS(g_aGuid2SigTypeMapping); i++) if (!RTEfiGuidCompare(&g_aGuid2SigTypeMapping[i].GuidSignatureType, pGuid)) return &g_aGuid2SigTypeMapping[i]; return NULL; } /** * Validates the given signature lsit header. * * @returns Flag whether the list header is considered valid. * @param pLstHdr The list header to validate. * @param pDesc The descriptor for the signature type of the given list. */ static bool rtEfiSigDbSigHdrValidate(PCEFI_SIGNATURE_LIST pLstHdr, PCRTEFISIGDBDESC pDesc) { uint32_t cbSigLst = RT_LE2H_U32(pLstHdr->cbSigLst); uint32_t cbSigHdr = RT_LE2H_U32(pLstHdr->cbSigHdr); uint32_t cbSig = RT_LE2H_U32(pLstHdr->cbSig); if (cbSigHdr != pDesc->cbSigHdr) return false; if (cbSig < sizeof(EFI_SIGNATURE_DATA)) return false; if ( pDesc->cbSig && pLstHdr->cbSig != pDesc->cbSig) return false; if ( cbSigLst <= sizeof(*pLstHdr) || cbSigLst <= cbSigHdr || cbSigLst <= cbSig) return false; if ((cbSigLst - sizeof(*pLstHdr) - cbSigHdr) % cbSig) return false; return true; } /** * Loads a single signature list into the given signature database from the given file. * * @returns IPRT status code. * @param pThis The signature database instance. * @param hVfsFileIn The file to load the signature list from. * @param pcbConsumed Where to store the number of bytes consumed for this signature list on success. */ static int rtEfiSigDbLoadSigList(PRTEFISIGDBINT pThis, RTVFSFILE hVfsFileIn, uint64_t *pcbConsumed) { EFI_SIGNATURE_LIST LstHdr; int rc = RTVfsFileRead(hVfsFileIn, &LstHdr, sizeof(LstHdr), NULL /*pcbRead*/); if (RT_SUCCESS(rc)) { PCRTEFISIGDBDESC pDesc = rtEfiSigDbGetDescByGuid(&LstHdr.GuidSigType); if (pDesc) { if (rtEfiSigDbSigHdrValidate(&LstHdr, pDesc)) { RTLISTANCHOR LstTmp; uint32_t cbSig = RT_LE2H_U32(LstHdr.cbSig); uint32_t cbSigData = cbSig - sizeof(EFI_SIGNATURE_DATA); uint32_t cSigs = (RT_LE2H_U32(LstHdr.cbSigLst) - RT_LE2H_U32(LstHdr.cbSigHdr)) / cbSig; /** @todo Skip/parse signature header if we have to add a type which has this != 0. */ RTListInit(&LstTmp); for (uint32_t i = 0; i < cSigs && RT_SUCCESS(rc); i++) { PRTEFISIGNATURE pSig = (PRTEFISIGNATURE)RTMemAllocZ(RT_UOFFSETOF_DYN(RTEFISIGNATURE, abSignature[cbSigData])); if (pSig) { EFI_SIGNATURE_DATA SigData; rc = RTVfsFileRead(hVfsFileIn, &SigData, sizeof(SigData), NULL /*pcbRead*/); if (RT_SUCCESS(rc)) rc = RTVfsFileRead(hVfsFileIn, &pSig->abSignature[0], cbSigData, NULL /*pcbRead*/); if (RT_SUCCESS(rc)) { RTEfiGuidToUuid(&pSig->UuidOwner, &SigData.GuidOwner); pSig->cbSignature = cbSigData; RTListAppend(&LstTmp, &pSig->NdLst); } else RTMemFree(pSig); } else rc = VERR_NO_MEMORY; } if (RT_SUCCESS(rc)) { /* Add the signatures to the list. */ RTListConcatenate(&pThis->aLstSigTypes[pDesc->enmSigType], &LstTmp); *pcbConsumed = sizeof(LstHdr) + RT_LE2H_U32(LstHdr.cbSigHdr) + cSigs * cbSig; } else { /* Destroy the temporary list. */ PRTEFISIGNATURE pIt, pItNext; RTListForEachSafe(&LstTmp, pIt, pItNext, RTEFISIGNATURE, NdLst) { RTListNodeRemove(&pIt->NdLst); RTMemFree(pIt); } } } else rc = VERR_NOT_SUPPORTED; } else rc = VERR_NOT_SUPPORTED; } return rc; } /** * Variant for written a list of signatures where each signature gets its own signature list header * (for types where each signature can differ in size like X.509). * * @returns IPRT status code. * @param pLst The list of signatures to write. * @param pDesc The signature type descriptor. * @param hVfsFileOut The file to write the database to. * @param pcbThisWritten Where to store the number of bytes written for the given signature list. */ static int rtEfiSigDbWriteListSingle(PRTLISTANCHOR pLst, PCRTEFISIGDBDESC pDesc, RTVFSFILE hVfsFileOut, size_t *pcbThisWritten) { int rc = VINF_SUCCESS; size_t cbWritten = 0; PRTEFISIGNATURE pIt; RTListForEach(pLst, pIt, RTEFISIGNATURE, NdLst) { EFI_SIGNATURE_LIST LstHdr; EFI_SIGNATURE_DATA SigData; LstHdr.GuidSigType = pDesc->GuidSignatureType; LstHdr.cbSigLst = RT_H2LE_U32(sizeof(LstHdr) + sizeof(SigData) + pDesc->cbSigHdr + pIt->cbSignature); LstHdr.cbSigHdr = RT_H2LE_U32(pDesc->cbSigHdr); LstHdr.cbSig = RT_H2LE_U32(pIt->cbSignature + sizeof(SigData)); RTEfiGuidFromUuid(&SigData.GuidOwner, &pIt->UuidOwner); RTSGSEG aSegs[3]; RTSGBUF SgBuf; Assert(!pDesc->cbSigHdr); aSegs[0].pvSeg = &LstHdr; aSegs[0].cbSeg = sizeof(LstHdr); aSegs[1].pvSeg = &SigData; aSegs[1].cbSeg = sizeof(SigData); aSegs[2].pvSeg = &pIt->abSignature[0]; aSegs[2].cbSeg = pIt->cbSignature; RTSgBufInit(&SgBuf, &aSegs[0], RT_ELEMENTS(aSegs)); rc = RTVfsFileSgWrite(hVfsFileOut, -1, &SgBuf, true /*fBlocking*/, NULL /*pcbWritten*/); if (RT_FAILURE(rc)) break; cbWritten += sizeof(LstHdr) + sizeof(SigData) + pDesc->cbSigHdr + pIt->cbSignature; } if (RT_SUCCESS(rc)) *pcbThisWritten = cbWritten; return rc; } /** * Writes the given signature list to the database in the given file. * * @returns IPRT status code. * @param pLst The list of signatures to write. * @param pDesc The signature type descriptor. * @param hVfsFileOut The file to write the database to. * @param pcbThisWritten Where to store the number of bytes written for the given signature list. */ static int rtEfiSigDbWriteList(PRTLISTANCHOR pLst, PCRTEFISIGDBDESC pDesc, RTVFSFILE hVfsFileOut, size_t *pcbThisWritten) { /* * For signature lists where each signature can have a different size (X.509 for example) * writing a new list for each signature is required which is done by a dedicated method. */ if (!pDesc->cbSig) return rtEfiSigDbWriteListSingle(pLst, pDesc, hVfsFileOut, pcbThisWritten); /* Count the number of signatures first. */ uint32_t cSigs = 0; PRTEFISIGNATURE pIt; RTListForEach(pLst, pIt, RTEFISIGNATURE, NdLst) { cSigs++; } EFI_SIGNATURE_LIST LstHdr; LstHdr.GuidSigType = pDesc->GuidSignatureType; LstHdr.cbSigLst = RT_H2LE_U32(sizeof(LstHdr) + pDesc->cbSigHdr + cSigs * pDesc->cbSig); LstHdr.cbSigHdr = RT_H2LE_U32(pDesc->cbSigHdr); LstHdr.cbSig = RT_H2LE_U32(pDesc->cbSig); int rc = RTVfsFileWrite(hVfsFileOut, &LstHdr, sizeof(LstHdr), NULL /*pcbWritten*/); if (RT_SUCCESS(rc)) { RTListForEach(pLst, pIt, RTEFISIGNATURE, NdLst) { RTSGSEG aSegs[2]; RTSGBUF SgBuf; EFI_SIGNATURE_DATA SigData; RTEfiGuidFromUuid(&SigData.GuidOwner, &pIt->UuidOwner); Assert(pDesc->cbSig == pIt->cbSignature); aSegs[0].pvSeg = &SigData; aSegs[0].cbSeg = sizeof(SigData); aSegs[1].pvSeg = &pIt->abSignature[0]; aSegs[1].cbSeg = pIt->cbSignature; RTSgBufInit(&SgBuf, &aSegs[0], RT_ELEMENTS(aSegs)); rc = RTVfsFileSgWrite(hVfsFileOut, -1, &SgBuf, true /*fBlocking*/, NULL /*pcbWritten*/); if (RT_FAILURE(rc)) break; } } if (RT_SUCCESS(rc)) *pcbThisWritten = sizeof(LstHdr) + pDesc->cbSigHdr + cSigs * pDesc->cbSig; return rc; } /** * Allocate a new signature of the given size. * * @returns Pointer to the new signature or NULL if out of memory. * @param pUuidOwner The UUID of the signature owner. * @param cbSig Size of the signature data in bytes. */ static PRTEFISIGNATURE rtEfiSigDbAllocSignature(PCRTUUID pUuidOwner, uint32_t cbSig) { PRTEFISIGNATURE pSig = (PRTEFISIGNATURE)RTMemAllocZ(RT_UOFFSETOF_DYN(RTEFISIGNATURE, abSignature[cbSig])); if (pSig) { pSig->UuidOwner = *pUuidOwner; pSig->cbSignature = cbSig; } return pSig; } RTDECL(int) RTEfiSigDbCreate(PRTEFISIGDB phEfiSigDb) { AssertPtrReturn(phEfiSigDb, VERR_INVALID_POINTER); PRTEFISIGDBINT pThis = (PRTEFISIGDBINT)RTMemAllocZ(sizeof(*pThis)); if (RT_LIKELY(pThis)) { for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aLstSigTypes); i++) RTListInit(&pThis->aLstSigTypes[i]); *phEfiSigDb = pThis; return VINF_SUCCESS; } return VERR_NO_MEMORY; } RTDECL(int) RTEfiSigDbDestroy(RTEFISIGDB hEfiSigDb) { PRTEFISIGDBINT pThis = hEfiSigDb; AssertPtrReturn(pThis, VERR_INVALID_HANDLE); for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aLstSigTypes); i++) { PRTEFISIGNATURE pIt, pItNext; RTListForEachSafe(&pThis->aLstSigTypes[i], pIt, pItNext, RTEFISIGNATURE, NdLst) { RTListNodeRemove(&pIt->NdLst); RTMemFree(pIt); } } RTMemFree(pThis); return VINF_SUCCESS; } RTDECL(int) RTEfiSigDbAddFromExistingDb(RTEFISIGDB hEfiSigDb, RTVFSFILE hVfsFileIn) { PRTEFISIGDBINT pThis = hEfiSigDb; AssertPtrReturn(pThis, VERR_INVALID_HANDLE); uint64_t cbFile; int rc = RTVfsFileQuerySize(hVfsFileIn, &cbFile); if ( RT_SUCCESS(rc) && cbFile) { do { uint64_t cbConsumed = 0; rc = rtEfiSigDbLoadSigList(pThis, hVfsFileIn, &cbConsumed); cbFile -= cbConsumed; } while ( RT_SUCCESS(rc) && cbFile); } return rc; } RTDECL(int) RTEfiSigDbAddSignatureFromFile(RTEFISIGDB hEfiSigDb, RTEFISIGTYPE enmSigType, PCRTUUID pUuidOwner, RTVFSFILE hVfsFileIn) { PRTEFISIGDBINT pThis = hEfiSigDb; AssertPtrReturn(pThis, VERR_INVALID_HANDLE); AssertReturn(enmSigType >= RTEFISIGTYPE_FIRST_VALID && enmSigType < RTEFISIGTYPE_FIRST_INVALID, VERR_INVALID_PARAMETER); AssertPtrReturn(pUuidOwner, VERR_INVALID_POINTER); PCRTEFISIGDBDESC pDesc = &g_aGuid2SigTypeMapping[enmSigType]; uint64_t cbSig = 0; int rc = RTVfsFileQuerySize(hVfsFileIn, &cbSig); if (RT_SUCCESS(rc)) { if ( ( !pDesc->cbSig || pDesc->cbSig - sizeof(EFI_SIGNATURE_DATA) == cbSig) && cbSig < UINT32_MAX) { PRTEFISIGNATURE pSig = rtEfiSigDbAllocSignature(pUuidOwner, (uint32_t)cbSig); if (pSig) { rc = RTVfsFileRead(hVfsFileIn, &pSig->abSignature[0], (size_t)cbSig, NULL /*pcbRead*/); if (RT_SUCCESS(rc)) RTListAppend(&pThis->aLstSigTypes[enmSigType], &pSig->NdLst); else RTMemFree(pSig); } else rc = VERR_NO_MEMORY; } else rc = VERR_INVALID_PARAMETER; } return rc; } RTDECL(int) RTEfiSigDbAddSignatureFromBuf(RTEFISIGDB hEfiSigDb, RTEFISIGTYPE enmSigType, PCRTUUID pUuidOwner, const void *pvBuf, size_t cbBuf) { PRTEFISIGDBINT pThis = hEfiSigDb; AssertPtrReturn(pThis, VERR_INVALID_HANDLE); AssertReturn(enmSigType >= RTEFISIGTYPE_FIRST_VALID && enmSigType < RTEFISIGTYPE_FIRST_INVALID, VERR_INVALID_PARAMETER); AssertPtrReturn(pUuidOwner, VERR_INVALID_POINTER); AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); AssertReturn(cbBuf && cbBuf < UINT32_MAX, VERR_INVALID_PARAMETER); int rc = VINF_SUCCESS; PCRTEFISIGDBDESC pDesc = &g_aGuid2SigTypeMapping[enmSigType]; if ( !pDesc->cbSig || pDesc->cbSig - sizeof(EFI_SIGNATURE_DATA) == cbBuf) { PRTEFISIGNATURE pSig = rtEfiSigDbAllocSignature(pUuidOwner, (uint32_t)cbBuf); if (pSig) { memcpy(&pSig->abSignature[0], pvBuf, cbBuf); RTListAppend(&pThis->aLstSigTypes[enmSigType], &pSig->NdLst); } else rc = VERR_NO_MEMORY; } else rc = VERR_INVALID_PARAMETER; return rc; } RTDECL(int) RTEfiSigDbWriteToFile(RTEFISIGDB hEfiSigDb, RTVFSFILE hVfsFileOut) { PRTEFISIGDBINT pThis = hEfiSigDb; AssertPtrReturn(pThis, VERR_INVALID_HANDLE); int rc = VINF_SUCCESS; size_t cbSigDb = 0; for (uint32_t i = RTEFISIGTYPE_FIRST_VALID; i < RT_ELEMENTS(pThis->aLstSigTypes) && RT_SUCCESS(rc); i++) { if (!RTListIsEmpty(&pThis->aLstSigTypes[i])) { size_t cbThisWritten = 0; rc = rtEfiSigDbWriteList(&pThis->aLstSigTypes[i], &g_aGuid2SigTypeMapping[i], hVfsFileOut, &cbThisWritten); if (RT_SUCCESS(rc)) cbSigDb += cbThisWritten; } } if (RT_SUCCESS(rc)) rc = RTVfsFileSetSize(hVfsFileOut, cbSigDb, RTVFSFILE_SIZE_F_NORMAL); return rc; } RTDECL(int) RTEfiSigDbEnum(RTEFISIGDB hEfiSigDb, PFNRTEFISIGDBENUMSIG pfnEnumSig, void *pvUser) { PRTEFISIGDBINT pThis = hEfiSigDb; AssertPtrReturn(pThis, VERR_INVALID_HANDLE); for (uint32_t i = RTEFISIGTYPE_FIRST_VALID; i < RT_ELEMENTS(pThis->aLstSigTypes); i++) { PRTEFISIGNATURE pIt; RTListForEach(&pThis->aLstSigTypes[i], pIt, RTEFISIGNATURE, NdLst) { int rc = pfnEnumSig(pThis, (RTEFISIGTYPE)i, &pIt->UuidOwner, &pIt->abSignature[0], pIt->cbSignature, pvUser); if (rc != VINF_SUCCESS) return rc; } } return VINF_SUCCESS; } RTDECL(const char *) RTEfiSigDbTypeStringify(RTEFISIGTYPE enmSigType) { AssertReturn(enmSigType < RTEFISIGTYPE_FIRST_INVALID, NULL); return g_aGuid2SigTypeMapping[enmSigType].pszName; } RTDECL(PCEFI_GUID) RTEfiSigDbTypeGetGuid(RTEFISIGTYPE enmSigType) { AssertReturn(enmSigType < RTEFISIGTYPE_FIRST_INVALID, NULL); return &g_aGuid2SigTypeMapping[enmSigType].GuidSignatureType; }