mirror of
https://github.com/acidanthera/OpenCorePkg.git
synced 2025-12-08 19:25:01 +00:00
1600 lines
46 KiB
C
1600 lines
46 KiB
C
/** @file
|
|
Mkext support.
|
|
|
|
Copyright (c) 2020, Goldfish64. All rights reserved.
|
|
|
|
This program and the accompanying materials
|
|
are licensed and made available under the terms and conditions of the BSD License
|
|
which accompanies this distribution. The full text of the license may be found at
|
|
http://opensource.org/licenses/bsd-license.php
|
|
|
|
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
|
|
|
**/
|
|
|
|
#include <Uefi.h>
|
|
|
|
#include <Library/BaseLib.h>
|
|
#include <Library/BaseMemoryLib.h>
|
|
#include <Library/DebugLib.h>
|
|
#include <Library/MemoryAllocationLib.h>
|
|
#include <Library/OcAppleKernelLib.h>
|
|
#include <Library/OcCompressionLib.h>
|
|
#include <Library/OcGuardLib.h>
|
|
#include <Library/OcStringLib.h>
|
|
|
|
#include "MkextInternal.h"
|
|
#include "PrelinkedInternal.h"
|
|
|
|
//
|
|
// Alignment to 8 bytes.
|
|
//
|
|
#define MKEXT_ALIGN(a) (ALIGN_VALUE (a, sizeof (UINT64)))
|
|
|
|
BOOLEAN
|
|
InternalParseKextBinary (
|
|
IN OUT UINT8 **Buffer,
|
|
IN OUT UINT32 *BufferSize,
|
|
IN BOOLEAN Is32Bit
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
MACH_HEADER_ANY *MachHeader;
|
|
|
|
Status = FatFilterArchitectureByType (Buffer, BufferSize, Is32Bit ? MachCpuTypeI386 : MachCpuTypeX8664);
|
|
if (EFI_ERROR (Status)) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Size/alignment checked by FatFilterArchitectureByType.
|
|
//
|
|
MachHeader = (MACH_HEADER_ANY *)*Buffer;
|
|
|
|
if ( (!Is32Bit && (MachHeader->Signature == MACH_HEADER_64_SIGNATURE))
|
|
|| (Is32Bit && (MachHeader->Signature == MACH_HEADER_SIGNATURE)))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
STATIC
|
|
VOID
|
|
UpdateMkextLengthChecksum (
|
|
IN MKEXT_HEADER_ANY *Mkext,
|
|
IN UINT32 Length
|
|
)
|
|
{
|
|
Mkext->Common.Length = SwapBytes32 (Length);
|
|
Mkext->Common.Adler32 = SwapBytes32 (
|
|
Adler32 (
|
|
(UINT8 *)&Mkext->Common.Version,
|
|
Length - OFFSET_OF (MKEXT_CORE_HEADER, Version)
|
|
)
|
|
);
|
|
}
|
|
|
|
STATIC
|
|
BOOLEAN
|
|
ParseMkextV2Plist (
|
|
IN MKEXT_V2_HEADER *Mkext,
|
|
OUT UINT8 **Plist,
|
|
OUT UINT32 *PlistSize,
|
|
OUT XML_DOCUMENT **PlistDoc,
|
|
OUT XML_NODE **PlistBundles
|
|
)
|
|
{
|
|
UINT8 *MkextBuffer;
|
|
UINT32 MkextLength;
|
|
VOID *PlistBuffer;
|
|
XML_DOCUMENT *PlistXml;
|
|
UINT32 PlistOffset;
|
|
UINT32 PlistCompressedSize;
|
|
UINT32 PlistFullSize;
|
|
UINT32 PlistStoredSize;
|
|
UINT32 Tmp;
|
|
|
|
XML_NODE *MkextInfoRoot;
|
|
UINT32 MkextInfoRootIndex;
|
|
UINT32 MkextInfoRootCount;
|
|
|
|
XML_NODE *PlistBundleArray;
|
|
CONST CHAR8 *BundleArrayKey;
|
|
|
|
MkextBuffer = (UINT8 *)Mkext;
|
|
MkextLength = SwapBytes32 (Mkext->Header.Length);
|
|
PlistOffset = SwapBytes32 (Mkext->PlistOffset);
|
|
PlistCompressedSize = SwapBytes32 (Mkext->PlistCompressedSize);
|
|
PlistFullSize = SwapBytes32 (Mkext->PlistFullSize);
|
|
|
|
PlistStoredSize = PlistCompressedSize;
|
|
if (PlistStoredSize == 0) {
|
|
PlistStoredSize = PlistFullSize;
|
|
}
|
|
|
|
if (OcOverflowAddU32 (PlistOffset, PlistStoredSize, &Tmp) || (Tmp > MkextLength)) {
|
|
return FALSE;
|
|
}
|
|
|
|
PlistBuffer = AllocatePool (PlistFullSize);
|
|
if (PlistBuffer == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Copy/decompress plist.
|
|
//
|
|
if (PlistCompressedSize > 0) {
|
|
if (DecompressZLIB (
|
|
PlistBuffer,
|
|
PlistFullSize,
|
|
&MkextBuffer[PlistOffset],
|
|
PlistCompressedSize
|
|
) != PlistFullSize)
|
|
{
|
|
FreePool (PlistBuffer);
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
CopyMem (PlistBuffer, &MkextBuffer[PlistOffset], PlistFullSize);
|
|
}
|
|
|
|
PlistXml = XmlDocumentParse (PlistBuffer, PlistFullSize, FALSE);
|
|
if (PlistXml == NULL) {
|
|
FreePool (PlistBuffer);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Mkext v2 root element is a dictionary containing an array of bundles.
|
|
//
|
|
MkextInfoRoot = PlistNodeCast (XmlDocumentRoot (PlistXml), PLIST_NODE_TYPE_DICT);
|
|
if (MkextInfoRoot == NULL) {
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Get bundle array.
|
|
//
|
|
MkextInfoRootCount = PlistDictChildren (MkextInfoRoot);
|
|
for (MkextInfoRootIndex = 0; MkextInfoRootIndex < MkextInfoRootCount; MkextInfoRootIndex++) {
|
|
BundleArrayKey = PlistKeyValue (PlistDictChild (MkextInfoRoot, MkextInfoRootIndex, &PlistBundleArray));
|
|
if (BundleArrayKey == NULL) {
|
|
continue;
|
|
}
|
|
|
|
if (AsciiStrCmp (BundleArrayKey, MKEXT_INFO_DICTIONARIES_KEY) == 0) {
|
|
*Plist = PlistBuffer;
|
|
*PlistSize = PlistFullSize;
|
|
*PlistDoc = PlistXml;
|
|
*PlistBundles = PlistBundleArray;
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// No bundle array found.
|
|
//
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
return FALSE;
|
|
}
|
|
|
|
STATIC
|
|
UINT32
|
|
UpdateMkextV2Plist (
|
|
IN OUT MKEXT_V2_HEADER *Mkext,
|
|
IN UINT32 AllocatedSize,
|
|
IN XML_DOCUMENT *PlistDoc,
|
|
IN UINT32 Offset
|
|
)
|
|
{
|
|
UINT8 *MkextBuffer;
|
|
CHAR8 *ExportedInfo;
|
|
UINT32 ExportedInfoSize;
|
|
UINT32 TmpSize;
|
|
|
|
//
|
|
// Export plist and include \0 terminator in size.
|
|
//
|
|
ExportedInfo = XmlDocumentExport (PlistDoc, &ExportedInfoSize, 0, FALSE);
|
|
if (ExportedInfo == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
ExportedInfoSize++;
|
|
|
|
if ( OcOverflowAddU32 (Offset, ExportedInfoSize, &TmpSize)
|
|
|| (TmpSize > AllocatedSize))
|
|
{
|
|
FreePool (ExportedInfo);
|
|
return 0;
|
|
}
|
|
|
|
MkextBuffer = (UINT8 *)Mkext;
|
|
CopyMem (&MkextBuffer[Offset], ExportedInfo, ExportedInfoSize);
|
|
FreePool (ExportedInfo);
|
|
|
|
Mkext->PlistOffset = SwapBytes32 (Offset);
|
|
Mkext->PlistFullSize = SwapBytes32 (ExportedInfoSize);
|
|
Mkext->PlistCompressedSize = 0;
|
|
return ExportedInfoSize;
|
|
}
|
|
|
|
STATIC
|
|
MKEXT_KEXT *
|
|
InsertCachedMkextKext (
|
|
IN OUT MKEXT_CONTEXT *Context,
|
|
IN CONST CHAR8 *Identifier,
|
|
IN UINT32 BinOffset,
|
|
IN UINT32 BinSize
|
|
)
|
|
{
|
|
MKEXT_KEXT *MkextKext;
|
|
|
|
MkextKext = AllocateZeroPool (sizeof (*MkextKext));
|
|
if (MkextKext == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
MkextKext->Signature = MKEXT_KEXT_SIGNATURE;
|
|
MkextKext->BinaryOffset = BinOffset;
|
|
MkextKext->BinarySize = BinSize;
|
|
MkextKext->Identifier = AllocateCopyPool (AsciiStrSize (Identifier), Identifier);
|
|
if (MkextKext->Identifier == NULL) {
|
|
FreePool (MkextKext);
|
|
return NULL;
|
|
}
|
|
|
|
InsertTailList (&Context->CachedKexts, &MkextKext->Link);
|
|
|
|
DEBUG ((DEBUG_VERBOSE, "OCAK: Inserted %a into mkext cache\n", Identifier));
|
|
|
|
return MkextKext;
|
|
}
|
|
|
|
MKEXT_KEXT *
|
|
InternalCachedMkextKext (
|
|
IN OUT MKEXT_CONTEXT *Context,
|
|
IN CONST CHAR8 *Identifier
|
|
)
|
|
{
|
|
MKEXT_HEADER_ANY *MkextHeader;
|
|
MKEXT_V2_FILE_ENTRY *MkextV2FileEntry;
|
|
|
|
MKEXT_KEXT *MkextKext;
|
|
LIST_ENTRY *KextLink;
|
|
UINT32 Index;
|
|
UINT32 PlistOffsetSize;
|
|
UINT32 BinOffsetSize;
|
|
BOOLEAN IsKextMatch;
|
|
|
|
UINT32 PlistOffset;
|
|
UINT32 PlistSize;
|
|
CHAR8 *PlistBuffer;
|
|
XML_DOCUMENT *PlistXml;
|
|
XML_NODE *PlistRoot;
|
|
|
|
UINT32 PlistBundlesCount;
|
|
XML_NODE *PlistBundle;
|
|
UINT32 PlistBundleIndex;
|
|
UINT32 PlistBundleCount;
|
|
CONST CHAR8 *PlistBundleKey;
|
|
XML_NODE *PlistBundleKeyValue;
|
|
|
|
CONST CHAR8 *KextIdentifier;
|
|
UINT32 KextBinOffset;
|
|
UINT32 KextBinSize;
|
|
|
|
MkextHeader = Context->MkextHeader;
|
|
|
|
//
|
|
// Try to get cached kext.
|
|
//
|
|
KextLink = GetFirstNode (&Context->CachedKexts);
|
|
while (!IsNull (&Context->CachedKexts, KextLink)) {
|
|
MkextKext = GET_MKEXT_KEXT_FROM_LINK (KextLink);
|
|
|
|
if (AsciiStrCmp (Identifier, MkextKext->Identifier) == 0) {
|
|
return MkextKext;
|
|
}
|
|
|
|
KextLink = GetNextNode (&Context->CachedKexts, KextLink);
|
|
}
|
|
|
|
//
|
|
// Search mkext and add kext to cache.
|
|
//
|
|
IsKextMatch = FALSE;
|
|
|
|
//
|
|
// Mkext v1.
|
|
//
|
|
if (Context->MkextVersion == MKEXT_VERSION_V1) {
|
|
for (Index = 0; Index < Context->NumKexts; Index++) {
|
|
//
|
|
// Do not cache binaryless or compressed kexts.
|
|
//
|
|
if ( (MkextHeader->V1.Kexts[Index].Plist.CompressedSize != 0)
|
|
|| (MkextHeader->V1.Kexts[Index].Binary.CompressedSize != 0)
|
|
|| (MkextHeader->V1.Kexts[Index].Binary.Offset == 0))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
PlistOffset = SwapBytes32 (MkextHeader->V1.Kexts[Index].Plist.Offset);
|
|
PlistSize = SwapBytes32 (MkextHeader->V1.Kexts[Index].Plist.FullSize);
|
|
KextBinOffset = SwapBytes32 (MkextHeader->V1.Kexts[Index].Binary.Offset);
|
|
KextBinSize = SwapBytes32 (MkextHeader->V1.Kexts[Index].Binary.FullSize);
|
|
|
|
//
|
|
// Verify plist and binary are within bounds.
|
|
//
|
|
if ( OcOverflowAddU32 (PlistOffset, PlistSize, &PlistOffsetSize)
|
|
|| (PlistOffsetSize > Context->MkextSize)
|
|
|| OcOverflowAddU32 (KextBinOffset, KextBinSize, &BinOffsetSize)
|
|
|| (BinOffsetSize > Context->MkextSize))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
PlistBuffer = AllocateCopyPool (PlistSize, &Context->Mkext[PlistOffset]);
|
|
if (PlistBuffer == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
PlistXml = XmlDocumentParse (PlistBuffer, PlistSize, FALSE);
|
|
if (PlistXml == NULL) {
|
|
FreePool (PlistBuffer);
|
|
return NULL;
|
|
}
|
|
|
|
PlistRoot = PlistNodeCast (PlistDocumentRoot (PlistXml), PLIST_NODE_TYPE_DICT);
|
|
if (PlistRoot == NULL) {
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
return NULL;
|
|
}
|
|
|
|
KextIdentifier = NULL;
|
|
PlistBundleCount = PlistDictChildren (PlistRoot);
|
|
for (PlistBundleIndex = 0; PlistBundleIndex < PlistBundleCount; PlistBundleIndex++) {
|
|
PlistBundleKey = PlistKeyValue (PlistDictChild (PlistRoot, PlistBundleIndex, &PlistBundleKeyValue));
|
|
if ((PlistBundleKey == NULL) || (PlistBundleKeyValue == NULL)) {
|
|
continue;
|
|
}
|
|
|
|
if (AsciiStrCmp (PlistBundleKey, INFO_BUNDLE_IDENTIFIER_KEY) == 0) {
|
|
KextIdentifier = XmlNodeContent (PlistBundleKeyValue);
|
|
break;
|
|
}
|
|
}
|
|
|
|
IsKextMatch = KextIdentifier != NULL && AsciiStrCmp (KextIdentifier, Identifier) == 0;
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
|
|
if (IsKextMatch && (KextBinOffset > 0) && (KextBinSize > 0)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Bundle was not found, or invalid.
|
|
//
|
|
if (!IsKextMatch) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Mkext v2.
|
|
//
|
|
} else if (Context->MkextVersion == MKEXT_VERSION_V2) {
|
|
KextIdentifier = NULL;
|
|
KextBinOffset = 0;
|
|
|
|
//
|
|
// Enumerate bundle dicts.
|
|
//
|
|
PlistBundlesCount = XmlNodeChildren (Context->MkextKexts);
|
|
for (Index = 0; Index < PlistBundlesCount; Index++) {
|
|
PlistBundle = PlistNodeCast (XmlNodeChild (Context->MkextKexts, Index), PLIST_NODE_TYPE_DICT);
|
|
if (PlistBundle == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
PlistBundleCount = PlistDictChildren (PlistBundle);
|
|
for (PlistBundleIndex = 0; PlistBundleIndex < PlistBundleCount; PlistBundleIndex++) {
|
|
PlistBundleKey = PlistKeyValue (PlistDictChild (PlistBundle, PlistBundleIndex, &PlistBundleKeyValue));
|
|
if ((PlistBundleKey == NULL) || (PlistBundleKeyValue == NULL)) {
|
|
continue;
|
|
}
|
|
|
|
if (AsciiStrCmp (PlistBundleKey, INFO_BUNDLE_IDENTIFIER_KEY) == 0) {
|
|
KextIdentifier = XmlNodeContent (PlistBundleKeyValue);
|
|
}
|
|
|
|
if (AsciiStrCmp (PlistBundleKey, MKEXT_EXECUTABLE_KEY) == 0) {
|
|
//
|
|
// Ensure binary offset is before plist offset.
|
|
//
|
|
if (!PlistIntegerValue (PlistBundleKeyValue, &KextBinOffset, sizeof (KextBinOffset), TRUE)) {
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( (KextIdentifier != NULL)
|
|
&& (AsciiStrCmp (KextIdentifier, Identifier) == 0)
|
|
&& (KextBinOffset > 0)
|
|
&& (KextBinOffset < Context->MkextSize - sizeof (MKEXT_V2_FILE_ENTRY)))
|
|
{
|
|
IsKextMatch = TRUE;
|
|
break;
|
|
}
|
|
|
|
KextIdentifier = NULL;
|
|
KextBinOffset = 0;
|
|
}
|
|
|
|
//
|
|
// Bundle was not found, or invalid.
|
|
//
|
|
if (!IsKextMatch) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Parse v2 binary header.
|
|
// We cannot support compressed binaries.
|
|
//
|
|
MkextV2FileEntry = (MKEXT_V2_FILE_ENTRY *)&Context->Mkext[KextBinOffset];
|
|
if (MkextV2FileEntry->CompressedSize != 0) {
|
|
return NULL;
|
|
}
|
|
|
|
KextBinOffset += OFFSET_OF (MKEXT_V2_FILE_ENTRY, Data);
|
|
KextBinSize = SwapBytes32 (MkextV2FileEntry->FullSize);
|
|
|
|
//
|
|
// Ensure binary is within mkext bounds.
|
|
//
|
|
if ( OcOverflowAddU32 (KextBinOffset, KextBinSize, &BinOffsetSize)
|
|
|| (BinOffsetSize > Context->MkextSize))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Unsupported version.
|
|
//
|
|
} else {
|
|
return NULL;
|
|
}
|
|
|
|
return InsertCachedMkextKext (Context, Identifier, KextBinOffset, KextBinSize);
|
|
}
|
|
|
|
EFI_STATUS
|
|
MkextDecompress (
|
|
IN CONST UINT8 *Buffer,
|
|
IN UINT32 BufferSize,
|
|
IN UINT32 NumReservedKexts,
|
|
IN OUT UINT8 *OutBuffer OPTIONAL,
|
|
IN UINT32 OutBufferSize OPTIONAL,
|
|
IN OUT UINT32 *OutMkextSize
|
|
)
|
|
{
|
|
BOOLEAN Decompress;
|
|
|
|
MKEXT_HEADER_ANY *MkextHeader;
|
|
MKEXT_HEADER_ANY *MkextHeaderOut;
|
|
UINT32 MkextSize;
|
|
UINT32 MkextVersion;
|
|
UINT32 NumKexts;
|
|
UINT32 NumMaxKexts;
|
|
|
|
UINT32 Tmp;
|
|
UINT32 Index;
|
|
UINT32 CurrentOffset;
|
|
UINT32 NewOffset;
|
|
UINT32 PlistOffset;
|
|
UINT32 PlistCompSize;
|
|
UINT32 PlistFullSize;
|
|
UINT32 PlistFullSizeAligned;
|
|
UINT32 BinOffset;
|
|
UINT32 BinCompSize;
|
|
UINT32 BinFullSize;
|
|
UINT32 BinFullSizeAligned;
|
|
|
|
UINT8 *PlistBuffer;
|
|
XML_DOCUMENT *PlistXml;
|
|
XML_NODE *PlistBundles;
|
|
UINT32 PlistBundlesCount;
|
|
XML_NODE *PlistBundle;
|
|
UINT32 PlistBundleIndex;
|
|
UINT32 PlistBundleCount;
|
|
CONST CHAR8 *PlistBundleKey;
|
|
XML_NODE *BundleExecutable;
|
|
|
|
MKEXT_V2_FILE_ENTRY *MkextExecutableEntry;
|
|
MKEXT_V2_FILE_ENTRY *MkextOutExecutableEntry;
|
|
CHAR8 *BinaryOffsetStrings;
|
|
UINT32 BinaryOffsetStringsSize;
|
|
|
|
ASSERT (Buffer != NULL);
|
|
ASSERT (BufferSize > 0);
|
|
|
|
Decompress = OutBufferSize > 0;
|
|
if (Decompress) {
|
|
ASSERT (OutBuffer != NULL);
|
|
ASSERT (OutMkextSize != NULL);
|
|
}
|
|
|
|
if ( (BufferSize < sizeof (MKEXT_CORE_HEADER))
|
|
|| !OC_TYPE_ALIGNED (MKEXT_CORE_HEADER, Buffer))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
MkextHeader = (MKEXT_HEADER_ANY *)Buffer;
|
|
MkextSize = SwapBytes32 (MkextHeader->Common.Length);
|
|
MkextVersion = SwapBytes32 (MkextHeader->Common.Version);
|
|
NumKexts = SwapBytes32 (MkextHeader->Common.NumKexts);
|
|
|
|
if ( (MkextHeader->Common.Magic != MKEXT_INVERT_MAGIC)
|
|
|| (MkextHeader->Common.Signature != MKEXT_INVERT_SIGNATURE)
|
|
|| (BufferSize != MkextSize)
|
|
|| OcOverflowAddU32 (NumKexts, NumReservedKexts, &NumMaxKexts))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
MkextHeaderOut = NULL;
|
|
|
|
//
|
|
// Mkext v1.
|
|
//
|
|
if (MkextVersion == MKEXT_VERSION_V1) {
|
|
//
|
|
// Validate header and array size.
|
|
// We need to start our offset after the header including reserved kext slots.
|
|
//
|
|
if ( OcOverflowMulAddU32 (sizeof (MKEXT_V1_KEXT), NumKexts, sizeof (MKEXT_V1_HEADER), &Tmp)
|
|
|| (Tmp > MkextSize)
|
|
|| OcOverflowMulAddU32 (sizeof (MKEXT_V1_KEXT), NumMaxKexts, sizeof (MKEXT_V1_HEADER), &CurrentOffset)
|
|
|| (MKEXT_ALIGN (CurrentOffset) < CurrentOffset))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
CurrentOffset = MKEXT_ALIGN (CurrentOffset);
|
|
|
|
if (Decompress) {
|
|
//
|
|
// When decompressing, CurrentOffset needs to be within bounds of OutBufferSize.
|
|
// If not decompressing, this is unnecessary as we are calculating decompressed size only.
|
|
//
|
|
if (CurrentOffset > OutBufferSize) {
|
|
return EFI_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
//
|
|
// Copy header.
|
|
//
|
|
CopyMem (OutBuffer, Buffer, sizeof (MKEXT_V1_HEADER));
|
|
MkextHeaderOut = (MKEXT_HEADER_ANY *)OutBuffer;
|
|
}
|
|
|
|
//
|
|
// Process plists and binaries if present, decompressing as needed.
|
|
//
|
|
for (Index = 0; Index < NumKexts; Index++) {
|
|
PlistFullSize = SwapBytes32 (MkextHeader->V1.Kexts[Index].Plist.FullSize);
|
|
BinFullSize = SwapBytes32 (MkextHeader->V1.Kexts[Index].Binary.FullSize);
|
|
PlistFullSizeAligned = MKEXT_ALIGN (PlistFullSize);
|
|
BinFullSizeAligned = MKEXT_ALIGN (BinFullSize);
|
|
|
|
if ( (PlistFullSizeAligned < PlistFullSize)
|
|
|| (BinFullSizeAligned < BinFullSize))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (Decompress) {
|
|
//
|
|
// Decompress entry.
|
|
//
|
|
PlistOffset = SwapBytes32 (MkextHeader->V1.Kexts[Index].Plist.Offset);
|
|
PlistCompSize = SwapBytes32 (MkextHeader->V1.Kexts[Index].Plist.CompressedSize);
|
|
BinOffset = SwapBytes32 (MkextHeader->V1.Kexts[Index].Binary.Offset);
|
|
BinCompSize = SwapBytes32 (MkextHeader->V1.Kexts[Index].Binary.CompressedSize);
|
|
|
|
if (OcOverflowTriAddU32 (CurrentOffset, PlistFullSizeAligned, BinFullSizeAligned, &Tmp)) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (Tmp > OutBufferSize) {
|
|
return EFI_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
//
|
|
// Compressed size == 0 means no compression.
|
|
//
|
|
if (PlistCompSize > 0) {
|
|
if ( OcOverflowAddU32 (PlistOffset, PlistCompSize, &Tmp)
|
|
|| (Tmp > MkextSize)
|
|
|| (DecompressLZSS (
|
|
&OutBuffer[CurrentOffset],
|
|
PlistFullSize,
|
|
&((UINT8 *)Buffer)[PlistOffset],
|
|
PlistCompSize
|
|
) != PlistFullSize))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
} else {
|
|
if ( OcOverflowAddU32 (PlistOffset, PlistFullSize, &Tmp)
|
|
|| (Tmp > MkextSize))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
CopyMem (&OutBuffer[CurrentOffset], &Buffer[PlistOffset], PlistFullSize);
|
|
}
|
|
|
|
MkextHeaderOut->V1.Kexts[Index].Plist.Offset = SwapBytes32 (CurrentOffset);
|
|
MkextHeaderOut->V1.Kexts[Index].Plist.CompressedSize = 0;
|
|
MkextHeaderOut->V1.Kexts[Index].Plist.FullSize = SwapBytes32 (PlistFullSize);
|
|
MkextHeaderOut->V1.Kexts[Index].Plist.ModifiedSeconds = MkextHeader->V1.Kexts[Index].Plist.ModifiedSeconds;
|
|
CurrentOffset += PlistFullSizeAligned;
|
|
|
|
if (BinFullSize > 0) {
|
|
//
|
|
// Compressed size == 0 means no compression.
|
|
//
|
|
if (BinCompSize > 0) {
|
|
if ( OcOverflowAddU32 (BinOffset, BinCompSize, &Tmp)
|
|
|| (Tmp > MkextSize)
|
|
|| (DecompressLZSS (
|
|
&OutBuffer[CurrentOffset],
|
|
BinFullSize,
|
|
&((UINT8 *)Buffer)[BinOffset],
|
|
BinCompSize
|
|
) != BinFullSize))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
} else {
|
|
if ( OcOverflowAddU32 (BinOffset, BinFullSize, &Tmp)
|
|
|| (Tmp > MkextSize))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
CopyMem (&OutBuffer[CurrentOffset], &Buffer[BinOffset], BinFullSize);
|
|
}
|
|
|
|
MkextHeaderOut->V1.Kexts[Index].Binary.Offset = SwapBytes32 (CurrentOffset);
|
|
MkextHeaderOut->V1.Kexts[Index].Binary.CompressedSize = 0;
|
|
MkextHeaderOut->V1.Kexts[Index].Binary.FullSize = SwapBytes32 (BinFullSize);
|
|
MkextHeaderOut->V1.Kexts[Index].Binary.ModifiedSeconds = MkextHeader->V1.Kexts[Index].Binary.ModifiedSeconds;
|
|
CurrentOffset += BinFullSizeAligned;
|
|
} else {
|
|
ZeroMem (&MkextHeaderOut->V1.Kexts[Index].Binary, sizeof (MKEXT_V1_KEXT_FILE));
|
|
}
|
|
} else {
|
|
//
|
|
// Calculate size only.
|
|
//
|
|
if (OcOverflowTriAddU32 (
|
|
CurrentOffset,
|
|
PlistFullSizeAligned,
|
|
BinFullSizeAligned,
|
|
&CurrentOffset
|
|
))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
}
|
|
|
|
*OutMkextSize = CurrentOffset;
|
|
|
|
//
|
|
// Mkext v2.
|
|
//
|
|
} else if (MkextVersion == MKEXT_VERSION_V2) {
|
|
if (MkextSize < sizeof (MKEXT_V2_HEADER)) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
CurrentOffset = MKEXT_ALIGN (sizeof (MKEXT_V2_HEADER));
|
|
|
|
if (Decompress) {
|
|
//
|
|
// When decompressing, CurrentOffset needs to be within bounds of OutBufferSize.
|
|
// If not decompressing, this is unnecessary as we are calculating decompressed size only.
|
|
//
|
|
if (CurrentOffset > OutBufferSize) {
|
|
return EFI_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
//
|
|
// Copy header.
|
|
//
|
|
CopyMem (OutBuffer, Buffer, sizeof (MKEXT_V2_HEADER));
|
|
MkextHeaderOut = (MKEXT_HEADER_ANY *)OutBuffer;
|
|
}
|
|
|
|
if (!ParseMkextV2Plist (&MkextHeader->V2, &PlistBuffer, &PlistFullSize, &PlistXml, &PlistBundles)) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// All offset strings are kept in array until the plist is regenerated.
|
|
//
|
|
PlistBundlesCount = XmlNodeChildren (PlistBundles);
|
|
BinaryOffsetStrings = NULL;
|
|
|
|
if (Decompress) {
|
|
if (OcOverflowTriMulU32 (PlistBundlesCount, KEXT_OFFSET_STR_LEN, sizeof (CHAR8), &BinaryOffsetStringsSize)) {
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
BinaryOffsetStrings = AllocateZeroPool (BinaryOffsetStringsSize);
|
|
if (BinaryOffsetStrings == NULL) {
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Enumerate bundles.
|
|
//
|
|
for (Index = 0; Index < PlistBundlesCount; Index++) {
|
|
PlistBundle = PlistNodeCast (XmlNodeChild (PlistBundles, Index), PLIST_NODE_TYPE_DICT);
|
|
if (PlistBundle == NULL) {
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
if (Decompress) {
|
|
FreePool (BinaryOffsetStrings);
|
|
}
|
|
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Get executable information, if present.
|
|
//
|
|
PlistBundleCount = PlistDictChildren (PlistBundle);
|
|
for (PlistBundleIndex = 0; PlistBundleIndex < PlistBundleCount; PlistBundleIndex++) {
|
|
PlistBundleKey = PlistKeyValue (PlistDictChild (PlistBundle, PlistBundleIndex, &BundleExecutable));
|
|
if (PlistBundleKey == NULL) {
|
|
continue;
|
|
}
|
|
|
|
if (AsciiStrCmp (PlistBundleKey, MKEXT_EXECUTABLE_KEY) == 0) {
|
|
if ( !PlistIntegerValue (BundleExecutable, &BinOffset, sizeof (BinOffset), TRUE)
|
|
|| (BinOffset == 0)
|
|
|| (BinOffset > MkextSize - sizeof (MKEXT_V2_FILE_ENTRY)))
|
|
{
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
if (Decompress) {
|
|
FreePool (BinaryOffsetStrings);
|
|
}
|
|
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
MkextExecutableEntry = (MKEXT_V2_FILE_ENTRY *)&Buffer[BinOffset];
|
|
BinCompSize = SwapBytes32 (MkextExecutableEntry->CompressedSize);
|
|
BinFullSize = SwapBytes32 (MkextExecutableEntry->FullSize);
|
|
BinFullSizeAligned = MKEXT_ALIGN (BinFullSize);
|
|
|
|
if (OcOverflowTriAddU32 (CurrentOffset, sizeof (MKEXT_V2_FILE_ENTRY), BinFullSizeAligned, &NewOffset)) {
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
if (Decompress) {
|
|
FreePool (BinaryOffsetStrings);
|
|
}
|
|
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (Decompress) {
|
|
if (NewOffset > OutBufferSize) {
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
FreePool (BinaryOffsetStrings);
|
|
return EFI_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
MkextOutExecutableEntry = (MKEXT_V2_FILE_ENTRY *)&OutBuffer[CurrentOffset];
|
|
MkextOutExecutableEntry->CompressedSize = 0;
|
|
MkextOutExecutableEntry->FullSize = SwapBytes32 (BinFullSize);
|
|
|
|
//
|
|
// Compressed size == 0 means no compression.
|
|
//
|
|
if (BinCompSize > 0) {
|
|
if ( OcOverflowAddU32 (BinOffset, BinCompSize, &Tmp)
|
|
|| (Tmp > MkextSize - sizeof (MKEXT_V2_FILE_ENTRY))
|
|
|| (DecompressZLIB (
|
|
MkextOutExecutableEntry->Data,
|
|
BinFullSize,
|
|
MkextExecutableEntry->Data,
|
|
BinCompSize
|
|
) != BinFullSize))
|
|
{
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
FreePool (BinaryOffsetStrings);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
} else {
|
|
if ( OcOverflowAddU32 (BinOffset, BinFullSize, &Tmp)
|
|
|| (Tmp > MkextSize - sizeof (MKEXT_V2_FILE_ENTRY)))
|
|
{
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
FreePool (BinaryOffsetStrings);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
CopyMem (MkextOutExecutableEntry->Data, MkextExecutableEntry->Data, BinFullSize);
|
|
}
|
|
|
|
if (!AsciiUint64ToLowerHex (
|
|
&BinaryOffsetStrings[Index * KEXT_OFFSET_STR_LEN],
|
|
KEXT_OFFSET_STR_LEN,
|
|
CurrentOffset
|
|
))
|
|
{
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
FreePool (BinaryOffsetStrings);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
XmlNodeChangeContent (BundleExecutable, &BinaryOffsetStrings[Index * KEXT_OFFSET_STR_LEN]);
|
|
}
|
|
|
|
//
|
|
// Move to next bundle.
|
|
//
|
|
CurrentOffset = NewOffset;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Decompress) {
|
|
PlistFullSize = UpdateMkextV2Plist (&MkextHeaderOut->V2, OutBufferSize, PlistXml, CurrentOffset);
|
|
}
|
|
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
if (Decompress) {
|
|
FreePool (BinaryOffsetStrings);
|
|
|
|
if ( (PlistFullSize == 0)
|
|
|| OcOverflowAddU32 (CurrentOffset, PlistFullSize, OutMkextSize))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
} else {
|
|
//
|
|
// Account for plist, future plist expansion for each bundle,
|
|
// and additional headers for future kext injection.
|
|
//
|
|
PlistFullSize = SwapBytes32 (MkextHeader->V2.PlistFullSize);
|
|
if ( OcOverflowAddU32 (CurrentOffset, PlistFullSize, &CurrentOffset)
|
|
|| OcOverflowMulAddU32 (PlistBundlesCount, PLIST_EXPANSION_SIZE, CurrentOffset, &CurrentOffset)
|
|
|| OcOverflowMulAddU32 (NumReservedKexts, sizeof (MKEXT_V2_FILE_ENTRY), CurrentOffset, OutMkextSize))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Unsupported version.
|
|
//
|
|
} else {
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
if (Decompress) {
|
|
UpdateMkextLengthChecksum (MkextHeaderOut, *OutMkextSize);
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
BOOLEAN
|
|
MkextCheckCpuType (
|
|
IN UINT8 *Mkext,
|
|
IN UINT32 MkextSize,
|
|
IN MACH_CPU_TYPE CpuType
|
|
)
|
|
{
|
|
MKEXT_HEADER_ANY *MkextHeader;
|
|
MACH_CPU_TYPE MkextCpuType;
|
|
|
|
ASSERT (Mkext != NULL);
|
|
ASSERT (MkextSize > 0);
|
|
|
|
if ( (MkextSize < sizeof (MKEXT_CORE_HEADER))
|
|
|| !OC_TYPE_ALIGNED (MKEXT_CORE_HEADER, Mkext))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
MkextHeader = (MKEXT_HEADER_ANY *)Mkext;
|
|
MkextCpuType = SwapBytes32 (MkextHeader->Common.CpuType);
|
|
|
|
if ( (MkextHeader->Common.Magic != MKEXT_INVERT_MAGIC)
|
|
|| (MkextHeader->Common.Signature != MKEXT_INVERT_SIGNATURE)
|
|
|| (MkextSize != SwapBytes32 (MkextHeader->Common.Length)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return MkextCpuType == CpuType;
|
|
}
|
|
|
|
EFI_STATUS
|
|
MkextContextInit (
|
|
IN OUT MKEXT_CONTEXT *Context,
|
|
IN OUT UINT8 *Mkext,
|
|
IN UINT32 MkextSize,
|
|
IN UINT32 MkextAllocSize
|
|
)
|
|
{
|
|
MKEXT_HEADER_ANY *MkextHeader;
|
|
UINT32 MkextVersion;
|
|
UINT32 MkextHeaderSize;
|
|
MACH_CPU_TYPE CpuType;
|
|
BOOLEAN Is32Bit;
|
|
UINT32 NumKexts;
|
|
UINT32 NumMaxKexts;
|
|
|
|
UINT32 Tmp;
|
|
UINT32 Index;
|
|
UINT32 StartingOffset;
|
|
UINT32 CurrentOffset;
|
|
|
|
UINT8 *PlistBuffer;
|
|
XML_DOCUMENT *PlistXml;
|
|
UINT32 PlistOffset;
|
|
UINT32 PlistFullSize;
|
|
XML_NODE *PlistBundles;
|
|
|
|
//
|
|
// Assumptions:
|
|
// Kexts are aligned to 8 bytes.
|
|
// Patching or blocking requires kexts to be decompressed.
|
|
// Plist (for v2) is at end of mkext.
|
|
// Mkext is big-endian per XNU requirements.
|
|
//
|
|
|
|
ASSERT (Context != NULL);
|
|
ASSERT (Mkext != NULL);
|
|
ASSERT (MkextSize > 0);
|
|
ASSERT (MkextAllocSize >= MkextSize);
|
|
|
|
if ( (MkextSize < sizeof (MKEXT_CORE_HEADER))
|
|
|| !OC_TYPE_ALIGNED (MKEXT_CORE_HEADER, Mkext))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
MkextHeader = (MKEXT_HEADER_ANY *)Mkext;
|
|
MkextVersion = SwapBytes32 (MkextHeader->Common.Version);
|
|
NumKexts = SwapBytes32 (MkextHeader->Common.NumKexts);
|
|
CpuType = SwapBytes32 (MkextHeader->Common.CpuType);
|
|
|
|
if ( (MkextHeader->Common.Magic != MKEXT_INVERT_MAGIC)
|
|
|| (MkextHeader->Common.Signature != MKEXT_INVERT_SIGNATURE)
|
|
|| (MkextSize != SwapBytes32 (MkextHeader->Common.Length)))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (CpuType == MachCpuTypeI386) {
|
|
Is32Bit = TRUE;
|
|
} else if (CpuType == MachCpuTypeX8664) {
|
|
Is32Bit = FALSE;
|
|
} else {
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
//
|
|
// Mkext v1.
|
|
//
|
|
if (MkextVersion == MKEXT_VERSION_V1) {
|
|
//
|
|
// Validate header and array size.
|
|
//
|
|
if ( OcOverflowMulAddU32 (sizeof (MKEXT_V1_KEXT), NumKexts, sizeof (MKEXT_V1_HEADER), &MkextHeaderSize)
|
|
|| (MkextHeaderSize > MkextSize))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Calculate available kext slots. This value is assumed to be under the UINT32 max later on.
|
|
//
|
|
// Below finds the lowest offset to a plist or a binary, which is used to locate
|
|
// the end of the usable space allocated for kext slots.
|
|
//
|
|
StartingOffset = 0;
|
|
for (Index = 0; Index < NumKexts; Index++) {
|
|
CurrentOffset = SwapBytes32 (MkextHeader->V1.Kexts[Index].Plist.Offset);
|
|
if ((StartingOffset == 0) || (CurrentOffset < StartingOffset)) {
|
|
StartingOffset = CurrentOffset;
|
|
}
|
|
|
|
//
|
|
// A binary entry of zero size indicates no binary is present.
|
|
//
|
|
if (MkextHeader->V1.Kexts[Index].Binary.FullSize > 0) {
|
|
CurrentOffset = SwapBytes32 (MkextHeader->V1.Kexts[Index].Binary.Offset);
|
|
if (CurrentOffset < StartingOffset) {
|
|
StartingOffset = CurrentOffset;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( (StartingOffset < MkextHeaderSize)
|
|
|| (StartingOffset > MkextSize))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
Tmp = (StartingOffset - MkextHeaderSize) / sizeof (MKEXT_V1_KEXT);
|
|
if ( OcOverflowAddU32 (Tmp, NumKexts, &NumMaxKexts)
|
|
|| (NumMaxKexts == MAX_UINT32))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Mkext v2.
|
|
//
|
|
} else if (MkextVersion == MKEXT_VERSION_V2) {
|
|
if ( (MkextSize < sizeof (MKEXT_V2_HEADER))
|
|
|| !ParseMkextV2Plist (&MkextHeader->V2, &PlistBuffer, &PlistFullSize, &PlistXml, &PlistBundles))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
PlistOffset = SwapBytes32 (MkextHeader->V2.PlistOffset);
|
|
|
|
if ( OcOverflowAddU32 (PlistOffset, PlistFullSize, &Tmp)
|
|
|| (Tmp != MkextSize))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Unsupported version.
|
|
//
|
|
} else {
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
ZeroMem (Context, sizeof (*Context));
|
|
Context->Mkext = Mkext;
|
|
Context->MkextSize = MkextSize;
|
|
Context->MkextHeader = MkextHeader;
|
|
Context->MkextAllocSize = MkextAllocSize;
|
|
Context->MkextVersion = MkextVersion;
|
|
Context->Is32Bit = Is32Bit;
|
|
Context->NumKexts = NumKexts;
|
|
InitializeListHead (&Context->CachedKexts);
|
|
|
|
if (MkextVersion == MKEXT_VERSION_V1) {
|
|
Context->NumMaxKexts = NumMaxKexts;
|
|
} else if (MkextVersion == MKEXT_VERSION_V2) {
|
|
Context->MkextInfoOffset = PlistOffset;
|
|
Context->MkextInfo = PlistBuffer;
|
|
Context->MkextInfoDocument = PlistXml;
|
|
Context->MkextKexts = PlistBundles;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
VOID
|
|
MkextContextFree (
|
|
IN OUT MKEXT_CONTEXT *Context
|
|
)
|
|
{
|
|
MKEXT_KEXT *MkextKext;
|
|
LIST_ENTRY *KextLink;
|
|
|
|
ASSERT (Context != NULL);
|
|
|
|
while (!IsListEmpty (&Context->CachedKexts)) {
|
|
KextLink = GetFirstNode (&Context->CachedKexts);
|
|
MkextKext = GET_MKEXT_KEXT_FROM_LINK (KextLink);
|
|
RemoveEntryList (KextLink);
|
|
|
|
FreePool (MkextKext->Identifier);
|
|
FreePool (MkextKext);
|
|
}
|
|
|
|
if (Context->MkextInfoDocument != NULL) {
|
|
XmlDocumentFree (Context->MkextInfoDocument);
|
|
}
|
|
|
|
if (Context->MkextInfo != NULL) {
|
|
FreePool (Context->MkextInfo);
|
|
}
|
|
|
|
ZeroMem (Context, sizeof (*Context));
|
|
}
|
|
|
|
EFI_STATUS
|
|
MkextReserveKextSize (
|
|
IN OUT UINT32 *ReservedInfoSize,
|
|
IN OUT UINT32 *ReservedExeSize,
|
|
IN UINT32 InfoPlistSize,
|
|
IN UINT8 *Executable OPTIONAL,
|
|
IN UINT32 ExecutableSize OPTIONAL,
|
|
IN BOOLEAN Is32Bit
|
|
)
|
|
{
|
|
OC_MACHO_CONTEXT Context;
|
|
|
|
ASSERT (ReservedInfoSize != NULL);
|
|
ASSERT (ReservedExeSize != NULL);
|
|
|
|
InfoPlistSize = MACHO_ALIGN (InfoPlistSize);
|
|
|
|
if (Executable != NULL) {
|
|
ASSERT (ExecutableSize > 0);
|
|
if (!MachoInitializeContext (&Context, Executable, ExecutableSize, 0, ExecutableSize, Is32Bit)) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
ExecutableSize = MachoGetVmSize (&Context);
|
|
if (ExecutableSize == 0) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
if ( OcOverflowAddU32 (*ReservedInfoSize, InfoPlistSize, &InfoPlistSize)
|
|
|| OcOverflowAddU32 (*ReservedExeSize, ExecutableSize, &ExecutableSize))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
*ReservedInfoSize = InfoPlistSize;
|
|
*ReservedExeSize = ExecutableSize;
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
MkextInjectKext (
|
|
IN OUT MKEXT_CONTEXT *Context,
|
|
IN CONST CHAR8 *Identifier OPTIONAL,
|
|
IN CONST CHAR8 *BundlePath,
|
|
IN CONST CHAR8 *InfoPlist,
|
|
IN UINT32 InfoPlistSize,
|
|
IN UINT8 *Executable OPTIONAL,
|
|
IN UINT32 ExecutableSize OPTIONAL,
|
|
OUT CHAR8 BundleVersion[MAX_INFO_BUNDLE_VERSION_KEY_SIZE] OPTIONAL
|
|
)
|
|
{
|
|
UINT32 MkextNewSize;
|
|
UINT32 PlistOffset;
|
|
UINT32 BinOffset;
|
|
UINT32 InfoPlistSizeAligned;
|
|
UINT32 ExecutableSizeAligned;
|
|
|
|
CHAR8 *PlistBuffer;
|
|
CHAR8 *PlistExported;
|
|
UINT32 PlistExportedSize;
|
|
XML_DOCUMENT *PlistXml;
|
|
XML_NODE *PlistRoot;
|
|
XML_NODE *KextPlistValue;
|
|
BOOLEAN PlistFailed;
|
|
UINT32 PlistBundleIndex;
|
|
UINT32 PlistBundleCount;
|
|
CONST CHAR8 *PlistBundleKey;
|
|
XML_NODE *PlistBundleKeyValue;
|
|
CONST CHAR8 *BundleVerStr;
|
|
|
|
CONST CHAR8 *TmpKeyValue;
|
|
UINT32 FieldCount;
|
|
UINT32 FieldIndex;
|
|
|
|
CHAR8 ExecutableSourceAddrStr[24];
|
|
MKEXT_V2_FILE_ENTRY *MkextExecutableEntry;
|
|
|
|
ASSERT (Context != NULL);
|
|
ASSERT (BundlePath != NULL);
|
|
ASSERT (InfoPlist != NULL);
|
|
ASSERT (InfoPlistSize > 0);
|
|
|
|
BinOffset = 0;
|
|
|
|
//
|
|
// If an identifier was passed, ensure it does not already exist.
|
|
//
|
|
if (Identifier != NULL) {
|
|
if (InternalCachedMkextKext (Context, Identifier) != NULL) {
|
|
DEBUG ((DEBUG_INFO, "OCAK: Bundle %a is already present in mkext\n", Identifier));
|
|
return EFI_ALREADY_STARTED;
|
|
}
|
|
}
|
|
|
|
PlistBuffer = AllocateCopyPool (InfoPlistSize, InfoPlist);
|
|
if (PlistBuffer == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
PlistXml = XmlDocumentParse (PlistBuffer, InfoPlistSize, FALSE);
|
|
if (PlistXml == NULL) {
|
|
FreePool (PlistBuffer);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
PlistRoot = PlistNodeCast (PlistDocumentRoot (PlistXml), PLIST_NODE_TYPE_DICT);
|
|
if (PlistRoot == NULL) {
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// We are not supposed to check for this, it is XNU responsibility, which reliably panics.
|
|
// However, to avoid certain users making this kind of mistake, we still provide some
|
|
// code in debug mode to diagnose it.
|
|
//
|
|
DEBUG_CODE_BEGIN ();
|
|
FieldCount = PlistDictChildren (PlistRoot);
|
|
|
|
if (BundleVersion != NULL) {
|
|
for (FieldIndex = 0; FieldIndex < FieldCount; ++FieldIndex) {
|
|
TmpKeyValue = PlistKeyValue (PlistDictChild (PlistRoot, FieldIndex, &KextPlistValue));
|
|
if (TmpKeyValue == NULL) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Match CFBundleVersion.
|
|
//
|
|
if (AsciiStrCmp (TmpKeyValue, INFO_BUNDLE_VERSION_KEY) == 0) {
|
|
if (PlistNodeCast (KextPlistValue, PLIST_NODE_TYPE_STRING) == NULL) {
|
|
break;
|
|
}
|
|
|
|
BundleVerStr = XmlNodeContent (KextPlistValue);
|
|
AsciiStrCpyS (BundleVersion, MAX_INFO_BUNDLE_VERSION_KEY_SIZE, BundleVerStr);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Executable == NULL) {
|
|
for (FieldIndex = 0; FieldIndex < FieldCount; ++FieldIndex) {
|
|
TmpKeyValue = PlistKeyValue (PlistDictChild (PlistRoot, FieldIndex, NULL));
|
|
if (TmpKeyValue == NULL) {
|
|
continue;
|
|
}
|
|
|
|
if (AsciiStrCmp (TmpKeyValue, INFO_BUNDLE_EXECUTABLE_KEY) == 0) {
|
|
DEBUG ((DEBUG_ERROR, "OCK: Plist-only kext has %a key\n", INFO_BUNDLE_EXECUTABLE_KEY));
|
|
ASSERT (FALSE);
|
|
CpuDeadLoop ();
|
|
}
|
|
}
|
|
}
|
|
|
|
DEBUG_CODE_END ();
|
|
|
|
Identifier = NULL;
|
|
PlistBundleCount = PlistDictChildren (PlistRoot);
|
|
for (PlistBundleIndex = 0; PlistBundleIndex < PlistBundleCount; PlistBundleIndex++) {
|
|
PlistBundleKey = PlistKeyValue (PlistDictChild (PlistRoot, PlistBundleIndex, &PlistBundleKeyValue));
|
|
if ((PlistBundleKey == NULL) || (PlistBundleKeyValue == NULL)) {
|
|
continue;
|
|
}
|
|
|
|
if (AsciiStrCmp (PlistBundleKey, INFO_BUNDLE_IDENTIFIER_KEY) == 0) {
|
|
Identifier = XmlNodeContent (PlistBundleKeyValue);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Identifier == NULL) {
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Mkext v1.
|
|
//
|
|
if (Context->MkextVersion == MKEXT_VERSION_V1) {
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
|
|
if (Context->NumKexts >= Context->NumMaxKexts) {
|
|
return EFI_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
InfoPlistSizeAligned = MKEXT_ALIGN (InfoPlistSize);
|
|
if (InfoPlistSizeAligned < InfoPlistSize) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Plist will be placed at end of mkext.
|
|
//
|
|
PlistOffset = Context->MkextSize;
|
|
if (OcOverflowAddU32 (PlistOffset, InfoPlistSizeAligned, &MkextNewSize)) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (MkextNewSize > Context->MkextAllocSize) {
|
|
return EFI_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
//
|
|
// Executable, if present, will be placed right after plist.
|
|
//
|
|
if (Executable != NULL) {
|
|
ASSERT (ExecutableSize > 0);
|
|
|
|
BinOffset = MkextNewSize;
|
|
if (!InternalParseKextBinary (&Executable, &ExecutableSize, Context->Is32Bit)) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
ExecutableSizeAligned = MKEXT_ALIGN (ExecutableSize);
|
|
if ( (ExecutableSizeAligned < ExecutableSize)
|
|
|| OcOverflowAddU32 (BinOffset, ExecutableSizeAligned, &MkextNewSize))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (MkextNewSize > Context->MkextAllocSize) {
|
|
return EFI_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
CopyMem (&Context->Mkext[BinOffset], Executable, ExecutableSize);
|
|
Context->MkextHeader->V1.Kexts[Context->NumKexts].Binary.Offset = SwapBytes32 (BinOffset);
|
|
Context->MkextHeader->V1.Kexts[Context->NumKexts].Binary.CompressedSize = 0;
|
|
Context->MkextHeader->V1.Kexts[Context->NumKexts].Binary.FullSize = SwapBytes32 (ExecutableSize);
|
|
Context->MkextHeader->V1.Kexts[Context->NumKexts].Binary.ModifiedSeconds = 0;
|
|
}
|
|
|
|
CopyMem (&Context->Mkext[PlistOffset], InfoPlist, InfoPlistSize);
|
|
Context->MkextHeader->V1.Kexts[Context->NumKexts].Plist.Offset = SwapBytes32 (PlistOffset);
|
|
Context->MkextHeader->V1.Kexts[Context->NumKexts].Plist.CompressedSize = 0;
|
|
Context->MkextHeader->V1.Kexts[Context->NumKexts].Plist.FullSize = SwapBytes32 (InfoPlistSize);
|
|
Context->MkextHeader->V1.Kexts[Context->NumKexts].Plist.ModifiedSeconds = 0;
|
|
|
|
//
|
|
// Assumption:
|
|
// NumKexts is checked to be under MaxNumKexts, which is assumed to be under
|
|
// the max value of UINT32 during context creation. This operation thus cannot overflow.
|
|
//
|
|
Context->MkextSize = MkextNewSize;
|
|
Context->NumKexts++;
|
|
|
|
//
|
|
// Mkext v2.
|
|
//
|
|
} else if (Context->MkextVersion == MKEXT_VERSION_V2) {
|
|
//
|
|
// Executable, if present, will be placed at end of mkext and plist will be moved further out.
|
|
//
|
|
PlistOffset = Context->MkextInfoOffset;
|
|
PlistFailed = FALSE;
|
|
if (Executable != NULL) {
|
|
ASSERT (ExecutableSize > 0);
|
|
|
|
BinOffset = PlistOffset;
|
|
if (!InternalParseKextBinary (&Executable, &ExecutableSize, Context->Is32Bit)) {
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
ExecutableSizeAligned = MKEXT_ALIGN (ExecutableSize);
|
|
if ( (ExecutableSizeAligned < ExecutableSize)
|
|
|| OcOverflowTriAddU32 (BinOffset, sizeof (MKEXT_V2_FILE_ENTRY), ExecutableSizeAligned, &PlistOffset))
|
|
{
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (PlistOffset >= Context->MkextAllocSize) {
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
return EFI_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
PlistFailed |= !AsciiUint64ToLowerHex (ExecutableSourceAddrStr, sizeof (ExecutableSourceAddrStr), BinOffset);
|
|
PlistFailed |= XmlNodeAppend (PlistRoot, "key", NULL, MKEXT_EXECUTABLE_KEY) == NULL;
|
|
PlistFailed |= XmlNodeAppend (PlistRoot, "integer", MKEXT_INFO_INTEGER_ATTRIBUTES, ExecutableSourceAddrStr) == NULL;
|
|
}
|
|
|
|
//
|
|
// Add bundle path.
|
|
//
|
|
PlistFailed |= XmlNodeAppend (PlistRoot, "key", NULL, MKEXT_BUNDLE_PATH_KEY) == NULL;
|
|
PlistFailed |= XmlNodeAppend (PlistRoot, "string", NULL, BundlePath) == NULL;
|
|
if (PlistFailed) {
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
//
|
|
// Strip down Info.plist for appending to primary plist.
|
|
//
|
|
PlistExported = XmlDocumentExport (PlistXml, &PlistExportedSize, 2, FALSE);
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
|
|
if (XmlNodeAppend (Context->MkextKexts, "dict", NULL, PlistExported) == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
Context->MkextInfoOffset = PlistOffset;
|
|
|
|
if (Executable != NULL) {
|
|
MkextExecutableEntry = (MKEXT_V2_FILE_ENTRY *)&Context->Mkext[BinOffset];
|
|
MkextExecutableEntry->CompressedSize = 0;
|
|
MkextExecutableEntry->FullSize = SwapBytes32 (ExecutableSize);
|
|
CopyMem (MkextExecutableEntry->Data, Executable, ExecutableSize);
|
|
|
|
BinOffset += OFFSET_OF (MKEXT_V2_FILE_ENTRY, Data);
|
|
}
|
|
|
|
//
|
|
// Unsupported version.
|
|
//
|
|
} else {
|
|
XmlDocumentFree (PlistXml);
|
|
FreePool (PlistBuffer);
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
//
|
|
// Add kext to cache.
|
|
//
|
|
InsertCachedMkextKext (Context, Identifier, BinOffset, ExecutableSize);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
MkextContextApplyPatch (
|
|
IN OUT MKEXT_CONTEXT *Context,
|
|
IN CONST CHAR8 *Identifier,
|
|
IN PATCHER_GENERIC_PATCH *Patch
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
PATCHER_CONTEXT Patcher;
|
|
|
|
ASSERT (Context != NULL);
|
|
ASSERT (Identifier != NULL);
|
|
ASSERT (Patch != NULL);
|
|
|
|
Status = PatcherInitContextFromMkext (&Patcher, Context, Identifier);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_INFO, "OCAK: Failed to mkext find %a - %r\n", Identifier, Status));
|
|
return Status;
|
|
}
|
|
|
|
return PatcherApplyGenericPatch (&Patcher, Patch);
|
|
}
|
|
|
|
EFI_STATUS
|
|
MkextContextApplyQuirk (
|
|
IN OUT MKEXT_CONTEXT *Context,
|
|
IN KERNEL_QUIRK_NAME Quirk,
|
|
IN UINT32 KernelVersion
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
KERNEL_QUIRK *KernelQuirk;
|
|
PATCHER_CONTEXT Patcher;
|
|
|
|
ASSERT (Context != NULL);
|
|
|
|
KernelQuirk = &gKernelQuirks[Quirk];
|
|
ASSERT (KernelQuirk->Identifier != NULL);
|
|
|
|
Status = PatcherInitContextFromMkext (&Patcher, Context, KernelQuirk->Identifier);
|
|
if (!EFI_ERROR (Status)) {
|
|
return KernelQuirk->PatchFunction (&Patcher, KernelVersion);
|
|
}
|
|
|
|
//
|
|
// It is up to the function to decide whether this is critical or not.
|
|
//
|
|
DEBUG ((DEBUG_INFO, "OCAK: Failed to mkext find %a - %r\n", KernelQuirk->Identifier, Status));
|
|
return KernelQuirk->PatchFunction (NULL, KernelVersion);
|
|
}
|
|
|
|
EFI_STATUS
|
|
MkextContextBlock (
|
|
IN OUT MKEXT_CONTEXT *Context,
|
|
IN CONST CHAR8 *Identifier,
|
|
IN BOOLEAN Exclude
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
PATCHER_CONTEXT Patcher;
|
|
|
|
ASSERT (Context != NULL);
|
|
ASSERT (Identifier != NULL);
|
|
|
|
Status = PatcherInitContextFromMkext (&Patcher, Context, Identifier);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_INFO, "OCAK: Failed to mkext find %a - %r\n", Identifier, Status));
|
|
return Status;
|
|
}
|
|
|
|
return PatcherBlockKext (&Patcher);
|
|
}
|
|
|
|
EFI_STATUS
|
|
MkextInjectPatchComplete (
|
|
IN OUT MKEXT_CONTEXT *Context
|
|
)
|
|
{
|
|
UINT32 MkextPlistSize;
|
|
|
|
ASSERT (Context != NULL);
|
|
|
|
//
|
|
// Mkext v1.
|
|
//
|
|
if (Context->MkextVersion == MKEXT_VERSION_V1) {
|
|
Context->MkextHeader->Common.NumKexts = SwapBytes32 (Context->NumKexts);
|
|
|
|
//
|
|
// Mkext v2.
|
|
//
|
|
} else if (Context->MkextVersion == MKEXT_VERSION_V2) {
|
|
MkextPlistSize = UpdateMkextV2Plist (
|
|
&Context->MkextHeader->V2,
|
|
Context->MkextAllocSize,
|
|
Context->MkextInfoDocument,
|
|
Context->MkextInfoOffset
|
|
);
|
|
Context->MkextSize = Context->MkextInfoOffset + MkextPlistSize;
|
|
|
|
//
|
|
// Unsupported version.
|
|
//
|
|
} else {
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
UpdateMkextLengthChecksum (Context->MkextHeader, Context->MkextSize);
|
|
return EFI_SUCCESS;
|
|
}
|