mirror of
https://github.com/acidanthera/OpenCorePkg.git
synced 2025-12-08 19:25:01 +00:00
1661 lines
48 KiB
C
1661 lines
48 KiB
C
/** @file
|
|
Library handling KEXT prelinking.
|
|
Currently limited to Intel 64 architectures.
|
|
|
|
Copyright (c) 2018, Download-Fritz. All rights reserved.<BR>
|
|
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 <Base.h>
|
|
|
|
#include <IndustryStandard/AppleKmodInfo.h>
|
|
#include <IndustryStandard/AppleMachoImage.h>
|
|
|
|
#include <Library/BaseLib.h>
|
|
#include <Library/BaseMemoryLib.h>
|
|
#include <Library/DebugLib.h>
|
|
#include <Library/OcAppleKernelLib.h>
|
|
#include <Library/OcGuardLib.h>
|
|
#include <Library/OcMachoLib.h>
|
|
|
|
#include "PrelinkedInternal.h"
|
|
|
|
#define TEXT_SEG_PROT (MACH_SEGMENT_VM_PROT_READ | MACH_SEGMENT_VM_PROT_EXECUTE)
|
|
#define DATA_SEG_PROT (MACH_SEGMENT_VM_PROT_READ | MACH_SEGMENT_VM_PROT_WRITE)
|
|
|
|
//
|
|
// Symbols
|
|
//
|
|
|
|
STATIC
|
|
CONST PRELINKED_KEXT_SYMBOL *
|
|
InternalOcGetSymbolWorkerName (
|
|
IN PRELINKED_KEXT *Kext,
|
|
IN CONST CHAR8 *LookupValue,
|
|
IN UINT32 LookupValueLength,
|
|
IN OC_GET_SYMBOL_LEVEL SymbolLevel
|
|
)
|
|
{
|
|
PRELINKED_KEXT *Dependency;
|
|
CONST PRELINKED_KEXT_SYMBOL *Symbols;
|
|
CONST PRELINKED_KEXT_SYMBOL *SymbolsEnd;
|
|
UINT32 Index;
|
|
UINT32 NumSymbols;
|
|
|
|
//
|
|
// Block any 1+ level dependencies.
|
|
//
|
|
Kext->Processed = TRUE;
|
|
|
|
if (Kext->LinkedSymbolTable != NULL) {
|
|
NumSymbols = Kext->NumberOfSymbols;
|
|
Symbols = Kext->LinkedSymbolTable;
|
|
|
|
if (SymbolLevel == OcGetSymbolOnlyCxx) {
|
|
NumSymbols = Kext->NumberOfCxxSymbols;
|
|
Symbols = &Kext->LinkedSymbolTable[Kext->NumberOfSymbols - Kext->NumberOfCxxSymbols];
|
|
}
|
|
|
|
SymbolsEnd = &Symbols[NumSymbols];
|
|
while (Symbols < SymbolsEnd) {
|
|
//
|
|
// Symbol names often start and end similarly due to C++ mangling (e.g. __ZN).
|
|
// To optimise the lookup we compare their length check in the middle.
|
|
// Please do not change this without careful profiling.
|
|
//
|
|
if (Symbols->Length == LookupValueLength) {
|
|
if (Symbols->Name[LookupValueLength / 2] == LookupValue[LookupValueLength / 2]
|
|
&& Symbols->Name[(LookupValueLength / 2) + 1] == LookupValue[(LookupValueLength / 2) + 1]) {
|
|
for (Index = 0; Index < LookupValueLength; ++Index) {
|
|
if (Symbols->Name[Index] != LookupValue[Index]) {
|
|
break;
|
|
}
|
|
}
|
|
if (Index == LookupValueLength) {
|
|
return Symbols;
|
|
}
|
|
}
|
|
}
|
|
Symbols++;
|
|
}
|
|
}
|
|
|
|
if (SymbolLevel != OcGetSymbolFirstLevel) {
|
|
for (Index = 0; Index < ARRAY_SIZE (Kext->Dependencies); ++Index) {
|
|
Dependency = Kext->Dependencies[Index];
|
|
if (Dependency == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (Dependency->Processed) {
|
|
continue;
|
|
}
|
|
|
|
Symbols = InternalOcGetSymbolWorkerName (
|
|
Dependency,
|
|
LookupValue,
|
|
LookupValueLength,
|
|
OcGetSymbolOnlyCxx
|
|
);
|
|
if (Symbols != NULL) {
|
|
return Symbols;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
STATIC
|
|
CONST PRELINKED_KEXT_SYMBOL *
|
|
InternalOcGetSymbolWorkerValue (
|
|
IN PRELINKED_KEXT *Kext,
|
|
IN UINT64 LookupValue,
|
|
IN OC_GET_SYMBOL_LEVEL SymbolLevel
|
|
)
|
|
{
|
|
PRELINKED_KEXT *Dependency;
|
|
CONST PRELINKED_KEXT_SYMBOL *Symbols;
|
|
CONST PRELINKED_KEXT_SYMBOL *SymbolsEnd;
|
|
UINT32 Index;
|
|
UINT32 NumSymbols;
|
|
|
|
//
|
|
// Block any 1+ level dependencies.
|
|
//
|
|
Kext->Processed = TRUE;
|
|
|
|
if (Kext->LinkedSymbolTable != NULL) {
|
|
NumSymbols = Kext->NumberOfSymbols;
|
|
Symbols = Kext->LinkedSymbolTable;
|
|
|
|
if (SymbolLevel == OcGetSymbolOnlyCxx) {
|
|
NumSymbols = Kext->NumberOfCxxSymbols;
|
|
Symbols = &Kext->LinkedSymbolTable[(Kext->NumberOfSymbols - Kext->NumberOfCxxSymbols) & ~15ULL];
|
|
}
|
|
//
|
|
// WARN! Hot path! Do not change this code unless you have decent profiling data.
|
|
// We are not allowed to use SIMD in UEFI, but we can still do better with larger iteration.
|
|
// Up to 15 C symbols extra may get parsed, but it is fine, as they will not match.
|
|
// Increasing the iteration block to more than 16 no longer pays off.
|
|
// Note, lower loop is not on hot path.
|
|
//
|
|
SymbolsEnd = &Symbols[NumSymbols & ~15ULL];
|
|
while (Symbols < SymbolsEnd) {
|
|
#define MATCH(X) if (Symbols[X].Value == LookupValue) { return &Symbols[X]; }
|
|
MATCH (0) MATCH (1) MATCH (2) MATCH (3) MATCH (4) MATCH (5) MATCH (6) MATCH (7)
|
|
MATCH (8) MATCH (9) MATCH (10) MATCH (11) MATCH (12) MATCH (13) MATCH (14) MATCH (15)
|
|
#undef MATCH
|
|
Symbols += 16;
|
|
}
|
|
}
|
|
|
|
if (SymbolLevel != OcGetSymbolFirstLevel) {
|
|
for (Index = 0; Index < ARRAY_SIZE (Kext->Dependencies); ++Index) {
|
|
Dependency = Kext->Dependencies[Index];
|
|
if (Dependency == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (Dependency->Processed) {
|
|
continue;
|
|
}
|
|
|
|
Symbols = InternalOcGetSymbolWorkerValue (
|
|
Dependency,
|
|
LookupValue,
|
|
OcGetSymbolOnlyCxx
|
|
);
|
|
if (Symbols != NULL) {
|
|
return Symbols;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
CONST PRELINKED_KEXT_SYMBOL *
|
|
InternalOcGetSymbolName (
|
|
IN PRELINKED_CONTEXT *Context,
|
|
IN PRELINKED_KEXT *Kext,
|
|
IN CONST CHAR8 *LookupValue,
|
|
IN OC_GET_SYMBOL_LEVEL SymbolLevel
|
|
)
|
|
{
|
|
CONST PRELINKED_KEXT_SYMBOL *Symbol;
|
|
|
|
PRELINKED_KEXT *Dependency;
|
|
UINT32 Index;
|
|
UINT32 LookupValueLength;
|
|
|
|
Symbol = NULL;
|
|
LookupValueLength = (UINT32)AsciiStrLen (LookupValue);
|
|
|
|
//
|
|
// Such symbols are illegit, but InternalOcGetSymbolWorkerName assumes Length > 0.
|
|
//
|
|
if (LookupValueLength == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
if ((SymbolLevel == OcGetSymbolOnlyCxx) && (Kext->LinkedSymbolTable != NULL)) {
|
|
Symbol = InternalOcGetSymbolWorkerName (
|
|
Kext,
|
|
LookupValue,
|
|
LookupValueLength,
|
|
SymbolLevel
|
|
);
|
|
} else {
|
|
for (Index = 0; Index < ARRAY_SIZE (Kext->Dependencies); ++Index) {
|
|
Dependency = Kext->Dependencies[Index];
|
|
if (Dependency == NULL) {
|
|
break;
|
|
}
|
|
|
|
Symbol = InternalOcGetSymbolWorkerName (
|
|
Dependency,
|
|
LookupValue,
|
|
LookupValueLength,
|
|
SymbolLevel
|
|
);
|
|
if (Symbol != NULL) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
InternalUnlockContextKexts (Context);
|
|
|
|
return Symbol;
|
|
}
|
|
|
|
CONST PRELINKED_KEXT_SYMBOL *
|
|
InternalOcGetSymbolValue (
|
|
IN PRELINKED_CONTEXT *Context,
|
|
IN PRELINKED_KEXT *Kext,
|
|
IN UINT64 LookupValue,
|
|
IN OC_GET_SYMBOL_LEVEL SymbolLevel
|
|
)
|
|
{
|
|
CONST PRELINKED_KEXT_SYMBOL *Symbol;
|
|
|
|
PRELINKED_KEXT *Dependency;
|
|
UINT32 Index;
|
|
|
|
Symbol = NULL;
|
|
|
|
if ((SymbolLevel == OcGetSymbolOnlyCxx) && (Kext->LinkedSymbolTable != NULL)) {
|
|
Symbol = InternalOcGetSymbolWorkerValue (Kext, LookupValue, SymbolLevel);
|
|
} else {
|
|
for (Index = 0; Index < ARRAY_SIZE (Kext->Dependencies); ++Index) {
|
|
Dependency = Kext->Dependencies[Index];
|
|
if (Dependency == NULL) {
|
|
break;
|
|
}
|
|
|
|
Symbol = InternalOcGetSymbolWorkerValue (
|
|
Dependency,
|
|
LookupValue,
|
|
SymbolLevel
|
|
);
|
|
if (Symbol != NULL) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
InternalUnlockContextKexts (Context);
|
|
|
|
return Symbol;
|
|
}
|
|
|
|
/**
|
|
Patches Symbol with Value and marks it as solved.
|
|
|
|
@param[in] Value The value to solve Symbol with.
|
|
@param[out] Symbol The symbol to solve.
|
|
|
|
**/
|
|
VOID
|
|
InternalSolveSymbolValue64 (
|
|
IN UINT64 Value,
|
|
OUT MACH_NLIST_64 *Symbol
|
|
)
|
|
{
|
|
Symbol->Value = Value;
|
|
Symbol->Type = (MACH_N_TYPE_ABS | MACH_N_TYPE_EXT);
|
|
Symbol->Section = NO_SECT;
|
|
}
|
|
|
|
/**
|
|
Worker function to solve Symbol against the specified DefinedSymbols.
|
|
It does not consider Symbol might be a weak reference.
|
|
|
|
@param[in,out] Context Prelinking context.
|
|
@param[in] Kext KEXT prelinking context.
|
|
@param[in] Name The name of the symbol to resolve against.
|
|
@param[in,out] Symbol The symbol to be resolved.
|
|
|
|
@retval Returned is whether the symbol was solved successfully.
|
|
|
|
**/
|
|
STATIC
|
|
BOOLEAN
|
|
InternalSolveSymbolNonWeak64 (
|
|
IN PRELINKED_CONTEXT *Context,
|
|
IN PRELINKED_KEXT *Kext,
|
|
IN CONST CHAR8 *Name,
|
|
IN OUT MACH_NLIST_64 *Symbol
|
|
)
|
|
{
|
|
INTN Result;
|
|
CONST PRELINKED_KEXT_SYMBOL *ResolveSymbol;
|
|
|
|
if ((Symbol->Type & MACH_N_TYPE_TYPE) != MACH_N_TYPE_UNDF) {
|
|
if ((Symbol->Type & MACH_N_TYPE_TYPE) != MACH_N_TYPE_INDR) {
|
|
//
|
|
// KXLD_WEAK_TEST_SYMBOL might have been resolved by the resolving code
|
|
// at the end of InternalSolveSymbol64.
|
|
//
|
|
Result = AsciiStrCmp (
|
|
MachoGetSymbolName64 (&Kext->Context.MachContext, Symbol),
|
|
KXLD_WEAK_TEST_SYMBOL
|
|
);
|
|
if (Result == 0) {
|
|
//
|
|
// KXLD_WEAK_TEST_SYMBOL has been solved successfully already.
|
|
//
|
|
return TRUE;
|
|
}
|
|
//
|
|
// Any other symbols must be undefined or indirect.
|
|
//
|
|
return FALSE;
|
|
}
|
|
} else if (Symbol->Value != 0) {
|
|
//
|
|
// Common symbols are not supported.
|
|
//
|
|
return FALSE;
|
|
}
|
|
//
|
|
// Do not error when the referenced symbol cannot be found as some will be
|
|
// patched by the VTable code. This matches the KXLD behaviour.
|
|
//
|
|
ResolveSymbol = InternalOcGetSymbolName (
|
|
Context,
|
|
Kext,
|
|
Name,
|
|
OcGetSymbolFirstLevel
|
|
);
|
|
if (ResolveSymbol != NULL) {
|
|
InternalSolveSymbolValue64 (ResolveSymbol->Value, Symbol);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
Solves Symbol against the specified DefinedSymbols.
|
|
|
|
@param[in,out] Context Prelinking context.
|
|
@param[in] Kext KEXT prelinking context.
|
|
@param[in] Name The name of the symbol to resolve against.
|
|
@param[in,out] Symbol The symbol to be resolved.
|
|
@param[in,out] WeakTestValue Points to the value of the Weak test symbol.
|
|
If it is 0 on input, it will get assigned
|
|
the value on the first resolution of a weak
|
|
symbol.
|
|
@param[in] UndefinedSymbols Array of the Mach-O'S undefined symbols.
|
|
@param[in] NumUndefinedSymbols Number of symbols in UndefinedSymbols.
|
|
|
|
@retval Returned is whether the symbol was solved successfully. For weak
|
|
symbols, this includes solving with _gOSKextUnresolved.
|
|
|
|
**/
|
|
STATIC
|
|
BOOLEAN
|
|
InternalSolveSymbol64 (
|
|
IN PRELINKED_CONTEXT *Context,
|
|
IN PRELINKED_KEXT *Kext,
|
|
IN CONST CHAR8 *Name,
|
|
IN OUT MACH_NLIST_64 *Symbol,
|
|
IN OUT UINT64 *WeakTestValue,
|
|
IN CONST MACH_NLIST_64 *UndefinedSymbols,
|
|
IN UINT32 NumUndefinedSymbols
|
|
)
|
|
{
|
|
BOOLEAN Success;
|
|
UINT32 Index;
|
|
INTN Result;
|
|
UINT64 Value;
|
|
CONST MACH_NLIST_64 *WeakTestSymbol;
|
|
|
|
ASSERT (Symbol != NULL);
|
|
ASSERT (UndefinedSymbols != NULL || NumUndefinedSymbols == 0);
|
|
//
|
|
// STAB symbols are not considered undefined.
|
|
//
|
|
if ((Symbol->Type & MACH_N_TYPE_STAB) != 0) {
|
|
return TRUE;
|
|
}
|
|
|
|
Success = InternalSolveSymbolNonWeak64 (
|
|
Context,
|
|
Kext,
|
|
Name,
|
|
Symbol
|
|
);
|
|
if (Success) {
|
|
return TRUE;
|
|
}
|
|
|
|
if ((Symbol->Descriptor & MACH_N_WEAK_DEF) != 0) {
|
|
//
|
|
// KXLD_WEAK_TEST_SYMBOL is not going to be defined or exposed by a KEXT
|
|
// prelinked by this library, hence only check the undefined symbols region
|
|
// for matches.
|
|
//
|
|
Value = *WeakTestValue;
|
|
|
|
if (Value == 0) {
|
|
for (Index = 0; Index < NumUndefinedSymbols; ++Index) {
|
|
WeakTestSymbol = &UndefinedSymbols[Index];
|
|
Result = AsciiStrCmp (
|
|
MachoGetSymbolName64 (
|
|
&Kext->Context.MachContext,
|
|
WeakTestSymbol
|
|
),
|
|
KXLD_WEAK_TEST_SYMBOL
|
|
);
|
|
if (Result == 0) {
|
|
if ((WeakTestSymbol->Type & MACH_N_TYPE_TYPE) == MACH_N_TYPE_UNDF) {
|
|
Success = InternalSolveSymbolNonWeak64 (
|
|
Context,
|
|
Kext,
|
|
Name,
|
|
Symbol
|
|
);
|
|
if (!Success) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
Value = WeakTestSymbol->Value;
|
|
*WeakTestValue = Value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Value != 0) {
|
|
InternalSolveSymbolValue64 (Value, Symbol);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Relocations
|
|
//
|
|
|
|
/**
|
|
Calculate the target address' displacement for the Intel 64 platform.
|
|
Instruction will be patched with the resulting address.
|
|
Logically matches XNU's calculate_displacement_x86_64.
|
|
|
|
@param[in] Target The target address.
|
|
@param[in] Adjustment Adjustment to be subtracted from the
|
|
displacement.
|
|
@param[in,out] Instruction Pointer to the instruction to be patched.
|
|
|
|
@retval Returned is whether the target instruction has been patched.
|
|
|
|
**/
|
|
STATIC
|
|
BOOLEAN
|
|
InternalCalculateDisplacementIntel64 (
|
|
IN UINT64 Target,
|
|
IN UINT64 Adjustment,
|
|
IN OUT INT32 *Instruction
|
|
)
|
|
{
|
|
INT64 Displacement;
|
|
UINT64 Difference;
|
|
|
|
ASSERT (Instruction != NULL);
|
|
|
|
Displacement = ((*Instruction + Target) - Adjustment);
|
|
Difference = ABS (Displacement);
|
|
|
|
if (Difference >= X86_64_RIP_RELATIVE_LIMIT) {
|
|
return FALSE;
|
|
}
|
|
|
|
*Instruction = (INT32)Displacement;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
Calculate the target addresses for Relocation and NextRelocation.
|
|
Logically matches XNU's calculate_targets.
|
|
|
|
@param[in,out] Context Prelinking context.
|
|
@param[in] Kext KEXT prelinking context.
|
|
@param[in] LoadAddress The address to be linked against.
|
|
@param[in] Relocation The Relocation to be resolved.
|
|
@param[in] NextRelocation The Relocation following Relocation.
|
|
@param[out] Target Relocation's target address.
|
|
@param[out] PairTarget NextRelocation's target address.
|
|
@param[out] Vtable The VTable described by the symbol referenced
|
|
by Relocation. NULL, if there is none.
|
|
|
|
@returns Whether the operation was completed successfully.
|
|
|
|
**/
|
|
STATIC
|
|
BOOLEAN
|
|
InternalCalculateTargetsIntel64 (
|
|
IN PRELINKED_CONTEXT *Context,
|
|
IN OUT PRELINKED_KEXT *Kext,
|
|
IN UINT64 LoadAddress,
|
|
IN CONST MACH_RELOCATION_INFO *Relocation,
|
|
IN CONST MACH_RELOCATION_INFO *NextRelocation OPTIONAL,
|
|
OUT UINT64 *Target,
|
|
OUT UINT64 *PairTarget,
|
|
OUT CONST PRELINKED_VTABLE **Vtable OPTIONAL
|
|
)
|
|
{
|
|
BOOLEAN Success;
|
|
|
|
OC_MACHO_CONTEXT *MachoContext;
|
|
|
|
UINT64 TargetAddress;
|
|
MACH_NLIST_64 *Symbol;
|
|
CONST CHAR8 *Name;
|
|
MACH_SECTION_64 *Section;
|
|
UINT64 PairAddress;
|
|
UINT64 PairDummy;
|
|
|
|
ASSERT (Target != NULL);
|
|
ASSERT (PairTarget != NULL);
|
|
|
|
MachoContext = &Kext->Context.MachContext;
|
|
|
|
PairAddress = 0;
|
|
//
|
|
// Pull out the data from the relocation entries. The target_type depends
|
|
// on the Extern bit:
|
|
// Scattered -> Section Lookup by Address.
|
|
// Local (not extern) -> Section by Index
|
|
// Extern -> Symbolnum by Index
|
|
//
|
|
// Relocations referencing a symbol result in a Target of its resolved value.
|
|
// Relocations referencing a section result in a Target of
|
|
// (link_addr - base_addr), which should be LoadAddress aligned on the
|
|
// section's boundary.
|
|
//
|
|
if (Relocation->Extern != 0) {
|
|
Symbol = MachoGetSymbolByIndex64 (
|
|
MachoContext,
|
|
Relocation->SymbolNumber
|
|
);
|
|
if (Symbol == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
Name = MachoGetSymbolName64 (MachoContext, Symbol);
|
|
//
|
|
// If this symbol is a padslot that has already been replaced, then the
|
|
// only way a relocation entry can still reference it is if there is a
|
|
// vtable that has not been patched. The vtable patcher uses the
|
|
// MetaClass structure to find classes for patching, so an unpatched
|
|
// vtable means that there is an OSObject-dervied class that is missing
|
|
// its OSDeclare/OSDefine macros.
|
|
// - FIXME: This cannot currently be checked with the means of this
|
|
// library. KXLD creates copies of patched VTable symbols, marks
|
|
// the originals patched and then updates the referencing reloc.
|
|
//
|
|
|
|
if ((Vtable != NULL) && MachoSymbolNameIsVtable64 (Name)) {
|
|
*Vtable = InternalGetOcVtableByName (Context, Kext, Name);
|
|
}
|
|
|
|
TargetAddress = Symbol->Value;
|
|
} else {
|
|
if ((Relocation->SymbolNumber == NO_SECT)
|
|
|| (Relocation->SymbolNumber > MAX_SECT)) {
|
|
return FALSE;
|
|
}
|
|
|
|
Section = MachoGetSectionByIndex64 (
|
|
MachoContext,
|
|
(Relocation->SymbolNumber - 1)
|
|
);
|
|
if (Section == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
TargetAddress = ALIGN_VALUE (
|
|
(Section->Address + LoadAddress),
|
|
LShiftU64 (1, Section->Alignment)
|
|
);
|
|
TargetAddress -= Section->Address;
|
|
}
|
|
|
|
if (MachoRelocationIsPairIntel64 ((UINT8)Relocation->Type)) {
|
|
if (NextRelocation == NULL) {
|
|
return FALSE;
|
|
}
|
|
//
|
|
// As this relocation is the second one in a pair, it cannot be the start
|
|
// of a pair itself. Pass dummy data for the related arguments. This call
|
|
// shall never reach this very branch.
|
|
//
|
|
Success = InternalCalculateTargetsIntel64 (
|
|
Context,
|
|
Kext,
|
|
LoadAddress,
|
|
NextRelocation,
|
|
NULL,
|
|
&PairAddress,
|
|
&PairDummy,
|
|
NULL
|
|
);
|
|
if (!Success) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
*Target = TargetAddress;
|
|
*PairTarget = PairAddress;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
Returns whether the VTable entry at Offset is a direct pure virtual call.
|
|
Logically macthes XNU's check_for_direct_pure_virtual_call.
|
|
|
|
@param[in] Vtable The current VTable to be inspected.
|
|
@param[in] Offset The VTable's entry offset.
|
|
|
|
**/
|
|
STATIC
|
|
BOOLEAN
|
|
InternalIsDirectPureVirtualCall64 (
|
|
IN CONST PRELINKED_VTABLE *Vtable,
|
|
IN UINT64 Offset
|
|
)
|
|
{
|
|
UINT64 Index;
|
|
CONST PRELINKED_VTABLE_ENTRY *Entry;
|
|
|
|
if ((Offset % sizeof (UINT64)) != 0 || (Offset < VTABLE_ENTRY_SIZE_64)) {
|
|
return FALSE;
|
|
}
|
|
|
|
Index = ((Offset - VTABLE_ENTRY_SIZE_64) / sizeof (UINT64));
|
|
if (Index >= Vtable->NumEntries) {
|
|
return FALSE;
|
|
}
|
|
|
|
Entry = &Vtable->Entries[Index];
|
|
if (Entry->Name == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
return MachoSymbolNameIsPureVirtual (Entry->Name);
|
|
}
|
|
|
|
/**
|
|
Relocates Relocation against the specified Symtab's Symbol Table and
|
|
LoadAddress. This logically matches KXLD's x86_64_process_reloc.
|
|
|
|
@param[in,out] Context Prelinking context.
|
|
@param[in] Kext KEXT prelinking context.
|
|
@param[in] LoadAddress The address to be linked against.
|
|
@param[in] RelocationBase The Relocations base address.
|
|
@param[in] Relocation The Relocation to be processed.
|
|
@param[in] NextRelocation The Relocation following Relocation.
|
|
|
|
@retval 0 The Relocation does not need to be preseved.
|
|
@retval 1 The Relocation must be preseved.
|
|
@retval 0 | BIT31 The Relocation does not need to be preseved.
|
|
The next Relocation shall be skipped.
|
|
@retval 1 | BIT31 The Relocation must be preseved.
|
|
The next Relocation shall be skipped.
|
|
@retval MAX_UINTN The Relocation's target cannot be determined or it is a
|
|
direct pure virtual call.
|
|
|
|
**/
|
|
STATIC
|
|
UINTN
|
|
InternalRelocateRelocationIntel64 (
|
|
IN PRELINKED_CONTEXT *Context,
|
|
IN PRELINKED_KEXT *Kext,
|
|
IN UINT64 LoadAddress,
|
|
IN UINT64 RelocationBase,
|
|
IN CONST MACH_RELOCATION_INFO *Relocation,
|
|
IN CONST MACH_RELOCATION_INFO *NextRelocation OPTIONAL
|
|
)
|
|
{
|
|
UINTN ReturnValue;
|
|
|
|
UINT8 Type;
|
|
INT32 Instruction32;
|
|
UINT64 Instruction64;
|
|
UINT64 Target;
|
|
BOOLEAN IsPair;
|
|
UINT64 PairTarget;
|
|
CONST PRELINKED_VTABLE *Vtable;
|
|
UINT64 LinkPc;
|
|
UINT64 Adjustment;
|
|
UINT32 Length;
|
|
UINT32 Address;
|
|
UINT8 *InstructionPtr;
|
|
UINT32 MaxSize;
|
|
BOOLEAN PcRelative;
|
|
BOOLEAN IsNormalLocal;
|
|
BOOLEAN Result;
|
|
BOOLEAN InvalidPcRel;
|
|
|
|
ASSERT (Relocation != NULL);
|
|
|
|
IsPair = FALSE;
|
|
Adjustment = 0;
|
|
IsNormalLocal = FALSE;
|
|
|
|
Address = Relocation->Address;
|
|
Length = Relocation->Size;
|
|
Type = (UINT8)Relocation->Type;
|
|
PcRelative = (Relocation->PcRelative != 0);
|
|
|
|
if (Length < 2) {
|
|
return MAX_UINTN;
|
|
}
|
|
|
|
InstructionPtr = MachoGetFilePointerByAddress64 (
|
|
&Kext->Context.MachContext,
|
|
(RelocationBase + Address),
|
|
&MaxSize
|
|
);
|
|
if ((InstructionPtr == NULL) || (MaxSize < ((Length != 3) ? 4U : 8U))) {
|
|
return MAX_UINTN;
|
|
}
|
|
|
|
if (Relocation->Extern == 0) {
|
|
IsNormalLocal = TRUE;
|
|
}
|
|
|
|
LinkPc = (Address + LoadAddress);
|
|
|
|
Vtable = NULL;
|
|
Result = InternalCalculateTargetsIntel64 (
|
|
Context,
|
|
Kext,
|
|
LoadAddress,
|
|
Relocation,
|
|
NextRelocation,
|
|
&Target,
|
|
&PairTarget,
|
|
&Vtable
|
|
);
|
|
if (!Result) {
|
|
return MAX_UINTN;
|
|
}
|
|
|
|
// Length == 2
|
|
if (Length != 3) {
|
|
CopyMem (&Instruction32, InstructionPtr, sizeof (Instruction32));
|
|
|
|
if ((Vtable != NULL)
|
|
&& InternalIsDirectPureVirtualCall64 (Vtable, Instruction32)) {
|
|
return MAX_UINTN;
|
|
}
|
|
//
|
|
// There are a number of different small adjustments for pc-relative
|
|
// relocation entries. The general case is to subtract the size of the
|
|
// relocation (represented by the length parameter), and it applies to
|
|
// the GOT types and external SIGNED types. The non-external signed types
|
|
// have a different adjustment corresponding to the specific type.
|
|
//
|
|
switch (Type) {
|
|
case MachX8664RelocSigned:
|
|
if (IsNormalLocal) {
|
|
Adjustment = 0;
|
|
break;
|
|
}
|
|
// Fall through
|
|
case MachX8664RelocSigned1:
|
|
if (IsNormalLocal) {
|
|
Adjustment = 1;
|
|
break;
|
|
}
|
|
// Fall through
|
|
case MachX8664RelocSigned2:
|
|
if (IsNormalLocal) {
|
|
Adjustment = 2;
|
|
break;
|
|
}
|
|
// Fall through
|
|
case MachX8664RelocSigned4:
|
|
if (IsNormalLocal) {
|
|
Adjustment = 4;
|
|
break;
|
|
}
|
|
// Fall through
|
|
case MachX8664RelocBranch:
|
|
case MachX8664RelocGot:
|
|
case MachX8664RelocGotLoad:
|
|
{
|
|
Adjustment = LShiftU64 (1, Length);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
//
|
|
// Perform the actual relocation. All of the 32-bit relocations are
|
|
// pc-relative except for SUBTRACTOR, so a good chunk of the logic is
|
|
// stuck in calculate_displacement_x86_64. The signed relocations are
|
|
// a special case, because when they are non-external, the instruction
|
|
// already contains the pre-relocation displacement, so we only need to
|
|
// find the difference between how far the PC was relocated, and how
|
|
// far the target is relocated. Since the target variable already
|
|
// contains the difference between the target's base and link
|
|
// addresses, we add the difference between the PC's base and link
|
|
// addresses to the adjustment variable. This will yield the
|
|
// appropriate displacement in calculate_displacement.
|
|
//
|
|
switch (Type) {
|
|
case MachX8664RelocBranch:
|
|
{
|
|
InvalidPcRel = !PcRelative;
|
|
Adjustment += LinkPc;
|
|
break;
|
|
}
|
|
|
|
case MachX8664RelocSigned:
|
|
case MachX8664RelocSigned1:
|
|
case MachX8664RelocSigned2:
|
|
case MachX8664RelocSigned4:
|
|
{
|
|
InvalidPcRel = !PcRelative;
|
|
Adjustment += (IsNormalLocal ? LoadAddress : LinkPc);
|
|
break;
|
|
}
|
|
|
|
case MachX8664RelocGot:
|
|
case MachX8664RelocGotLoad:
|
|
{
|
|
InvalidPcRel = !PcRelative;
|
|
Adjustment += LinkPc;
|
|
Target = PairTarget;
|
|
IsPair = TRUE;
|
|
break;
|
|
}
|
|
|
|
case MachX8664RelocSubtractor:
|
|
{
|
|
InvalidPcRel = PcRelative;
|
|
Instruction32 = (INT32)(Target - PairTarget);
|
|
IsPair = TRUE;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
return MAX_UINTN;
|
|
}
|
|
}
|
|
|
|
if (PcRelative) {
|
|
Result = InternalCalculateDisplacementIntel64 (
|
|
Target,
|
|
Adjustment,
|
|
&Instruction32
|
|
);
|
|
if (!Result) {
|
|
return MAX_UINTN;
|
|
}
|
|
}
|
|
|
|
CopyMem (InstructionPtr, &Instruction32, sizeof (Instruction32));
|
|
} else {
|
|
CopyMem (&Instruction64, InstructionPtr, sizeof (Instruction64));
|
|
|
|
if ((Vtable != NULL)
|
|
&& InternalIsDirectPureVirtualCall64 (Vtable, Instruction64)) {
|
|
return MAX_UINTN;
|
|
}
|
|
|
|
switch (Type) {
|
|
case MachX8664RelocUnsigned:
|
|
{
|
|
InvalidPcRel = PcRelative;
|
|
Instruction64 += Target;
|
|
break;
|
|
}
|
|
|
|
case MachX8664RelocSubtractor:
|
|
{
|
|
InvalidPcRel = PcRelative;
|
|
Instruction64 = (Target - PairTarget);
|
|
IsPair = TRUE;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
return MAX_UINTN;
|
|
}
|
|
}
|
|
|
|
CopyMem (InstructionPtr, &Instruction64, sizeof (Instruction64));
|
|
}
|
|
|
|
if (InvalidPcRel) {
|
|
DEBUG ((DEBUG_WARN, "OCAK: Relocation has invalid PC relative flag\n"));
|
|
}
|
|
|
|
ReturnValue = (MachoPreserveRelocationIntel64 (Type) ? 1 : 0);
|
|
|
|
if (IsPair) {
|
|
ReturnValue |= BIT31;
|
|
}
|
|
|
|
return ReturnValue;
|
|
}
|
|
|
|
/**
|
|
Relocates all Mach-O Relocations and copies the ones to be preserved after
|
|
prelinking to TargetRelocations.
|
|
|
|
@param[in,out] Context Prelinking context.
|
|
@param[in] Kext KEXT prelinking context.
|
|
@param[in] LoadAddress The address to be linked against.
|
|
dependencies.
|
|
@param[in] RelocationBase The Relocations base address.
|
|
@param[in] SourceRelocations The Relocations source buffer.
|
|
@param[in] NumRelocations On input, the number of source Relocations.
|
|
On output, the number of Relocations to
|
|
preserve.
|
|
@param[out] TargetRelocations The Relocations destination buffer.
|
|
|
|
@retval Returned is the number of preserved Relocations.
|
|
|
|
**/
|
|
STATIC
|
|
BOOLEAN
|
|
InternalRelocateAndCopyRelocations64 (
|
|
IN PRELINKED_CONTEXT *Context,
|
|
IN PRELINKED_KEXT *Kext,
|
|
IN UINT64 LoadAddress,
|
|
IN UINT64 RelocationBase,
|
|
IN CONST MACH_RELOCATION_INFO *SourceRelocations,
|
|
IN OUT UINT32 *NumRelocations,
|
|
OUT MACH_RELOCATION_INFO *TargetRelocations
|
|
)
|
|
{
|
|
UINT32 PreservedRelocations;
|
|
|
|
UINT32 Index;
|
|
CONST MACH_RELOCATION_INFO *NextRelocation;
|
|
UINTN Result;
|
|
MACH_RELOCATION_INFO *Relocation;
|
|
|
|
ASSERT (Kext != NULL);
|
|
ASSERT (NumRelocations != NULL);
|
|
ASSERT (SourceRelocations != NULL || *NumRelocations == 0);
|
|
ASSERT (TargetRelocations != NULL);
|
|
|
|
PreservedRelocations = 0;
|
|
|
|
for (Index = 0; Index < *NumRelocations; ++Index) {
|
|
//
|
|
// Assertion: Not i386. Scattered Relocations are only supported by i386.
|
|
// && ((UINT32)Relocation->Address & MACH_RELOC_SCATTERED) == 0
|
|
//
|
|
if ((SourceRelocations[Index].Extern == 0)
|
|
&& (SourceRelocations[Index].SymbolNumber == MACH_RELOC_ABSOLUTE)) {
|
|
//
|
|
// A section-based relocation entry can be skipped for absolute
|
|
// symbols.
|
|
//
|
|
continue;
|
|
}
|
|
|
|
NextRelocation = &SourceRelocations[Index + 1];
|
|
//
|
|
// The last Relocation does not have a successor.
|
|
//
|
|
if (Index == (*NumRelocations - 1)) {
|
|
NextRelocation = NULL;
|
|
}
|
|
//
|
|
// Relocate the relocation.
|
|
//
|
|
Result = InternalRelocateRelocationIntel64 (
|
|
Context,
|
|
Kext,
|
|
LoadAddress,
|
|
RelocationBase,
|
|
&SourceRelocations[Index],
|
|
NextRelocation
|
|
);
|
|
if (Result == MAX_UINTN) {
|
|
return FALSE;
|
|
}
|
|
//
|
|
// Copy the Relocation to the destination buffer if it shall be preserved.
|
|
//
|
|
if ((Result & ~(UINTN)BIT31) != 0) {
|
|
Relocation = &TargetRelocations[PreservedRelocations];
|
|
|
|
CopyMem (Relocation, &SourceRelocations[Index], sizeof (*Relocation));
|
|
|
|
if (Relocation->Extern != 0) {
|
|
//
|
|
// All relocation targets have been updated with symbol values.
|
|
// Convert to a local relocation as only sliding is supported from now
|
|
// on.
|
|
//
|
|
Relocation->Extern = 0;
|
|
//
|
|
// Assertion: The entire kext will be slid by the same offset.
|
|
// This is asserted by KXLD as well.
|
|
//
|
|
Relocation->SymbolNumber = 1;
|
|
}
|
|
|
|
++PreservedRelocations;
|
|
}
|
|
//
|
|
// Skip the next Relocation as instructed by
|
|
// InternalRelocateRelocationIntel64().
|
|
//
|
|
if ((Result & BIT31) != 0) {
|
|
++Index;
|
|
}
|
|
}
|
|
|
|
*NumRelocations = PreservedRelocations;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// MACH header
|
|
//
|
|
|
|
STATIC
|
|
BOOLEAN
|
|
InternalRelocateSymbols (
|
|
IN OC_MACHO_CONTEXT *MachoContext,
|
|
IN UINT64 LoadAddress,
|
|
IN UINT32 NumSymbols,
|
|
IN OUT MACH_NLIST_64 *Symbols,
|
|
OUT UINT32 *KmodInfoOffset
|
|
)
|
|
{
|
|
UINT32 Index;
|
|
MACH_NLIST_64 *Symbol;
|
|
CONST CHAR8 *SymbolName;
|
|
BOOLEAN Result;
|
|
UINT32 KmodOffset;
|
|
UINT32 MaxSize;
|
|
|
|
ASSERT (MachoContext != NULL);
|
|
ASSERT (Symbols != NULL || NumSymbols == 0);
|
|
ASSERT (KmodInfoOffset != NULL);
|
|
|
|
KmodOffset = *KmodInfoOffset;
|
|
|
|
for (Index = 0; Index < NumSymbols; ++Index) {
|
|
Symbol = &Symbols[Index];
|
|
|
|
if ((KmodOffset == 0) && ((Symbol->Type & MACH_N_TYPE_STAB) == 0)) {
|
|
SymbolName = MachoGetSymbolName64 (MachoContext, Symbol);
|
|
ASSERT (SymbolName != NULL);
|
|
|
|
if (AsciiStrCmp (SymbolName, "_kmod_info") == 0) {
|
|
Result = MachoSymbolGetFileOffset64 (
|
|
MachoContext,
|
|
Symbol,
|
|
&KmodOffset,
|
|
&MaxSize
|
|
);
|
|
if (!Result
|
|
|| (MaxSize < sizeof (KMOD_INFO_64_V1)
|
|
|| (KmodOffset % 4) != 0)) {
|
|
return FALSE;
|
|
}
|
|
|
|
*KmodInfoOffset = KmodOffset;
|
|
}
|
|
}
|
|
|
|
Result = MachoRelocateSymbol64 (
|
|
MachoContext,
|
|
LoadAddress,
|
|
Symbol
|
|
);
|
|
if (!Result) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
process_symbol_pointers
|
|
**/
|
|
STATIC
|
|
BOOLEAN
|
|
InternalProcessSymbolPointers (
|
|
IN OC_MACHO_CONTEXT *MachoContext,
|
|
IN CONST MACH_DYSYMTAB_COMMAND *DySymtab,
|
|
IN UINT64 LoadAddress
|
|
)
|
|
{
|
|
CONST MACH_HEADER_64 *MachHeader;
|
|
UINT32 MachSize;
|
|
CONST MACH_SECTION_64 *Section;
|
|
UINT32 NumSymbols;
|
|
UINT32 FirstSym;
|
|
BOOLEAN Result;
|
|
UINT32 OffsetTop;
|
|
CONST UINT32 *SymIndices;
|
|
UINT64 *IndirectSymPtr;
|
|
UINT32 Index;
|
|
CONST MACH_NLIST_64 *Symbol;
|
|
|
|
VOID *Tmp;
|
|
|
|
Section = MachoGetSegmentSectionByName64 (
|
|
MachoContext,
|
|
"__DATA",
|
|
"__nl_symbol_ptr"
|
|
);
|
|
if (Section == NULL) {
|
|
return TRUE;
|
|
}
|
|
|
|
NumSymbols = (UINT32)(Section->Size / sizeof (*IndirectSymPtr));
|
|
FirstSym = Section->Reserved1;
|
|
|
|
Result = OcOverflowAddU32 (FirstSym, NumSymbols, &OffsetTop);
|
|
if (Result || (OffsetTop > DySymtab->NumIndirectSymbols)) {
|
|
return FALSE;
|
|
}
|
|
|
|
MachSize = MachoGetFileSize (MachoContext);
|
|
Result = OcOverflowMulAddU32 (
|
|
DySymtab->NumIndirectSymbols,
|
|
sizeof (*IndirectSymPtr),
|
|
DySymtab->IndirectSymbolsOffset,
|
|
&OffsetTop
|
|
);
|
|
if (Result || (OffsetTop > MachSize)) {
|
|
return FALSE;
|
|
}
|
|
|
|
MachHeader = MachoGetMachHeader64 (MachoContext);
|
|
ASSERT (MachHeader != NULL);
|
|
//
|
|
// Iterate through the indirect symbol table and fill in the section of
|
|
// symbol pointers. There are three cases:
|
|
// 1) A normal symbol - put its value directly in the table
|
|
// 2) An INDIRECT_SYMBOL_LOCAL - symbols that are local and already have
|
|
// their offset from the start of the file in the section. Simply
|
|
// add the file's link address to fill this entry.
|
|
// 3) An INDIRECT_SYMBOL_ABS - prepopulated absolute symbols. No
|
|
// action is required.
|
|
//
|
|
Tmp = (VOID *)((UINTN)MachHeader + DySymtab->IndirectSymbolsOffset);
|
|
if (!OC_TYPE_ALIGNED (UINT32, Tmp)) {
|
|
return FALSE;
|
|
}
|
|
SymIndices = (UINT32 *)Tmp + FirstSym;
|
|
|
|
Tmp = (VOID *)((UINTN)MachHeader + Section->Offset);
|
|
if (!OC_TYPE_ALIGNED (UINT64, Tmp)) {
|
|
return FALSE;
|
|
}
|
|
IndirectSymPtr = (UINT64 *)Tmp;
|
|
|
|
for (Index = 0; Index < NumSymbols; ++Index) {
|
|
if ((SymIndices[Index] & MACH_INDIRECT_SYMBOL_LOCAL) != 0) {
|
|
if ((SymIndices[Index] & MACH_INDIRECT_SYMBOL_ABS) != 0) {
|
|
continue;
|
|
}
|
|
|
|
IndirectSymPtr[Index] += LoadAddress;
|
|
} else {
|
|
Symbol = MachoGetSymbolByIndex64 (MachoContext, SymIndices[Index]);
|
|
if (Symbol == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
IndirectSymPtr[Index] += Symbol->Value;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
Prelinks the specified KEXT against the specified LoadAddress and the data
|
|
of its dependencies.
|
|
|
|
@param[in,out] Context Prelinking context.
|
|
@param[in] Kext KEXT prelinking context.
|
|
@param[in] LoadAddress The address this KEXT shall be linked against.
|
|
|
|
@retval Returned is whether the prelinking process has been successful.
|
|
The state of the KEXT is undefined in case this routine fails.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
InternalPrelinkKext64 (
|
|
IN OUT PRELINKED_CONTEXT *Context,
|
|
IN PRELINKED_KEXT *Kext,
|
|
IN UINT64 LoadAddress
|
|
)
|
|
{
|
|
OC_MACHO_CONTEXT *MachoContext;
|
|
MACH_SEGMENT_COMMAND_64 *LinkEditSegment;
|
|
|
|
MACH_HEADER_64 *MachHeader;
|
|
|
|
MACH_SEGMENT_COMMAND_64 *Segment;
|
|
MACH_SECTION_64 *Section;
|
|
|
|
UINT32 Index;
|
|
BOOLEAN Result;
|
|
UINT32 SymtabSize;
|
|
UINT32 SymtabSize2;
|
|
|
|
MACH_SYMTAB_COMMAND *Symtab;
|
|
MACH_DYSYMTAB_COMMAND *DySymtab;
|
|
MACH_NLIST_64 *Symbol;
|
|
CONST CHAR8 *SymbolName;
|
|
CONST MACH_NLIST_64 *SymbolTable;
|
|
CONST CHAR8 *StringTable;
|
|
UINT32 NumSymbols;
|
|
CONST MACH_NLIST_64 *IndirectSymtab;
|
|
UINT32 NumIndirectSymbols;
|
|
CONST MACH_NLIST_64 *LocalSymtab;
|
|
UINT32 NumLocalSymbols;
|
|
CONST MACH_NLIST_64 *ExternalSymtab;
|
|
UINT32 NumExternalSymbols;
|
|
CONST MACH_NLIST_64 *UndefinedSymtab;
|
|
UINT32 NumUndefinedSymbols;
|
|
UINT64 WeakTestValue;
|
|
|
|
UINT32 NumRelocations;
|
|
UINT32 NumRelocations2;
|
|
CONST MACH_RELOCATION_INFO *Relocations;
|
|
MACH_RELOCATION_INFO *TargetRelocation;
|
|
MACH_SEGMENT_COMMAND_64 *FirstSegment;
|
|
|
|
VOID *LinkEdit;
|
|
UINT32 LinkEditSize;
|
|
UINT32 SymbolTableOffset;
|
|
UINT32 SymbolTableSize;
|
|
UINT32 RelocationsOffset;
|
|
UINT32 RelocationsSize;
|
|
UINT32 StringTableOffset;
|
|
UINT32 StringTableSize;
|
|
|
|
UINT32 SegmentOffset;
|
|
UINT32 SegmentSize;
|
|
|
|
UINT64 SegmentVmSizes;
|
|
UINT32 KmodInfoOffset;
|
|
KMOD_INFO_64_V1 *KmodInfo;
|
|
|
|
ASSERT (Context != NULL);
|
|
ASSERT (Kext != NULL);
|
|
ASSERT (LoadAddress != 0);
|
|
|
|
MachoContext = &Kext->Context.MachContext;
|
|
LinkEditSegment = Kext->LinkEditSegment;
|
|
|
|
MachHeader = MachoGetMachHeader64 (MachoContext);
|
|
//
|
|
// Only perform actions when the kext is flag'd to be dynamically linked.
|
|
//
|
|
if ((MachHeader->Flags & MACH_HEADER_FLAG_DYNAMIC_LINKER_LINK) == 0) {
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
if (LinkEditSegment == NULL || Kext->Context.VirtualKmod == 0) {
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
//
|
|
// Retrieve the symbol tables required for most following operations.
|
|
//
|
|
NumSymbols = MachoGetSymbolTable (
|
|
MachoContext,
|
|
&SymbolTable,
|
|
&StringTable,
|
|
&LocalSymtab,
|
|
&NumLocalSymbols,
|
|
&ExternalSymtab,
|
|
&NumExternalSymbols,
|
|
&UndefinedSymtab,
|
|
&NumUndefinedSymbols
|
|
);
|
|
if (NumSymbols == 0) {
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
Symtab = MachoContext->Symtab;
|
|
ASSERT (Symtab != NULL);
|
|
|
|
DySymtab = MachoContext->DySymtab;
|
|
ASSERT (DySymtab != NULL);
|
|
//
|
|
// Prepare constructing a new __LINKEDIT section to...
|
|
// 1. strip undefined symbols for they are not allowed in prelinked
|
|
// binaries,
|
|
// 2. merge local and external relocations as only sliding is allowed from
|
|
// this point onwards,
|
|
// 3. strip Code Signature because we modified the binary, as well as other
|
|
// linker metadata stripped by KXLD as well, probably for space reasons.
|
|
//
|
|
// Example original layout:
|
|
// Local relocations - Symbol Table - external relocations - String Table -
|
|
// Code Signature
|
|
//
|
|
// Example prelinked layout
|
|
// Symbol Table - relocations (external -> local) - String Table
|
|
//
|
|
SymbolTableSize = (NumSymbols * sizeof (MACH_NLIST_64));
|
|
StringTableSize = Symtab->StringsSize;
|
|
//
|
|
// For the allocation, assume all relocations will be preserved to simplify
|
|
// the code, the memory is only temporarily allocated anyway.
|
|
//
|
|
NumRelocations = DySymtab->NumOfLocalRelocations;
|
|
NumRelocations += DySymtab->NumExternalRelocations;
|
|
RelocationsSize = (NumRelocations * sizeof (MACH_RELOCATION_INFO));
|
|
|
|
LinkEdit = Context->LinkBuffer;
|
|
LinkEditSize = (SymbolTableSize + RelocationsSize + StringTableSize);
|
|
|
|
if (LinkEditSize > LinkEditSegment->FileSize) {
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
SymbolTableSize -= (NumUndefinedSymbols * sizeof (MACH_NLIST_64));
|
|
|
|
SymbolTableOffset = 0;
|
|
RelocationsOffset = (SymbolTableOffset + SymbolTableSize);
|
|
//
|
|
// Solve indirect symbols.
|
|
//
|
|
WeakTestValue = 0;
|
|
NumIndirectSymbols = MachoGetIndirectSymbolTable (
|
|
MachoContext,
|
|
&IndirectSymtab
|
|
);
|
|
for (Index = 0; Index < NumIndirectSymbols; ++Index) {
|
|
Symbol = (MACH_NLIST_64 *)&IndirectSymtab[Index];
|
|
SymbolName = MachoGetIndirectSymbolName64 (MachoContext, Symbol);
|
|
if (SymbolName == NULL) {
|
|
return EFI_LOAD_ERROR;
|
|
}
|
|
|
|
Result = InternalSolveSymbol64 (
|
|
Context,
|
|
Kext,
|
|
SymbolName,
|
|
Symbol,
|
|
&WeakTestValue,
|
|
UndefinedSymtab,
|
|
NumUndefinedSymbols
|
|
);
|
|
if (!Result) {
|
|
return EFI_LOAD_ERROR;
|
|
}
|
|
}
|
|
//
|
|
// Solve undefined symbols.
|
|
//
|
|
for (Index = 0; Index < NumUndefinedSymbols; ++Index) {
|
|
Symbol = (MACH_NLIST_64 *)&UndefinedSymtab[Index];
|
|
//
|
|
// Undefined symbols are solved via their name.
|
|
//
|
|
SymbolName = MachoGetSymbolName64 (MachoContext, Symbol);
|
|
Result = InternalSolveSymbol64 (
|
|
Context,
|
|
Kext,
|
|
SymbolName,
|
|
Symbol,
|
|
&WeakTestValue,
|
|
UndefinedSymtab,
|
|
NumUndefinedSymbols
|
|
);
|
|
if (!Result) {
|
|
DEBUG ((
|
|
DEBUG_INFO,
|
|
"OCAK: Symbol %s was unresolved for kext %a\n",
|
|
MachoGetSymbolName64 (MachoContext, Symbol),
|
|
Kext->Identifier
|
|
));
|
|
return EFI_LOAD_ERROR;
|
|
}
|
|
}
|
|
//
|
|
// Create and patch the KEXT's VTables.
|
|
//
|
|
Result = InternalPatchByVtables64 (Context, Kext);
|
|
if (!Result) {
|
|
DEBUG ((DEBUG_INFO, "OCAK: Vtable patching failed for kext %a\n", Kext->Identifier));
|
|
return EFI_LOAD_ERROR;
|
|
}
|
|
//
|
|
// Relocate local and external symbols.
|
|
//
|
|
KmodInfoOffset = 0;
|
|
|
|
Result = InternalRelocateSymbols (
|
|
MachoContext,
|
|
LoadAddress,
|
|
NumLocalSymbols,
|
|
(MACH_NLIST_64 *)LocalSymtab,
|
|
&KmodInfoOffset
|
|
);
|
|
if (!Result) {
|
|
return EFI_LOAD_ERROR;
|
|
}
|
|
|
|
Result = InternalRelocateSymbols (
|
|
MachoContext,
|
|
LoadAddress,
|
|
NumExternalSymbols,
|
|
(MACH_NLIST_64 *)ExternalSymtab,
|
|
&KmodInfoOffset
|
|
);
|
|
if (!Result || (KmodInfoOffset == 0)) {
|
|
return EFI_LOAD_ERROR;
|
|
}
|
|
|
|
KmodInfo = (KMOD_INFO_64_V1 *)((UINTN)MachHeader + (UINTN)KmodInfoOffset);
|
|
|
|
FirstSegment = MachoGetNextSegment64 (MachoContext, NULL);
|
|
if (FirstSegment == NULL) {
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
Result = InternalProcessSymbolPointers (MachoContext, DySymtab, LoadAddress);
|
|
if (!Result) {
|
|
return EFI_LOAD_ERROR;
|
|
}
|
|
//
|
|
// Copy the relocations to be reserved and adapt the symbol number they
|
|
// reference in case it has been relocated above.
|
|
//
|
|
TargetRelocation = (MACH_RELOCATION_INFO *)(
|
|
(UINTN)LinkEdit + RelocationsOffset
|
|
);
|
|
//
|
|
// Relocate and copy local and external relocations.
|
|
//
|
|
Relocations = MachoContext->LocalRelocations;
|
|
NumRelocations = DySymtab->NumOfLocalRelocations;
|
|
Result = InternalRelocateAndCopyRelocations64 (
|
|
Context,
|
|
Kext,
|
|
LoadAddress,
|
|
FirstSegment->VirtualAddress,
|
|
Relocations,
|
|
&NumRelocations,
|
|
&TargetRelocation[0]
|
|
);
|
|
if (!Result) {
|
|
return EFI_LOAD_ERROR;
|
|
}
|
|
|
|
Relocations = MachoContext->ExternRelocations;
|
|
NumRelocations2 = DySymtab->NumExternalRelocations;
|
|
Result = InternalRelocateAndCopyRelocations64 (
|
|
Context,
|
|
Kext,
|
|
LoadAddress,
|
|
FirstSegment->VirtualAddress,
|
|
Relocations,
|
|
&NumRelocations2,
|
|
&TargetRelocation[NumRelocations]
|
|
);
|
|
if (!Result) {
|
|
return EFI_LOAD_ERROR;
|
|
}
|
|
NumRelocations += NumRelocations2;
|
|
RelocationsSize = (NumRelocations * sizeof (MACH_RELOCATION_INFO));
|
|
//
|
|
// Copy the entire symbol table excluding the area for undefined symbols.
|
|
//
|
|
SymtabSize = SymbolTableSize;
|
|
if (NumUndefinedSymbols > 0) {
|
|
SymtabSize = (UINT32)((UndefinedSymtab - SymbolTable) * sizeof (MACH_NLIST_64));
|
|
}
|
|
|
|
CopyMem (
|
|
(VOID *)((UINTN)LinkEdit + SymbolTableOffset),
|
|
SymbolTable,
|
|
SymtabSize
|
|
);
|
|
|
|
if (NumUndefinedSymbols > 0) {
|
|
SymtabSize2 = (UINT32)(&SymbolTable[NumSymbols] - &UndefinedSymtab[NumUndefinedSymbols]);
|
|
SymtabSize2 *= sizeof (MACH_NLIST_64);
|
|
CopyMem (
|
|
(VOID *)((UINTN)LinkEdit + SymbolTableOffset + SymtabSize),
|
|
(VOID *)&UndefinedSymtab[NumUndefinedSymbols],
|
|
SymtabSize2
|
|
);
|
|
|
|
NumSymbols -= NumUndefinedSymbols;
|
|
}
|
|
//
|
|
// Copy the String Table. Don't strip it for the saved bytes are unlikely
|
|
// worth the time required.
|
|
//
|
|
StringTableOffset = (RelocationsOffset + RelocationsSize);
|
|
CopyMem (
|
|
(VOID *)((UINTN)LinkEdit + StringTableOffset),
|
|
StringTable,
|
|
StringTableSize
|
|
);
|
|
//
|
|
// Set up the tables with the new offsets and Symbol Table length.
|
|
//
|
|
Symtab->SymbolsOffset = (UINT32)(LinkEditSegment->FileOffset + SymbolTableOffset);
|
|
Symtab->NumSymbols = NumSymbols;
|
|
Symtab->StringsOffset = (UINT32)(LinkEditSegment->FileOffset + StringTableOffset);
|
|
|
|
DySymtab->LocalRelocationsOffset = (UINT32)(LinkEditSegment->FileOffset + RelocationsOffset);
|
|
DySymtab->NumOfLocalRelocations = NumRelocations;
|
|
//
|
|
// Clear dynamic linker information.
|
|
//
|
|
DySymtab->LocalSymbolsIndex = 0;
|
|
DySymtab->NumLocalSymbols = 0;
|
|
DySymtab->NumExternalSymbols = 0;
|
|
DySymtab->ExternalSymbolsIndex = 0;
|
|
DySymtab->NumExternalRelocations = 0;
|
|
DySymtab->ExternalRelocationsOffset = 0;
|
|
DySymtab->UndefinedSymbolsIndex = 0;
|
|
DySymtab->NumUndefinedSymbols = 0;
|
|
DySymtab->IndirectSymbolsOffset = 0;
|
|
DySymtab->NumIndirectSymbols = 0;
|
|
//
|
|
// Copy the new __LINKEDIT segment into the binary and fix its Load Command.
|
|
//
|
|
LinkEditSize = (SymbolTableSize + RelocationsSize + StringTableSize);
|
|
|
|
CopyMem (
|
|
(VOID *)((UINTN)MachHeader + (UINTN)LinkEditSegment->FileOffset),
|
|
LinkEdit,
|
|
LinkEditSize
|
|
);
|
|
ZeroMem (
|
|
(VOID *)((UINTN)MachHeader + (UINTN)LinkEditSegment->FileOffset + LinkEditSize),
|
|
(UINTN)(LinkEditSegment->FileSize - LinkEditSize)
|
|
);
|
|
|
|
LinkEditSize = MACHO_ALIGN (LinkEditSize);
|
|
LinkEditSegment->FileSize = LinkEditSize;
|
|
LinkEditSegment->Size = LinkEditSize;
|
|
|
|
//
|
|
// Adapt the link addresses of all Segments and their Sections.
|
|
//
|
|
SegmentOffset = 0;
|
|
SegmentSize = 0;
|
|
|
|
SegmentVmSizes = 0;
|
|
|
|
Segment = NULL;
|
|
while ((Segment = MachoGetNextSegment64 (MachoContext, Segment)) != NULL) {
|
|
Section = NULL;
|
|
while ((Section = MachoGetNextSection64 (MachoContext, Segment, Section)) != NULL) {
|
|
Section->Address = ALIGN_VALUE (
|
|
(Section->Address + LoadAddress),
|
|
(UINT64)(1U << Section->Alignment)
|
|
);
|
|
}
|
|
|
|
Segment->VirtualAddress += LoadAddress;
|
|
|
|
//
|
|
// Logically equivalent to kxld_seg_set_vm_protections.
|
|
// Assertion: Not i386 (strict protection).
|
|
//
|
|
if (AsciiStrnCmp (Segment->SegmentName, "__TEXT", ARRAY_SIZE (Segment->SegmentName)) == 0) {
|
|
Segment->InitialProtection = TEXT_SEG_PROT;
|
|
Segment->MaximumProtection = TEXT_SEG_PROT;
|
|
} else {
|
|
Segment->InitialProtection = DATA_SEG_PROT;
|
|
Segment->MaximumProtection = DATA_SEG_PROT;
|
|
}
|
|
|
|
if (Segment->FileOffset > SegmentOffset) {
|
|
SegmentOffset = (UINT32)Segment->FileOffset;
|
|
SegmentSize = (UINT32)Segment->FileSize;
|
|
}
|
|
|
|
SegmentVmSizes += Segment->Size;
|
|
}
|
|
//
|
|
// Populate kmod information.
|
|
//
|
|
KmodInfo->Address = LoadAddress;
|
|
//
|
|
// This is a hack borrowed from XNU. Real header size is equal to:
|
|
// sizeof (*MachHeader) + MachHeader->CommandsSize (often aligned to 4096)
|
|
// However, it cannot be set to this value unless it exists in a separate segment,
|
|
// and presently it is not the case on macOS. When header is put to __TEXT (as usual),
|
|
// XNU makes it read only, and this prevents __TEXT from gaining executable permission.
|
|
// See OSKext::setVMAttributes.
|
|
//
|
|
KmodInfo->HdrSize = 0;
|
|
KmodInfo->Size = KmodInfo->HdrSize + SegmentVmSizes;
|
|
//
|
|
// Adapt the Mach-O header to signal being prelinked.
|
|
//
|
|
MachHeader->Flags = MACH_HEADER_FLAG_NO_UNDEFINED_REFERENCES;
|
|
//
|
|
// Reinitialize the Mach-O context to account for the changed __LINKEDIT
|
|
// segment and file size.
|
|
//
|
|
if (!MachoInitializeContext (MachoContext, MachHeader, (SegmentOffset + SegmentSize), MachoContext->ContainerOffset)) {
|
|
//
|
|
// This should never failed under normal and abnormal conditions.
|
|
//
|
|
ASSERT (FALSE);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
return EFI_SUCCESS;
|
|
}
|