mirror of
https://github.com/acidanthera/OpenCorePkg.git
synced 2025-12-08 19:25:01 +00:00
1331 lines
45 KiB
C
1331 lines
45 KiB
C
/** @file
|
|
Copyright (C) 2019, vit9696. All rights reserved.
|
|
|
|
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 <IndustryStandard/AppleKmodInfo.h>
|
|
|
|
#include <Library/BaseLib.h>
|
|
#include <Library/BaseMemoryLib.h>
|
|
#include <Library/DebugLib.h>
|
|
#include <Library/MemoryAllocationLib.h>
|
|
#include <Library/OcAppleKernelLib.h>
|
|
#include <Library/OcMachoLib.h>
|
|
#include <Library/OcStringLib.h>
|
|
|
|
#include "PrelinkedInternal.h"
|
|
|
|
STATIC
|
|
UINT64
|
|
PrelinkedFindLastLoadAddress (
|
|
IN XML_NODE *KextList
|
|
)
|
|
{
|
|
UINT32 KextCount;
|
|
UINT32 KextIndex;
|
|
UINT32 FieldIndex;
|
|
UINT32 FieldCount;
|
|
XML_NODE *LastKext;
|
|
CONST CHAR8 *KextPlistKey;
|
|
XML_NODE *KextPlistValue;
|
|
UINT64 LoadAddress;
|
|
UINT64 LoadSize;
|
|
|
|
KextCount = XmlNodeChildren (KextList);
|
|
if (KextCount == 0) {
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Here we make an assumption that last kext has the highest load address,
|
|
// yet there might be an arbitrary amount of trailing executable-less kexts.
|
|
//
|
|
for (KextIndex = 1; KextIndex <= KextCount; ++KextIndex) {
|
|
LastKext = PlistNodeCast (XmlNodeChild (KextList, KextCount - KextIndex), PLIST_NODE_TYPE_DICT);
|
|
if (LastKext == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
LoadAddress = 0;
|
|
LoadSize = 0;
|
|
|
|
FieldCount = PlistDictChildren (LastKext);
|
|
for (FieldIndex = 0; FieldIndex < FieldCount; ++FieldIndex) {
|
|
KextPlistKey = PlistKeyValue (PlistDictChild (LastKext, FieldIndex, &KextPlistValue));
|
|
if (KextPlistKey == NULL) {
|
|
continue;
|
|
}
|
|
|
|
if ((LoadAddress == 0) && (AsciiStrCmp (KextPlistKey, PRELINK_INFO_EXECUTABLE_LOAD_ADDR_KEY) == 0)) {
|
|
if (!PlistIntegerValue (KextPlistValue, &LoadAddress, sizeof (LoadAddress), TRUE)) {
|
|
return 0;
|
|
}
|
|
} else if ((LoadSize == 0) && (AsciiStrCmp (KextPlistKey, PRELINK_INFO_EXECUTABLE_SIZE_KEY) == 0)) {
|
|
if (!PlistIntegerValue (KextPlistValue, &LoadSize, sizeof (LoadSize), TRUE)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if ((LoadSize != 0) && (LoadAddress != 0)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (OcOverflowAddU64 (LoadAddress, LoadSize, &LoadAddress)) {
|
|
return 0;
|
|
}
|
|
|
|
if (LoadAddress != 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return LoadAddress;
|
|
}
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
PrelinkedGetSegmentsFromMacho (
|
|
IN OC_MACHO_CONTEXT *MachoContext,
|
|
OUT MACH_SEGMENT_COMMAND_ANY **PrelinkedInfoSegment,
|
|
OUT MACH_SECTION_ANY **PrelinkedInfoSection
|
|
)
|
|
{
|
|
*PrelinkedInfoSegment = MachoGetSegmentByName (
|
|
MachoContext,
|
|
PRELINK_INFO_SEGMENT
|
|
);
|
|
if (*PrelinkedInfoSegment == NULL) {
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
if ((MachoContext->Is32Bit ? (*PrelinkedInfoSegment)->Segment32.FileOffset : (*PrelinkedInfoSegment)->Segment64.FileOffset) > MAX_UINT32) {
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
*PrelinkedInfoSection = MachoGetSectionByName (
|
|
MachoContext,
|
|
*PrelinkedInfoSegment,
|
|
PRELINK_INFO_SECTION
|
|
);
|
|
if (*PrelinkedInfoSection == NULL) {
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
if ((MachoContext->Is32Bit ? (*PrelinkedInfoSection)->Section32.Size : (*PrelinkedInfoSection)->Section64.Size) > MAX_UINT32) {
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
InternalConnectExternalSymtab (
|
|
IN OUT OC_MACHO_CONTEXT *Context,
|
|
OUT OC_MACHO_CONTEXT *InnerContext,
|
|
IN UINT8 *Buffer,
|
|
IN UINT32 BufferSize,
|
|
OUT BOOLEAN *KernelCollection OPTIONAL
|
|
)
|
|
{
|
|
MACH_HEADER_ANY *Header;
|
|
MACH_SEGMENT_COMMAND_64 *Segment;
|
|
BOOLEAN IsKernelCollection;
|
|
|
|
//
|
|
// Detect kernel type.
|
|
//
|
|
Header = MachoGetMachHeader (Context);
|
|
IsKernelCollection = (Context->Is32Bit ?
|
|
Header->Header32.FileType : Header->Header64.FileType) == MachHeaderFileTypeFileSet;
|
|
|
|
if (KernelCollection != NULL) {
|
|
*KernelCollection = IsKernelCollection;
|
|
}
|
|
|
|
//
|
|
// When dealing with the kernel collections the actual kernel is pointed by one of the segments.
|
|
//
|
|
if (IsKernelCollection) {
|
|
Segment = MachoGetSegmentByName64 (
|
|
Context,
|
|
"__TEXT_EXEC"
|
|
);
|
|
if ((Segment == NULL) || (Segment->VirtualAddress < Segment->FileOffset)) {
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCAK: KC symtab failed locating inner %Lx %Lx (%d)\n",
|
|
Segment != NULL ? Segment->VirtualAddress : 0,
|
|
Segment != NULL ? Segment->FileOffset : 0,
|
|
Segment != NULL
|
|
));
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (!MachoInitializeContext64 (
|
|
InnerContext,
|
|
Buffer,
|
|
BufferSize,
|
|
(UINT32)Segment->FileOffset,
|
|
(UINT32)(BufferSize - Segment->FileOffset)
|
|
))
|
|
{
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCAK: KC symtab failed initialising inner %Lx %x\n",
|
|
Segment->FileOffset,
|
|
BufferSize
|
|
));
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (!MachoInitialiseSymtabsExternal (Context, InnerContext)) {
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCAK: KC symtab failed getting symtab from inner %Lx %x\n",
|
|
Segment->FileOffset,
|
|
BufferSize
|
|
));
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
PrelinkedContextInit (
|
|
IN OUT PRELINKED_CONTEXT *Context,
|
|
IN OUT UINT8 *Prelinked,
|
|
IN UINT32 PrelinkedSize,
|
|
IN UINT32 PrelinkedAllocSize,
|
|
IN BOOLEAN Is32Bit
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
XML_NODE *DocumentRoot;
|
|
XML_NODE *PrelinkedInfoRoot;
|
|
CONST CHAR8 *PrelinkedInfoRootKey;
|
|
UINT64 SegmentEndOffset;
|
|
UINT32 PrelinkedInfoRootIndex;
|
|
UINT32 PrelinkedInfoRootCount;
|
|
PRELINKED_KEXT *PrelinkedKext;
|
|
|
|
ASSERT (Context != NULL);
|
|
ASSERT (Prelinked != NULL);
|
|
ASSERT (PrelinkedSize > 0);
|
|
ASSERT (PrelinkedAllocSize >= PrelinkedSize);
|
|
|
|
ZeroMem (Context, sizeof (*Context));
|
|
|
|
Context->Prelinked = Prelinked;
|
|
Context->PrelinkedSize = MACHO_ALIGN (PrelinkedSize);
|
|
Context->PrelinkedAllocSize = PrelinkedAllocSize;
|
|
Context->Is32Bit = Is32Bit;
|
|
|
|
//
|
|
// Ensure that PrelinkedSize is always aligned.
|
|
//
|
|
if (Context->PrelinkedSize != PrelinkedSize) {
|
|
if (Context->PrelinkedSize > PrelinkedAllocSize) {
|
|
return EFI_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
ZeroMem (&Prelinked[PrelinkedSize], Context->PrelinkedSize - PrelinkedSize);
|
|
}
|
|
|
|
//
|
|
// Initialise primary context.
|
|
//
|
|
if (!MachoInitializeContext (&Context->PrelinkedMachContext, Prelinked, PrelinkedSize, 0, PrelinkedSize, Context->Is32Bit)) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
Status = InternalConnectExternalSymtab (
|
|
&Context->PrelinkedMachContext,
|
|
&Context->InnerMachContext,
|
|
Context->Prelinked,
|
|
Context->PrelinkedSize,
|
|
&Context->IsKernelCollection
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Initialize kext list with kernel pseudo kext.
|
|
//
|
|
InitializeListHead (&Context->PrelinkedKexts);
|
|
InitializeListHead (&Context->InjectedKexts);
|
|
PrelinkedKext = InternalCachedPrelinkedKernel (Context);
|
|
if (PrelinkedKext == NULL) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
Context->VirtualBase = PrelinkedKext->Context.VirtualBase;
|
|
|
|
Context->PrelinkedLastAddress = MACHO_ALIGN (MachoGetLastAddress (&Context->PrelinkedMachContext));
|
|
if (Context->PrelinkedLastAddress == 0) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Register normal segment entries.
|
|
//
|
|
Status = PrelinkedGetSegmentsFromMacho (
|
|
&Context->PrelinkedMachContext,
|
|
&Context->PrelinkedInfoSegment,
|
|
&Context->PrelinkedInfoSection
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
if (Context->IsKernelCollection) {
|
|
//
|
|
// Additionally process special entries for KC.
|
|
//
|
|
Status = PrelinkedGetSegmentsFromMacho (
|
|
&Context->InnerMachContext,
|
|
(MACH_SEGMENT_COMMAND_ANY **)&Context->InnerInfoSegment,
|
|
(MACH_SECTION_ANY **)&Context->InnerInfoSection
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
Context->RegionSegment = MachoGetSegmentByName64 (
|
|
&Context->PrelinkedMachContext,
|
|
KC_REGION0_SEGMENT
|
|
);
|
|
if (Context->RegionSegment == NULL) {
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
Context->LinkEditSegment = MachoGetSegmentByName (
|
|
&Context->PrelinkedMachContext,
|
|
KC_LINKEDIT_SEGMENT
|
|
);
|
|
if (Context->LinkEditSegment == NULL) {
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
} else {
|
|
Context->PrelinkedTextSegment = MachoGetSegmentByName (
|
|
&Context->PrelinkedMachContext,
|
|
PRELINK_TEXT_SEGMENT
|
|
);
|
|
if (Context->PrelinkedTextSegment == NULL) {
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
Context->PrelinkedTextSection = MachoGetSectionByName (
|
|
&Context->PrelinkedMachContext,
|
|
Context->PrelinkedTextSegment,
|
|
PRELINK_TEXT_SECTION
|
|
);
|
|
if (Context->PrelinkedTextSection == NULL) {
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
Context->PrelinkedInfo = Context->Is32Bit ?
|
|
AllocateCopyPool (
|
|
Context->PrelinkedInfoSection->Section32.Size,
|
|
&Context->Prelinked[Context->PrelinkedInfoSection->Section32.Offset]
|
|
) :
|
|
AllocateCopyPool (
|
|
(UINTN)Context->PrelinkedInfoSection->Section64.Size,
|
|
&Context->Prelinked[Context->PrelinkedInfoSection->Section64.Offset]
|
|
);
|
|
if (Context->PrelinkedInfo == NULL) {
|
|
PrelinkedContextFree (Context);
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
Context->PrelinkedInfoDocument = XmlDocumentParse (
|
|
Context->PrelinkedInfo,
|
|
(UINT32)(Context->Is32Bit ?
|
|
Context->PrelinkedInfoSection->Section32.Size : Context->PrelinkedInfoSection->Section64.Size),
|
|
TRUE
|
|
);
|
|
if (Context->PrelinkedInfoDocument == NULL) {
|
|
PrelinkedContextFree (Context);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// For a kernel collection the this is a full plist, while for legacy prelinked format
|
|
// it starts with a <dict> node.
|
|
//
|
|
if (Context->IsKernelCollection) {
|
|
DocumentRoot = PlistDocumentRoot (Context->PrelinkedInfoDocument);
|
|
} else {
|
|
DocumentRoot = XmlDocumentRoot (Context->PrelinkedInfoDocument);
|
|
}
|
|
|
|
PrelinkedInfoRoot = PlistNodeCast (DocumentRoot, PLIST_NODE_TYPE_DICT);
|
|
if (PrelinkedInfoRoot == NULL) {
|
|
PrelinkedContextFree (Context);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (Context->IsKernelCollection) {
|
|
//
|
|
// In KC mode last load address is the __LINKEDIT address.
|
|
//
|
|
SegmentEndOffset = Context->LinkEditSegment->Segment64.FileOffset + Context->LinkEditSegment->Segment64.FileSize;
|
|
|
|
if (MACHO_ALIGN (SegmentEndOffset) != Context->PrelinkedSize) {
|
|
PrelinkedContextFree (Context);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
Context->PrelinkedLastLoadAddress = Context->LinkEditSegment->Segment64.VirtualAddress + Context->LinkEditSegment->Segment64.Size;
|
|
}
|
|
|
|
//
|
|
// In legacy mode last load address is last kext address.
|
|
//
|
|
PrelinkedInfoRootCount = PlistDictChildren (PrelinkedInfoRoot);
|
|
for (PrelinkedInfoRootIndex = 0; PrelinkedInfoRootIndex < PrelinkedInfoRootCount; ++PrelinkedInfoRootIndex) {
|
|
PrelinkedInfoRootKey = PlistKeyValue (PlistDictChild (PrelinkedInfoRoot, PrelinkedInfoRootIndex, &Context->KextList));
|
|
if (PrelinkedInfoRootKey == NULL) {
|
|
continue;
|
|
}
|
|
|
|
if (AsciiStrCmp (PrelinkedInfoRootKey, PRELINK_INFO_DICTIONARY_KEY) == 0) {
|
|
if (PlistNodeCast (Context->KextList, PLIST_NODE_TYPE_ARRAY) != NULL) {
|
|
if (Context->PrelinkedLastLoadAddress == 0) {
|
|
Context->PrelinkedLastLoadAddress = PrelinkedFindLastLoadAddress (Context->KextList);
|
|
}
|
|
|
|
if (Context->PrelinkedLastLoadAddress != 0) {
|
|
return EFI_SUCCESS;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
PrelinkedContextFree (Context);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
VOID
|
|
PrelinkedContextFree (
|
|
IN OUT PRELINKED_CONTEXT *Context
|
|
)
|
|
{
|
|
UINT32 Index;
|
|
LIST_ENTRY *Link;
|
|
PRELINKED_KEXT *Kext;
|
|
|
|
if (Context->PrelinkedInfoDocument != NULL) {
|
|
XmlDocumentFree (Context->PrelinkedInfoDocument);
|
|
Context->PrelinkedInfoDocument = NULL;
|
|
}
|
|
|
|
if (Context->KextScratchBuffer != NULL) {
|
|
FreePool (Context->KextScratchBuffer);
|
|
Context->KextScratchBuffer = NULL;
|
|
}
|
|
|
|
if (Context->PrelinkedInfo != NULL) {
|
|
FreePool (Context->PrelinkedInfo);
|
|
Context->PrelinkedInfo = NULL;
|
|
}
|
|
|
|
if (Context->PooledBuffers != NULL) {
|
|
for (Index = 0; Index < Context->PooledBuffersCount; ++Index) {
|
|
FreePool (Context->PooledBuffers[Index]);
|
|
}
|
|
|
|
FreePool (Context->PooledBuffers);
|
|
Context->PooledBuffers = NULL;
|
|
}
|
|
|
|
if (Context->LinkBuffer != NULL) {
|
|
ZeroMem (Context->LinkBuffer, Context->LinkBufferSize);
|
|
FreePool (Context->LinkBuffer);
|
|
Context->LinkBuffer = NULL;
|
|
}
|
|
|
|
if (Context->PrelinkedStateKernel != NULL) {
|
|
FreePool (Context->PrelinkedStateKernel);
|
|
Context->PrelinkedStateKernel = NULL;
|
|
}
|
|
|
|
if (Context->PrelinkedStateKexts != NULL) {
|
|
FreePool (Context->PrelinkedStateKexts);
|
|
Context->PrelinkedStateKexts = NULL;
|
|
}
|
|
|
|
while (!IsListEmpty (&Context->PrelinkedKexts)) {
|
|
Link = GetFirstNode (&Context->PrelinkedKexts);
|
|
Kext = GET_PRELINKED_KEXT_FROM_LINK (Link);
|
|
RemoveEntryList (Link);
|
|
InternalFreePrelinkedKext (Kext);
|
|
}
|
|
|
|
ZeroMem (&Context->PrelinkedKexts, sizeof (Context->PrelinkedKexts));
|
|
|
|
//
|
|
// We do not need to iterate InjectedKexts here, as its memory was freed above.
|
|
//
|
|
ZeroMem (&Context->InjectedKexts, sizeof (Context->InjectedKexts));
|
|
}
|
|
|
|
EFI_STATUS
|
|
PrelinkedDependencyInsert (
|
|
IN OUT PRELINKED_CONTEXT *Context,
|
|
IN VOID *Buffer
|
|
)
|
|
{
|
|
VOID **NewPooledBuffers;
|
|
|
|
if (Context->PooledBuffersCount == Context->PooledBuffersAllocCount) {
|
|
NewPooledBuffers = AllocatePool (
|
|
2 * (Context->PooledBuffersAllocCount + 1) * sizeof (NewPooledBuffers[0])
|
|
);
|
|
if (NewPooledBuffers == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
if (Context->PooledBuffers != NULL) {
|
|
CopyMem (
|
|
&NewPooledBuffers[0],
|
|
&Context->PooledBuffers[0],
|
|
Context->PooledBuffersCount * sizeof (NewPooledBuffers[0])
|
|
);
|
|
FreePool (Context->PooledBuffers);
|
|
}
|
|
|
|
Context->PooledBuffers = NewPooledBuffers;
|
|
Context->PooledBuffersAllocCount = 2 * (Context->PooledBuffersAllocCount + 1);
|
|
}
|
|
|
|
Context->PooledBuffers[Context->PooledBuffersCount] = Buffer;
|
|
Context->PooledBuffersCount++;
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
PrelinkedInjectPrepare (
|
|
IN OUT PRELINKED_CONTEXT *Context,
|
|
IN UINT32 LinkedExpansion,
|
|
IN UINT32 ReservedExeSize
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINT64 SegmentEndOffset;
|
|
UINT32 AlignedExpansion;
|
|
|
|
if (Context->IsKernelCollection) {
|
|
//
|
|
// For newer variant (KC mode) __LINKEDIT is last, and we need to expand it to enable
|
|
// dyld fixup generation.
|
|
//
|
|
if ( (Context->PrelinkedAllocSize < LinkedExpansion)
|
|
|| (Context->PrelinkedAllocSize - LinkedExpansion < Context->PrelinkedSize))
|
|
{
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
ASSERT (Context->PrelinkedLastAddress == Context->PrelinkedLastLoadAddress);
|
|
//
|
|
// Ensured by PrelinkedContextInit().
|
|
//
|
|
ASSERT (Context->PrelinkedSize % MACHO_PAGE_SIZE == 0);
|
|
STATIC_ASSERT (
|
|
MACHO_PAGE_SIZE % OC_ALIGNOF (MACH_DYLD_CHAINED_STARTS_IN_SEGMENT) == 0,
|
|
"KextsFixupChains may be unaligned"
|
|
);
|
|
|
|
Context->KextsFixupChains = (VOID *)(Context->Prelinked + Context->PrelinkedSize);
|
|
|
|
AlignedExpansion = MACHO_ALIGN (LinkedExpansion);
|
|
//
|
|
// Zero the expansion to account for padding.
|
|
//
|
|
ZeroMem (
|
|
Context->Prelinked + Context->PrelinkedSize,
|
|
AlignedExpansion
|
|
);
|
|
|
|
Context->PrelinkedSize += AlignedExpansion;
|
|
Context->PrelinkedLastAddress += AlignedExpansion;
|
|
Context->PrelinkedLastLoadAddress += AlignedExpansion;
|
|
Context->LinkEditSegment->Segment64.Size += AlignedExpansion;
|
|
Context->LinkEditSegment->Segment64.FileSize += AlignedExpansion;
|
|
} else {
|
|
//
|
|
// For older variant of the prelinkedkernel plist info is normally
|
|
// the last segment, so we may potentially save some data by removing
|
|
// it and then appending new kexts over. This is different for KC,
|
|
// where plist info is in the middle of the file. For 10.6.8 we will
|
|
// also need to move __PRELINK_STATE, which is just some blob for kextd.
|
|
//
|
|
// 10.6.8
|
|
//
|
|
// ffffff8000200000 - ffffff8000600000 (at 0000000000000000 - 0000000000400000) - __TEXT
|
|
// ffffff8000600000 - ffffff80006da000 (at 0000000000400000 - 000000000046f000) - __DATA
|
|
// ffffff8000106000 - ffffff8000107000 (at 000000000046f000 - 0000000000470000) - __INITGDT
|
|
// ffffff8000100000 - ffffff8000106000 (at 0000000000470000 - 0000000000476000) - __INITPT
|
|
// ffffff80006da000 - ffffff80006db000 (at 0000000000476000 - 0000000000477000) - __DESC
|
|
// ffffff80006db000 - ffffff80006dc000 (at 0000000000477000 - 0000000000478000) - __VECTORS
|
|
// ffffff8000108000 - ffffff800010e000 (at 0000000000478000 - 000000000047d000) - __HIB
|
|
// ffffff8000107000 - ffffff8000108000 (at 000000000047d000 - 000000000047e000) - __SLEEP
|
|
// ffffff80006dc000 - ffffff80006de000 (at 000000000047e000 - 0000000000480000) - __KLD
|
|
// ffffff80006de000 - ffffff80006de000 (at 0000000000480000 - 0000000000480000) - __LAST
|
|
// ffffff8000772000 - ffffff800100a000 (at 0000000000514000 - 0000000000dac000) - __PRELINK_TEXT
|
|
// ffffff800100a000 - ffffff800155c000 (at 0000000000dac000 - 00000000012fe000) - __PRELINK_STATE
|
|
// ffffff800155c000 - ffffff8001612000 (at 00000000012fe000 - 00000000013b4000) - __PRELINK_INFO
|
|
// 0000000000000000 - 0000000000000000 (at 0000000000513018 - 0000000000551269) - __CTF
|
|
// ffffff80006de000 - ffffff8000771018 (at 0000000000480000 - 0000000000513018) - __LINKEDIT
|
|
//
|
|
// 10.15.6
|
|
//
|
|
// ffffff8000200000 - ffffff8000c00000 (at 0000000000000000 - 0000000000a00000) - __TEXT
|
|
// ffffff8000c00000 - ffffff8000e70000 (at 0000000000a00000 - 0000000000c70000) - __DATA
|
|
// ffffff8000e70000 - ffffff8000ea9000 (at 0000000000c70000 - 0000000000ca9000) - __DATA_CONST
|
|
// ffffff8000100000 - ffffff800019e000 (at 0000000000ca9000 - 0000000000d47000) - __HIB
|
|
// ffffff8000ea9000 - ffffff8000eaa000 (at 0000000000d47000 - 0000000000d48000) - __VECTORS
|
|
// ffffff8000eaa000 - ffffff8000ec4000 (at 0000000000d48000 - 0000000000d62000) - __KLD
|
|
// ffffff8000ec4000 - ffffff8000ec5000 (at 0000000000d62000 - 0000000000d63000) - __LAST
|
|
// ffffff8001036000 - ffffff8002e24000 (at 0000000000f48000 - 0000000002d36000) - __PRELINK_TEXT
|
|
// ffffff8002e24000 - ffffff80030d2000 (at 0000000002d36000 - 0000000002fe3389) - __PRELINK_INFO
|
|
// ffffff8000ec5000 - ffffff8000ec5000 (at 0000000000d63000 - 0000000000dd7000) - __CTF
|
|
// ffffff8000ec5000 - ffffff80010358a8 (at 0000000000dd7000 - 0000000000f478a8) - __LINKEDIT
|
|
//
|
|
SegmentEndOffset = Context->Is32Bit ?
|
|
Context->PrelinkedInfoSegment->Segment32.FileOffset + Context->PrelinkedInfoSegment->Segment32.FileSize :
|
|
Context->PrelinkedInfoSegment->Segment64.FileOffset + Context->PrelinkedInfoSegment->Segment64.FileSize;
|
|
|
|
if (MACHO_ALIGN (SegmentEndOffset) == Context->PrelinkedSize) {
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCAK: Reducing %a-bit prelink size from %X to %X via plist\n",
|
|
Context->Is32Bit ? "32" : "64",
|
|
Context->PrelinkedSize,
|
|
(UINT32)MACHO_ALIGN (
|
|
Context->Is32Bit ?
|
|
Context->PrelinkedInfoSegment->Segment32.FileOffset : Context->PrelinkedInfoSegment->Segment64.FileOffset
|
|
)
|
|
));
|
|
Context->PrelinkedSize = (UINT32)MACHO_ALIGN (
|
|
Context->Is32Bit ?
|
|
Context->PrelinkedInfoSegment->Segment32.FileOffset : Context->PrelinkedInfoSegment->Segment64.FileOffset
|
|
);
|
|
} else {
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCAK: Leaving unchanged %a-bit prelink size %X due to %LX plist\n",
|
|
Context->Is32Bit ? "32" : "64",
|
|
Context->PrelinkedSize,
|
|
SegmentEndOffset
|
|
));
|
|
}
|
|
|
|
if (Context->PrelinkedStateSegment != NULL) {
|
|
SegmentEndOffset = Context->Is32Bit ?
|
|
Context->PrelinkedStateSegment->Segment32.FileOffset + Context->PrelinkedStateSegment->Segment32.FileSize :
|
|
Context->PrelinkedStateSegment->Segment64.FileOffset + Context->PrelinkedStateSegment->Segment64.FileSize;
|
|
|
|
if (MACHO_ALIGN (SegmentEndOffset) == Context->PrelinkedSize) {
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCAK: Reducing %a-bit prelink size from %X to %X via state\n",
|
|
Context->Is32Bit ? "32" : "64",
|
|
Context->PrelinkedSize,
|
|
(UINT32)MACHO_ALIGN (
|
|
Context->Is32Bit ?
|
|
Context->PrelinkedStateSegment->Segment32.FileOffset : Context->PrelinkedStateSegment->Segment64.FileOffset
|
|
)
|
|
));
|
|
Context->PrelinkedSize = (UINT32)MACHO_ALIGN (
|
|
Context->Is32Bit ?
|
|
Context->PrelinkedStateSegment->Segment32.FileOffset : Context->PrelinkedStateSegment->Segment64.FileOffset
|
|
);
|
|
|
|
//
|
|
// Need to NULL this, as they are used in address calculations
|
|
// in e.g. MachoGetLastAddress.
|
|
//
|
|
if (Context->Is32Bit) {
|
|
Context->PrelinkedStateSegment->Segment32.VirtualAddress = 0;
|
|
Context->PrelinkedStateSegment->Segment32.Size = 0;
|
|
Context->PrelinkedStateSegment->Segment32.FileOffset = 0;
|
|
Context->PrelinkedStateSegment->Segment32.FileSize = 0;
|
|
Context->PrelinkedStateSectionKernel->Section32.Address = 0;
|
|
Context->PrelinkedStateSectionKernel->Section32.Size = 0;
|
|
Context->PrelinkedStateSectionKernel->Section32.Offset = 0;
|
|
Context->PrelinkedStateSectionKexts->Section32.Address = 0;
|
|
Context->PrelinkedStateSectionKexts->Section32.Size = 0;
|
|
Context->PrelinkedStateSectionKexts->Section32.Offset = 0;
|
|
} else {
|
|
Context->PrelinkedStateSegment->Segment64.VirtualAddress = 0;
|
|
Context->PrelinkedStateSegment->Segment64.Size = 0;
|
|
Context->PrelinkedStateSegment->Segment64.FileOffset = 0;
|
|
Context->PrelinkedStateSegment->Segment64.FileSize = 0;
|
|
Context->PrelinkedStateSectionKernel->Section64.Address = 0;
|
|
Context->PrelinkedStateSectionKernel->Section64.Size = 0;
|
|
Context->PrelinkedStateSectionKernel->Section64.Offset = 0;
|
|
Context->PrelinkedStateSectionKexts->Section64.Address = 0;
|
|
Context->PrelinkedStateSectionKexts->Section64.Size = 0;
|
|
Context->PrelinkedStateSectionKexts->Section64.Offset = 0;
|
|
}
|
|
} else {
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCAK: Leaving unchanged %a-bit prelink size %X due to %LX state\n",
|
|
Context->Is32Bit ? "32" : "64",
|
|
Context->PrelinkedSize,
|
|
SegmentEndOffset
|
|
));
|
|
}
|
|
}
|
|
|
|
if (Context->Is32Bit) {
|
|
Context->PrelinkedInfoSegment->Segment32.VirtualAddress = 0;
|
|
Context->PrelinkedInfoSegment->Segment32.Size = 0;
|
|
Context->PrelinkedInfoSegment->Segment32.FileOffset = 0;
|
|
Context->PrelinkedInfoSegment->Segment32.FileSize = 0;
|
|
Context->PrelinkedInfoSection->Section32.Address = 0;
|
|
Context->PrelinkedInfoSection->Section32.Size = 0;
|
|
Context->PrelinkedInfoSection->Section32.Offset = 0;
|
|
} else {
|
|
Context->PrelinkedInfoSegment->Segment64.VirtualAddress = 0;
|
|
Context->PrelinkedInfoSegment->Segment64.Size = 0;
|
|
Context->PrelinkedInfoSegment->Segment64.FileOffset = 0;
|
|
Context->PrelinkedInfoSegment->Segment64.FileSize = 0;
|
|
Context->PrelinkedInfoSection->Section64.Address = 0;
|
|
Context->PrelinkedInfoSection->Section64.Size = 0;
|
|
Context->PrelinkedInfoSection->Section64.Offset = 0;
|
|
}
|
|
|
|
Context->PrelinkedLastAddress = MACHO_ALIGN (MachoGetLastAddress (&Context->PrelinkedMachContext));
|
|
if (Context->PrelinkedLastAddress == 0) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Prior to plist there usually is prelinked text.
|
|
//
|
|
SegmentEndOffset = Context->Is32Bit ?
|
|
Context->PrelinkedTextSegment->Segment32.FileOffset + Context->PrelinkedTextSegment->Segment32.FileSize :
|
|
Context->PrelinkedTextSegment->Segment64.FileOffset + Context->PrelinkedTextSegment->Segment64.FileSize;
|
|
|
|
if (MACHO_ALIGN (SegmentEndOffset) != Context->PrelinkedSize) {
|
|
//
|
|
// TODO: Implement prelinked text relocation when it is not preceding prelinked info
|
|
// and is not in the end of prelinked info.
|
|
//
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Append the injected KEXTs to the current kernel end.
|
|
//
|
|
Context->KextsFileOffset = Context->PrelinkedSize;
|
|
Context->KextsVmAddress = Context->PrelinkedLastAddress;
|
|
|
|
if (Context->IsKernelCollection) {
|
|
Status = KcInitKextFixupChains (Context, LinkedExpansion, ReservedExeSize);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
PrelinkedInjectComplete (
|
|
IN OUT PRELINKED_CONTEXT *Context
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
CHAR8 *ExportedInfo;
|
|
UINT32 ExportedInfoSize;
|
|
UINT32 NewSize;
|
|
UINT32 KextsSize;
|
|
UINT32 ChainSize;
|
|
|
|
if (Context->IsKernelCollection) {
|
|
//
|
|
// Fix up the segment fixup chains structure to reflect the actually
|
|
// injected segment size.
|
|
//
|
|
KextsSize = Context->PrelinkedSize - Context->KextsFileOffset;
|
|
|
|
ChainSize = KcGetSegmentFixupChainsSize (KextsSize);
|
|
ASSERT (ChainSize != 0);
|
|
ASSERT (ChainSize <= Context->KextsFixupChains->Size);
|
|
|
|
Context->KextsFixupChains->Size = ChainSize;
|
|
Context->KextsFixupChains->PageCount = (UINT16)(KextsSize / MACHO_PAGE_SIZE);
|
|
|
|
Status = KcRebuildMachHeader (Context);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
} else if ((Context->PrelinkedStateSegment != NULL) && ((Context->Is32Bit ?
|
|
Context->PrelinkedStateSegment->Segment32.VirtualAddress : Context->PrelinkedStateSegment->Segment64.VirtualAddress)
|
|
== 0))
|
|
{
|
|
Status = InternalKxldStateRebuild (Context);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
ExportedInfo = XmlDocumentExport (Context->PrelinkedInfoDocument, &ExportedInfoSize, 0, FALSE);
|
|
if (ExportedInfo == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
//
|
|
// Include \0 terminator.
|
|
//
|
|
ExportedInfoSize++;
|
|
|
|
if ( OcOverflowAddU32 (Context->PrelinkedSize, MACHO_ALIGN (ExportedInfoSize), &NewSize)
|
|
|| (NewSize > Context->PrelinkedAllocSize))
|
|
{
|
|
FreePool (ExportedInfo);
|
|
return EFI_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
#if 0
|
|
//
|
|
// This is a potential optimisation for smaller kexts allowing us to use less space.
|
|
// This requires disable __KREMLIN relocation segment addition.
|
|
//
|
|
if (Context->IsKernelCollection && (MACHO_ALIGN (ExportedInfoSize) <= Context->PrelinkedInfoSegment->Size)) {
|
|
CopyMem (
|
|
&Context->Prelinked[Context->PrelinkedInfoSegment->FileOffset],
|
|
ExportedInfo,
|
|
ExportedInfoSize
|
|
);
|
|
|
|
ZeroMem (
|
|
&Context->Prelinked[Context->PrelinkedInfoSegment->FileOffset + ExportedInfoSize],
|
|
Context->PrelinkedInfoSegment->FileSize - ExportedInfoSize
|
|
);
|
|
|
|
FreePool (ExportedInfo);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
#endif
|
|
|
|
if (Context->Is32Bit) {
|
|
Context->PrelinkedInfoSegment->Segment32.VirtualAddress = (UINT32)Context->PrelinkedLastAddress;
|
|
Context->PrelinkedInfoSegment->Segment32.Size = MACHO_ALIGN (ExportedInfoSize);
|
|
Context->PrelinkedInfoSegment->Segment32.FileOffset = Context->PrelinkedSize;
|
|
Context->PrelinkedInfoSegment->Segment32.FileSize = MACHO_ALIGN (ExportedInfoSize);
|
|
Context->PrelinkedInfoSection->Section32.Address = (UINT32)Context->PrelinkedLastAddress;
|
|
Context->PrelinkedInfoSection->Section32.Size = MACHO_ALIGN (ExportedInfoSize);
|
|
Context->PrelinkedInfoSection->Section32.Offset = Context->PrelinkedSize;
|
|
} else {
|
|
Context->PrelinkedInfoSegment->Segment64.VirtualAddress = Context->PrelinkedLastAddress;
|
|
Context->PrelinkedInfoSegment->Segment64.Size = MACHO_ALIGN (ExportedInfoSize);
|
|
Context->PrelinkedInfoSegment->Segment64.FileOffset = Context->PrelinkedSize;
|
|
Context->PrelinkedInfoSegment->Segment64.FileSize = MACHO_ALIGN (ExportedInfoSize);
|
|
Context->PrelinkedInfoSection->Section64.Address = Context->PrelinkedLastAddress;
|
|
Context->PrelinkedInfoSection->Section64.Size = MACHO_ALIGN (ExportedInfoSize);
|
|
Context->PrelinkedInfoSection->Section64.Offset = Context->PrelinkedSize;
|
|
}
|
|
|
|
if (Context->IsKernelCollection) {
|
|
//
|
|
// For newer variant of the prelinkedkernel plist we need to adapt it
|
|
// in both inner and outer images.
|
|
//
|
|
Context->InnerInfoSegment->VirtualAddress = Context->PrelinkedLastAddress;
|
|
Context->InnerInfoSegment->Size = MACHO_ALIGN (ExportedInfoSize);
|
|
Context->InnerInfoSegment->FileOffset = Context->PrelinkedSize;
|
|
Context->InnerInfoSegment->FileSize = MACHO_ALIGN (ExportedInfoSize);
|
|
Context->InnerInfoSection->Address = Context->PrelinkedLastAddress;
|
|
Context->InnerInfoSection->Size = MACHO_ALIGN (ExportedInfoSize);
|
|
Context->InnerInfoSection->Offset = Context->PrelinkedSize;
|
|
}
|
|
|
|
CopyMem (
|
|
&Context->Prelinked[Context->PrelinkedSize],
|
|
ExportedInfo,
|
|
ExportedInfoSize
|
|
);
|
|
|
|
ZeroMem (
|
|
&Context->Prelinked[Context->PrelinkedSize + ExportedInfoSize],
|
|
MACHO_ALIGN (ExportedInfoSize) - ExportedInfoSize
|
|
);
|
|
|
|
Context->PrelinkedLastAddress += MACHO_ALIGN (ExportedInfoSize);
|
|
Context->PrelinkedSize += MACHO_ALIGN (ExportedInfoSize);
|
|
|
|
Context->PrelinkedMachContext.FileSize = Context->PrelinkedSize;
|
|
|
|
if (Context->IsKernelCollection) {
|
|
//
|
|
// After rebuilding the KC we may have moved the __LINKEDIT command.
|
|
// This happens when __REGION kexts are squashed.
|
|
// Obtain its address again.
|
|
//
|
|
Context->LinkEditSegment = MachoGetSegmentByName (
|
|
&Context->PrelinkedMachContext,
|
|
KC_LINKEDIT_SEGMENT
|
|
);
|
|
}
|
|
|
|
FreePool (ExportedInfo);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
PrelinkedReserveKextSize (
|
|
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;
|
|
|
|
//
|
|
// For new fields.
|
|
//
|
|
if (OcOverflowAddU32 (InfoPlistSize, PLIST_EXPANSION_SIZE, &InfoPlistSize)) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
InfoPlistSize = MACHO_ALIGN (InfoPlistSize);
|
|
|
|
if (Executable != NULL) {
|
|
ASSERT (ExecutableSize > 0);
|
|
if (!MachoInitializeContext (&Context, Executable, ExecutableSize, 0, ExecutableSize, Is32Bit)) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
ExecutableSize = MachoGetExpandedImageSize (&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
|
|
PrelinkedInjectKext (
|
|
IN OUT PRELINKED_CONTEXT *Context,
|
|
IN CONST CHAR8 *Identifier OPTIONAL,
|
|
IN CONST CHAR8 *BundlePath,
|
|
IN CONST CHAR8 *InfoPlist,
|
|
IN UINT32 InfoPlistSize,
|
|
IN CONST CHAR8 *ExecutablePath OPTIONAL,
|
|
IN CONST UINT8 *Executable OPTIONAL,
|
|
IN UINT32 ExecutableSize OPTIONAL,
|
|
OUT CHAR8 BundleVersion[MAX_INFO_BUNDLE_VERSION_KEY_SIZE] OPTIONAL
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
BOOLEAN Result;
|
|
|
|
XML_DOCUMENT *InfoPlistDocument;
|
|
XML_NODE *InfoPlistRoot;
|
|
XML_NODE *KextPlistValue;
|
|
CHAR8 *TmpInfoPlist;
|
|
CHAR8 *NewInfoPlist;
|
|
OC_MACHO_CONTEXT ExecutableContext;
|
|
CONST CHAR8 *TmpKeyValue;
|
|
UINT32 FieldCount;
|
|
UINT32 FieldIndex;
|
|
UINT32 NewInfoPlistSize;
|
|
UINT32 NewPrelinkedSize;
|
|
UINT32 AlignedExecutableSize;
|
|
BOOLEAN Failed;
|
|
UINT64 KmodAddress;
|
|
PRELINKED_KEXT *PrelinkedKext;
|
|
CHAR8 ExecutableSourceAddrStr[24];
|
|
CHAR8 ExecutableSizeStr[24];
|
|
CHAR8 ExecutableLoadAddrStr[24];
|
|
CHAR8 KmodInfoStr[24];
|
|
UINT32 KextOffset;
|
|
UINT64 FileOffset;
|
|
UINT64 LoadAddressOffset;
|
|
CONST CHAR8 *BundleVerStr;
|
|
|
|
PrelinkedKext = NULL;
|
|
|
|
ASSERT (Context != NULL);
|
|
ASSERT (BundlePath != NULL);
|
|
ASSERT (InfoPlist != NULL);
|
|
ASSERT (InfoPlistSize > 0);
|
|
|
|
KmodAddress = 0;
|
|
AlignedExecutableSize = 0;
|
|
KextOffset = 0;
|
|
|
|
//
|
|
// If an identifier was passed, ensure it does not already exist.
|
|
//
|
|
if (Identifier != NULL) {
|
|
if (InternalCachedPrelinkedKext (Context, Identifier) != NULL) {
|
|
DEBUG ((DEBUG_INFO, "OCAK: Bundle %a is already present in prelinked\n", Identifier));
|
|
return EFI_ALREADY_STARTED;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy executable to prelinkedkernel.
|
|
//
|
|
if (Executable != NULL) {
|
|
ASSERT (ExecutableSize > 0);
|
|
if (!MachoInitializeContext (&ExecutableContext, (UINT8 *)Executable, ExecutableSize, 0, ExecutableSize, Context->Is32Bit)) {
|
|
DEBUG ((DEBUG_INFO, "OCAK: Injected kext %a/%a is not a supported executable\n", BundlePath, ExecutablePath));
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Append the KEXT to the current prelinked end.
|
|
//
|
|
KextOffset = Context->PrelinkedSize;
|
|
|
|
ExecutableSize = MachoExpandImage (
|
|
&ExecutableContext,
|
|
&Context->Prelinked[KextOffset],
|
|
Context->PrelinkedAllocSize - KextOffset,
|
|
TRUE,
|
|
&FileOffset
|
|
);
|
|
|
|
AlignedExecutableSize = MACHO_ALIGN (ExecutableSize);
|
|
|
|
if ( OcOverflowAddU32 (KextOffset, AlignedExecutableSize, &NewPrelinkedSize)
|
|
|| (NewPrelinkedSize > Context->PrelinkedAllocSize)
|
|
|| (ExecutableSize == 0))
|
|
{
|
|
return EFI_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
ZeroMem (
|
|
&Context->Prelinked[KextOffset + ExecutableSize],
|
|
AlignedExecutableSize - ExecutableSize
|
|
);
|
|
|
|
if ( !MachoInitializeContext (&ExecutableContext, &Context->Prelinked[KextOffset], ExecutableSize, 0, ExecutableSize, Context->Is32Bit)
|
|
|| OcOverflowAddU64 (Context->PrelinkedLastLoadAddress, FileOffset, &LoadAddressOffset))
|
|
{
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
Result = KextFindKmodAddress (&ExecutableContext, LoadAddressOffset, ExecutableSize, &KmodAddress);
|
|
if (!Result) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Allocate Info.plist copy for XML_DOCUMENT.
|
|
//
|
|
TmpInfoPlist = AllocateCopyPool (InfoPlistSize, InfoPlist);
|
|
if (TmpInfoPlist == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
InfoPlistDocument = XmlDocumentParse (TmpInfoPlist, InfoPlistSize, FALSE);
|
|
if (InfoPlistDocument == NULL) {
|
|
FreePool (TmpInfoPlist);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
InfoPlistRoot = PlistNodeCast (PlistDocumentRoot (InfoPlistDocument), PLIST_NODE_TYPE_DICT);
|
|
if (InfoPlistRoot == NULL) {
|
|
XmlDocumentFree (InfoPlistDocument);
|
|
FreePool (TmpInfoPlist);
|
|
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 (InfoPlistRoot);
|
|
|
|
if (BundleVersion != NULL) {
|
|
for (FieldIndex = 0; FieldIndex < FieldCount; ++FieldIndex) {
|
|
TmpKeyValue = PlistKeyValue (PlistDictChild (InfoPlistRoot, 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 (InfoPlistRoot, FieldIndex, NULL));
|
|
if (TmpKeyValue == NULL) {
|
|
continue;
|
|
}
|
|
|
|
if (AsciiStrCmp (TmpKeyValue, INFO_BUNDLE_EXECUTABLE_KEY) == 0) {
|
|
DEBUG ((DEBUG_ERROR, "OCAK: Plist-only kext has %a key\n", INFO_BUNDLE_EXECUTABLE_KEY));
|
|
ASSERT (FALSE);
|
|
CpuDeadLoop ();
|
|
}
|
|
}
|
|
}
|
|
|
|
DEBUG_CODE_END ();
|
|
|
|
Failed = FALSE;
|
|
Failed |= XmlNodeAppend (InfoPlistRoot, "key", NULL, PRELINK_INFO_BUNDLE_PATH_KEY) == NULL;
|
|
Failed |= XmlNodeAppend (InfoPlistRoot, "string", NULL, BundlePath) == NULL;
|
|
if (Executable != NULL) {
|
|
Failed |= XmlNodeAppend (InfoPlistRoot, "key", NULL, PRELINK_INFO_EXECUTABLE_RELATIVE_PATH_KEY) == NULL;
|
|
Failed |= XmlNodeAppend (InfoPlistRoot, "string", NULL, ExecutablePath) == NULL;
|
|
Failed |= !AsciiUint64ToLowerHex (ExecutableSourceAddrStr, sizeof (ExecutableSourceAddrStr), Context->PrelinkedLastAddress);
|
|
Failed |= XmlNodeAppend (InfoPlistRoot, "key", NULL, PRELINK_INFO_EXECUTABLE_SOURCE_ADDR_KEY) == NULL;
|
|
Failed |= XmlNodeAppend (InfoPlistRoot, "integer", PRELINK_INFO_INTEGER_ATTRIBUTES, ExecutableSourceAddrStr) == NULL;
|
|
Failed |= !AsciiUint64ToLowerHex (ExecutableLoadAddrStr, sizeof (ExecutableLoadAddrStr), Context->PrelinkedLastLoadAddress);
|
|
Failed |= XmlNodeAppend (InfoPlistRoot, "key", NULL, PRELINK_INFO_EXECUTABLE_LOAD_ADDR_KEY) == NULL;
|
|
Failed |= XmlNodeAppend (InfoPlistRoot, "integer", PRELINK_INFO_INTEGER_ATTRIBUTES, ExecutableLoadAddrStr) == NULL;
|
|
Failed |= !AsciiUint64ToLowerHex (ExecutableSizeStr, sizeof (ExecutableSizeStr), AlignedExecutableSize);
|
|
Failed |= XmlNodeAppend (InfoPlistRoot, "key", NULL, PRELINK_INFO_EXECUTABLE_SIZE_KEY) == NULL;
|
|
Failed |= XmlNodeAppend (InfoPlistRoot, "integer", PRELINK_INFO_INTEGER_ATTRIBUTES, ExecutableSizeStr) == NULL;
|
|
Failed |= !AsciiUint64ToLowerHex (KmodInfoStr, sizeof (KmodInfoStr), KmodAddress);
|
|
Failed |= XmlNodeAppend (InfoPlistRoot, "key", NULL, PRELINK_INFO_KMOD_INFO_KEY) == NULL;
|
|
Failed |= XmlNodeAppend (InfoPlistRoot, "integer", PRELINK_INFO_INTEGER_ATTRIBUTES, KmodInfoStr) == NULL;
|
|
}
|
|
|
|
if (Failed) {
|
|
XmlDocumentFree (InfoPlistDocument);
|
|
FreePool (TmpInfoPlist);
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
if (Executable != NULL) {
|
|
PrelinkedKext = InternalLinkPrelinkedKext (
|
|
Context,
|
|
&ExecutableContext,
|
|
InfoPlistRoot,
|
|
Context->PrelinkedLastLoadAddress,
|
|
KmodAddress,
|
|
FileOffset
|
|
);
|
|
|
|
if (PrelinkedKext == NULL) {
|
|
XmlDocumentFree (InfoPlistDocument);
|
|
FreePool (TmpInfoPlist);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// XNU assumes that load size and source size are same, so we should append
|
|
// whatever is bigger to all sizes.
|
|
//
|
|
Context->PrelinkedSize += AlignedExecutableSize;
|
|
Context->PrelinkedLastAddress += AlignedExecutableSize;
|
|
Context->PrelinkedLastLoadAddress += AlignedExecutableSize;
|
|
|
|
if (Context->IsKernelCollection) {
|
|
//
|
|
// For KC, our KEXTs have their own segment - do not mod __PRELINK_INFO.
|
|
// Integrate the KEXT into KC by indexing its fixups and rebasing.
|
|
// Note, we are no longer using ExecutableContext here, as the context
|
|
// ownership was transferred by InternalLinkPrelinkedKext.
|
|
//
|
|
KcKextIndexFixups (Context, &PrelinkedKext->Context.MachContext);
|
|
Status = KcKextApplyFileDelta (Context, &PrelinkedKext->Context.MachContext, KextOffset);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((
|
|
DEBUG_WARN,
|
|
"Failed to rebase injected kext %a/%a by %u\n",
|
|
BundlePath,
|
|
ExecutablePath,
|
|
KextOffset
|
|
));
|
|
return Status;
|
|
}
|
|
} else {
|
|
//
|
|
// For legacy prelinkedkernel we append to __PRELINK_TEXT.
|
|
//
|
|
if (Context->Is32Bit) {
|
|
Context->PrelinkedTextSegment->Segment32.Size += AlignedExecutableSize;
|
|
Context->PrelinkedTextSegment->Segment32.FileSize += AlignedExecutableSize;
|
|
Context->PrelinkedTextSection->Section32.Size += AlignedExecutableSize;
|
|
} else {
|
|
Context->PrelinkedTextSegment->Segment64.Size += AlignedExecutableSize;
|
|
Context->PrelinkedTextSegment->Segment64.FileSize += AlignedExecutableSize;
|
|
Context->PrelinkedTextSection->Section64.Size += AlignedExecutableSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Strip outer plist & dict.
|
|
//
|
|
NewInfoPlist = XmlDocumentExport (InfoPlistDocument, &NewInfoPlistSize, 2, FALSE);
|
|
|
|
XmlDocumentFree (InfoPlistDocument);
|
|
FreePool (TmpInfoPlist);
|
|
|
|
if (NewInfoPlist == NULL) {
|
|
if (PrelinkedKext != NULL) {
|
|
InternalFreePrelinkedKext (PrelinkedKext);
|
|
}
|
|
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
Status = PrelinkedDependencyInsert (Context, NewInfoPlist);
|
|
if (EFI_ERROR (Status)) {
|
|
FreePool (NewInfoPlist);
|
|
if (PrelinkedKext != NULL) {
|
|
InternalFreePrelinkedKext (PrelinkedKext);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
if (XmlNodeAppend (Context->KextList, "dict", NULL, NewInfoPlist) == NULL) {
|
|
if (PrelinkedKext != NULL) {
|
|
InternalFreePrelinkedKext (PrelinkedKext);
|
|
}
|
|
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
//
|
|
// Let other kexts depend on this one.
|
|
//
|
|
if (PrelinkedKext != NULL) {
|
|
InsertTailList (&Context->PrelinkedKexts, &PrelinkedKext->Link);
|
|
//
|
|
// Additionally register this kext in the injected list, as this is required
|
|
// for KernelCollection support.
|
|
//
|
|
InsertTailList (&Context->InjectedKexts, &PrelinkedKext->InjectedLink);
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
PrelinkedContextApplyPatch (
|
|
IN OUT PRELINKED_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 = PatcherInitContextFromPrelinked (&Patcher, Context, Identifier);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_INFO, "OCAK: Failed to pk find %a - %r\n", Identifier, Status));
|
|
return Status;
|
|
}
|
|
|
|
return PatcherApplyGenericPatch (&Patcher, Patch);
|
|
}
|
|
|
|
EFI_STATUS
|
|
PrelinkedContextApplyQuirk (
|
|
IN OUT PRELINKED_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 = PatcherInitContextFromPrelinked (&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 pk find %a - %r\n", KernelQuirk->Identifier, Status));
|
|
return KernelQuirk->PatchFunction (NULL, KernelVersion);
|
|
}
|
|
|
|
EFI_STATUS
|
|
PrelinkedContextBlock (
|
|
IN OUT PRELINKED_CONTEXT *Context,
|
|
IN CONST CHAR8 *Identifier,
|
|
IN BOOLEAN Exclude
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
PATCHER_CONTEXT Patcher;
|
|
|
|
ASSERT (Context != NULL);
|
|
ASSERT (Identifier != NULL);
|
|
|
|
Status = PatcherInitContextFromPrelinked (&Patcher, Context, Identifier);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_INFO, "OCAK: Failed to pk find %a - %r\n", Identifier, Status));
|
|
return Status;
|
|
}
|
|
|
|
return Exclude ? PatcherExcludePrelinkedKext (Identifier, &Patcher, Context) : PatcherBlockKext (&Patcher);
|
|
}
|