mirror of
https://github.com/acidanthera/OpenCorePkg.git
synced 2025-12-08 19:25:01 +00:00
3049 lines
88 KiB
C
3049 lines
88 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 <Uefi.h>
|
|
|
|
#include <IndustryStandard/AppleMachoImage.h>
|
|
|
|
#include <Protocol/SimpleFileSystem.h>
|
|
|
|
#include <Library/BaseLib.h>
|
|
#include <Library/BaseMemoryLib.h>
|
|
#include <Library/DebugLib.h>
|
|
#include <Library/MemoryAllocationLib.h>
|
|
#include <Library/OcMachoLib.h>
|
|
|
|
#define KXLD_WEAK_TEST_SYMBOL "_gOSKextUnresolved"
|
|
|
|
#define OS_METACLASS_VTABLE_NAME "__ZTV11OSMetaClass"
|
|
|
|
#define X86_64_RIP_RELATIVE_LIMIT 0x80000000ULL
|
|
|
|
#define SYM_MAX_NAME_LEN 256U
|
|
|
|
#define VTABLE_ENTRY_SIZE_64 8U
|
|
#define VTABLE_HEADER_LEN_64 2U
|
|
#define VTABLE_HEADER_SIZE_64 (VTABLE_HEADER_LEN_64 * VTABLE_ENTRY_SIZE_64)
|
|
|
|
typedef struct {
|
|
UINT32 StringIndex; ///< index into the string table
|
|
UINT64 Value; ///< value of this symbol (or stab offset)
|
|
} OC_SYMBOL_64;
|
|
|
|
#define OC_SYMBOL_TABLE_64_SIGNATURE SIGNATURE_32 ('O', 'S', '6', '4')
|
|
|
|
/**
|
|
Gets the next element in a linked list of OC_SYMBOL_TABLE_64.
|
|
|
|
@param[in] This The current ListEntry.
|
|
|
|
**/
|
|
#define GET_OC_SYMBOL_TABLE_64_FROM_LINK(This) \
|
|
CR ( \
|
|
(This), \
|
|
OC_SYMBOL_TABLE_64, \
|
|
Link, \
|
|
OC_SYMBOL_TABLE_64_SIGNATURE \
|
|
)
|
|
|
|
typedef struct {
|
|
///
|
|
/// These data are used to construct linked lists of dependency information
|
|
/// for each KEXT. It is declared hear for every dependency will
|
|
/// eventually be part of a list and to save separate allocations per KEXT.
|
|
///
|
|
UINT32 Signature;
|
|
LIST_ENTRY Link;
|
|
BOOLEAN IsIndirect;
|
|
///
|
|
/// The String Table associated with this symbol table.
|
|
///
|
|
CONST CHAR8 *StringTable;
|
|
///
|
|
/// The number of symbols in the entire symbols buffer.
|
|
///
|
|
UINTN NumberOfSymbols;
|
|
///
|
|
/// The number of C++ symbols at the end of the symbols buffer.
|
|
///
|
|
UINTN NumberOfCxxSymbols;
|
|
OC_SYMBOL_64 Symbols[]; ///< The symbol buffer.
|
|
} OC_SYMBOL_TABLE_64;
|
|
|
|
typedef struct {
|
|
CONST CHAR8 *Name; ///< The symbol's name.
|
|
UINT64 Address; ///< The symbol's address.
|
|
} OC_VTABLE_ENTRY;
|
|
|
|
#define GET_NEXT_OC_VTABLE(This) \
|
|
((OC_VTABLE *)(&(This)->Entries[(This)->NumberOfEntries]))
|
|
|
|
typedef struct {
|
|
CONST CHAR8 *Name; ///< The VTable's name.
|
|
UINTN NumberOfEntries; ///< The number of VTable entries.
|
|
OC_VTABLE_ENTRY Entries[]; ///< The VTable entries.
|
|
} OC_VTABLE;
|
|
|
|
#define OC_VTABLE_ARRAY_SIGNATURE SIGNATURE_32 ('O', 'V', 'T', 'A')
|
|
|
|
/**
|
|
Gets the next element in a linked list of OC_VTABLE_ARRAY.
|
|
|
|
@param[in] This The current ListEntry.
|
|
|
|
**/
|
|
#define GET_OC_VTABLE_ARRAY_FROM_LINK(This) \
|
|
CR ( \
|
|
(This), \
|
|
OC_VTABLE_ARRAY, \
|
|
Link, \
|
|
OC_VTABLE_ARRAY_SIGNATURE \
|
|
)
|
|
|
|
#define GET_FIRST_OC_VTABLE(This) \
|
|
((OC_VTABLE *)((This) + 1))
|
|
|
|
typedef struct {
|
|
///
|
|
/// These data are used to construct linked lists of dependency information
|
|
/// for each KEXT. It is declared hear for every dependency will
|
|
/// eventually be part of a list and to save separate allocations per KEXT.
|
|
///
|
|
UINT32 Signature;
|
|
LIST_ENTRY Link;
|
|
///
|
|
/// The number of VTables in the array.
|
|
///
|
|
UINTN NumberOfVtables;
|
|
} OC_VTABLE_ARRAY;
|
|
|
|
typedef union {
|
|
struct {
|
|
UINT32 Major : 14;
|
|
UINT32 Minor : 7;
|
|
UINT32 Revision : 7;
|
|
UINT32 Stage : 4;
|
|
UINT32 StageLevel : 8;
|
|
} Bits;
|
|
UINT64 Value;
|
|
} OC_KEXT_VERSION;
|
|
|
|
typedef enum {
|
|
OcKextVersionStageDevelopment = 1,
|
|
OcKextVersionStageAlpha = 3,
|
|
OcKextVersionStageBeta = 5,
|
|
OcKextVersionStageCandidate = 7,
|
|
OcKextVersionStageRelease = 9
|
|
} OC_KEXT_VERSION_STAGE;
|
|
|
|
typedef struct {
|
|
OC_SYMBOL_TABLE_64 *SymbolTable;
|
|
OC_VTABLE_ARRAY *Vtables;
|
|
} OC_DEPENDENCY_DATA;
|
|
|
|
#define OC_DEPENDENCY_INFO_ENTRY_SIGNATURE \
|
|
SIGNATURE_32 ('O', 'D', 'I', 'E')
|
|
|
|
typedef struct OC_DEPENDENCY_INFO_ENTRY_ OC_DEPENDENCY_INFO_ENTRY;
|
|
|
|
typedef struct {
|
|
CONST CHAR8 *Name;
|
|
OC_KEXT_VERSION Version;
|
|
OC_KEXT_VERSION CompatibleVersion;
|
|
UINTN NumberOfDependencies;
|
|
OC_DEPENDENCY_INFO_ENTRY *Dependencies[];
|
|
} OC_DEPENDENCY_INFO;
|
|
|
|
struct OC_DEPENDENCY_INFO_ENTRY_ {
|
|
UINT32 Signature;
|
|
LIST_ENTRY Link;
|
|
OC_DEPENDENCY_DATA Data;
|
|
OC_DEPENDENCY_INFO Info;
|
|
};
|
|
|
|
typedef struct {
|
|
CONST CHAR8 *Plist;
|
|
CONST EFI_FILE_PROTOCOL *Directory;
|
|
//
|
|
// Private data.
|
|
//
|
|
CONST VOID *Private;
|
|
} OC_KEXT_REQUEST;
|
|
|
|
//
|
|
// Symbols
|
|
//
|
|
|
|
/**
|
|
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.
|
|
|
|
**/
|
|
STATIC
|
|
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;
|
|
}
|
|
|
|
STATIC
|
|
CONST OC_SYMBOL_64 *
|
|
InternalOcGetSymbolByName (
|
|
IN CONST OC_SYMBOL_TABLE_64 *DefinedSymbols,
|
|
IN CONST CHAR8 *Name,
|
|
IN BOOLEAN CheckIndirect
|
|
)
|
|
{
|
|
CONST OC_SYMBOL_TABLE_64 *SymbolsWalker;
|
|
CONST OC_SYMBOL_64 *Symbols;
|
|
UINTN NumberOfSymbols;
|
|
UINTN CxxIndex;
|
|
UINTN Index;
|
|
INTN Result;
|
|
|
|
SymbolsWalker = DefinedSymbols;
|
|
|
|
do {
|
|
Symbols = SymbolsWalker->Symbols;
|
|
NumberOfSymbols = SymbolsWalker->NumberOfSymbols;
|
|
|
|
if (SymbolsWalker->IsIndirect) {
|
|
if (!CheckIndirect) {
|
|
continue;
|
|
}
|
|
//
|
|
// Only consider C++ symbols for indirect dependencies.
|
|
//
|
|
CxxIndex = (
|
|
SymbolsWalker->NumberOfSymbols
|
|
- SymbolsWalker->NumberOfCxxSymbols
|
|
);
|
|
Symbols = &Symbols[CxxIndex];
|
|
NumberOfSymbols = SymbolsWalker->NumberOfCxxSymbols;
|
|
}
|
|
|
|
for (Index = 0; Index < NumberOfSymbols; ++Index) {
|
|
Result = AsciiStrCmp (
|
|
Name,
|
|
(SymbolsWalker->StringTable + Symbols[Index].StringIndex)
|
|
);
|
|
if (Result == 0) {
|
|
return &Symbols[Index];
|
|
}
|
|
}
|
|
|
|
SymbolsWalker = GET_OC_SYMBOL_TABLE_64_FROM_LINK (
|
|
GetNextNode (
|
|
&DefinedSymbols->Link,
|
|
&SymbolsWalker->Link
|
|
)
|
|
);
|
|
} while (!IsNull (&DefinedSymbols->Link, &SymbolsWalker->Link));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
Worker function to solve Symbol against the specified DefinedSymbols.
|
|
It does not consider Symbol might be a weak reference.
|
|
|
|
@param[in] DefinedSymbols List of defined symbols of all dependencies.
|
|
@param[in] Name The name of the symbol to resolve against.
|
|
@param[in,out] Symbol The symbol to be resolved.
|
|
@param[in] Strings The String Table associated with Symbol.
|
|
|
|
@retval Returned is whether the symbol was solved successfully.
|
|
|
|
**/
|
|
STATIC
|
|
BOOLEAN
|
|
InternalSolveSymbolNonWeak64 (
|
|
IN CONST OC_SYMBOL_TABLE_64 *DefinedSymbols,
|
|
IN CONST CHAR8 *Name,
|
|
IN OUT MACH_NLIST_64 *Symbol,
|
|
IN CONST CHAR8 *Strings
|
|
)
|
|
{
|
|
INTN Result;
|
|
CONST OC_SYMBOL_64 *ResolveSymbol;
|
|
|
|
if (Symbol->Type != MACH_N_TYPE_UNDF) {
|
|
if (Symbol->Type != MACH_N_TYPE_INDR) {
|
|
//
|
|
// KXLD_WEAK_TEST_SYMBOL might have been resolved by the resolving code
|
|
// at the end of InternalSolveSymbol64.
|
|
//
|
|
Result = AsciiStrCmp (
|
|
(Strings + Symbol->UnifiedName.StringIndex),
|
|
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.
|
|
//
|
|
ASSERT (FALSE);
|
|
}
|
|
} else if (Symbol->Value != 0) {
|
|
//
|
|
// Common symbols are not supported.
|
|
//
|
|
ASSERT (FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
ResolveSymbol = InternalOcGetSymbolByName (
|
|
DefinedSymbols,
|
|
Name,
|
|
FALSE
|
|
);
|
|
if (ResolveSymbol != NULL) {
|
|
InternalSolveSymbolValue64 (ResolveSymbol->Value, Symbol);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
Solves Symbol against the specified DefinedSymbols.
|
|
|
|
@param[in] DefinedSymbols List of defined symbols of all dependencies.
|
|
@param[in] SymbolTable The KEXT's Symbol Table.
|
|
@param[in] StringTable The KEXT's String Table.
|
|
@param[in] DySymtab The DYSYMTAB Load Command of the KEXT.
|
|
@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. For weak
|
|
symbols, this includes solving with _gOSKextUnresolved.
|
|
|
|
**/
|
|
STATIC
|
|
BOOLEAN
|
|
InternalSolveSymbol64 (
|
|
IN CONST OC_SYMBOL_TABLE_64 *DefinedSymbols,
|
|
IN CONST MACH_NLIST_64 *SymbolTable,
|
|
IN CONST CHAR8 *StringTable,
|
|
IN CONST MACH_DYSYMTAB_COMMAND *DySymtab,
|
|
IN CONST CHAR8 *Name,
|
|
IN OUT MACH_NLIST_64 *Symbol
|
|
)
|
|
{
|
|
BOOLEAN Success;
|
|
CONST MACH_NLIST_64 *Symbols;
|
|
UINTN Index;
|
|
INTN Result;
|
|
CONST MACH_NLIST_64 *WeakTestSymbol;
|
|
|
|
ASSERT (Symbol != NULL);
|
|
ASSERT ((Symbol->Type & MACH_N_TYPE_STAB) == 0);
|
|
|
|
Success = InternalSolveSymbolNonWeak64 (
|
|
DefinedSymbols,
|
|
Name,
|
|
Symbol,
|
|
StringTable
|
|
);
|
|
if (Success) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (((Symbol->Type & MACH_N_TYPE_STAB) == 0)
|
|
&& ((Symbol->Descriptor & MACH_N_WEAK_DEF) != 0)) {
|
|
//
|
|
// KXLD_WEAK_TEST_SYMBOL is not going to defined or exposed by a KEXT
|
|
// prelinked by this library, hence only check the undefined symbols region
|
|
// for matches.
|
|
//
|
|
Symbols = &SymbolTable[DySymtab->UndefinedSymbolsIndex];
|
|
|
|
for (Index = 0; Index < DySymtab->NumberOfUndefinedSymbols; ++Index) {
|
|
WeakTestSymbol = &Symbols[Index];
|
|
Result = AsciiStrCmp (
|
|
(StringTable + WeakTestSymbol->UnifiedName.StringIndex),
|
|
KXLD_WEAK_TEST_SYMBOL
|
|
);
|
|
if (Result == 0) {
|
|
if (WeakTestSymbol->Type == MACH_N_TYPE_UNDF) {
|
|
Success = InternalSolveSymbolNonWeak64 (
|
|
DefinedSymbols,
|
|
Name,
|
|
Symbol,
|
|
StringTable
|
|
);
|
|
if (!Success) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
InternalSolveSymbolValue64 (WeakTestSymbol->Value, Symbol);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
Fills SymbolTable with the symbols provided in Symbols. For performance
|
|
reasons, the C++ symbols are continuously added to the top of the buffer.
|
|
Their order is not preserved. SymbolTable->SymbolTable is expected to be
|
|
a valid buffer that can store at least NumberOfSymbols symbols.
|
|
|
|
@param[in] NumberOfSymbols The number of symbols to copy.
|
|
@param[in] Symbols The source symbol array.
|
|
@param[in] Strings The String Table associated with Symbols.
|
|
@param[in,out] SymbolTable The desination Symbol List. Must be able to
|
|
hold at least NumberOfSymbols symbols.
|
|
|
|
**/
|
|
STATIC
|
|
VOID
|
|
InternalFillSymbolTable64 (
|
|
IN UINTN NumberOfSymbols,
|
|
IN CONST MACH_NLIST_64 *Symbols,
|
|
IN CONST CHAR8 *Strings,
|
|
IN OUT OC_SYMBOL_TABLE_64 *SymbolTable
|
|
)
|
|
{
|
|
OC_SYMBOL_64 *WalkerBottom;
|
|
OC_SYMBOL_64 *WalkerTop;
|
|
UINTN NumberOfCxxSymbols;
|
|
UINTN Index;
|
|
CONST MACH_NLIST_64 *Symbol;
|
|
CONST CHAR8 *Name;
|
|
BOOLEAN Result;
|
|
|
|
ASSERT (NumberOfSymbols > 0);
|
|
ASSERT (Symbols != NULL);
|
|
ASSERT (Strings != NULL);
|
|
|
|
WalkerBottom = SymbolTable->Symbols;
|
|
WalkerTop = (WalkerBottom + (SymbolTable->NumberOfSymbols - 1));
|
|
|
|
NumberOfCxxSymbols = 0;
|
|
|
|
for (Index = 0; Index < NumberOfSymbols; ++Index) {
|
|
Symbol = &Symbols[Index];
|
|
Name = (Strings + Symbol->UnifiedName.StringIndex);
|
|
Result = MachoIsSymbolNameCxx (Name);
|
|
|
|
if (!Result) {
|
|
WalkerBottom->StringIndex = Symbol->UnifiedName.StringIndex;
|
|
WalkerBottom->Value = Symbol->Value;
|
|
++WalkerBottom;
|
|
} else {
|
|
WalkerTop->StringIndex = Symbol->UnifiedName.StringIndex;
|
|
WalkerTop->Value = Symbol->Value;
|
|
--WalkerTop;
|
|
|
|
++NumberOfCxxSymbols;
|
|
}
|
|
}
|
|
|
|
SymbolTable->NumberOfSymbols = NumberOfSymbols;
|
|
SymbolTable->NumberOfCxxSymbols = NumberOfCxxSymbols;
|
|
}
|
|
|
|
//
|
|
// VTables
|
|
//
|
|
|
|
/**
|
|
* kxld_vtable_patch
|
|
*/
|
|
STATIC
|
|
BOOLEAN
|
|
InternalPatchVtableSymbol (
|
|
IN CONST MACH_HEADER_64 *MachHeader,
|
|
IN CONST MACH_NLIST_64 *SymbolTable,
|
|
IN CONST MACH_DYSYMTAB_COMMAND *DySymtab,
|
|
IN CONST CHAR8 *StringTable,
|
|
IN CONST OC_VTABLE_ENTRY *ParentEntry,
|
|
IN CONST CHAR8 *VtableName,
|
|
OUT MACH_NLIST_64 *Symbol
|
|
)
|
|
{
|
|
CONST CHAR8 *Name;
|
|
INTN Result;
|
|
BOOLEAN Success;
|
|
CONST CHAR8 *ClassName;
|
|
CHAR8 FunctionPrefix[SYM_MAX_NAME_LEN];
|
|
|
|
ASSERT (Symbol != NULL);
|
|
ASSERT (ParentEntry != NULL);
|
|
//
|
|
// The child entry can be NULL when a locally-defined, non-external
|
|
// symbol is stripped. We wouldn't patch this entry anyway, so we
|
|
// just skip it.
|
|
//
|
|
if (Symbol == NULL) {
|
|
return TRUE;
|
|
}
|
|
//
|
|
// It's possible for the patched parent entry not to have a symbol
|
|
// (e.g. when the definition is inlined). We can't patch this entry no
|
|
// matter what, so we'll just skip it and die later if it's a problem
|
|
// (which is not likely).
|
|
//
|
|
if (ParentEntry->Name == NULL) {
|
|
return TRUE;
|
|
}
|
|
//
|
|
// 1) If the symbol is defined locally, do not patch
|
|
//
|
|
Success = MachoSymbolIsLocalDefined (
|
|
MachHeader,
|
|
SymbolTable,
|
|
DySymtab,
|
|
Symbol
|
|
);
|
|
if (!Success) {
|
|
return TRUE;
|
|
}
|
|
|
|
Name = (StringTable + Symbol->UnifiedName.StringIndex);
|
|
//
|
|
// 2) If the child is a pure virtual function, do not patch.
|
|
// In general, we want to proceed with patching when the symbol is
|
|
// externally defined because pad slots fall into this category.
|
|
// The pure virtual function symbol is special case, as the pure
|
|
// virtual property itself overrides the parent's implementation.
|
|
//
|
|
if (MachoIsSymbolNamePureVirtual (Name)) {
|
|
return TRUE;
|
|
}
|
|
//
|
|
// 3) If the symbols are the same, do not patch
|
|
//
|
|
Result = AsciiStrCmp (Name, ParentEntry->Name);
|
|
if (Result == 0) {
|
|
return TRUE;
|
|
}
|
|
//
|
|
// 4) If the parent vtable entry is a pad slot, and the child does not
|
|
// match it, then the child was built against a newer version of the
|
|
// libraries, so it is binary-incompatible.
|
|
//
|
|
if (MachoIsSymbolNamePadslot (ParentEntry->Name)) {
|
|
return FALSE;
|
|
}
|
|
//
|
|
// 5) If we are doing strict patching, we prevent kexts from declaring
|
|
// virtual functions and not implementing them. We can tell if a
|
|
// virtual function is declared but not implemented because we resolve
|
|
// symbols before patching; an unimplemented function will still be
|
|
// undefined at this point. We then look at whether the symbol has
|
|
// the same class prefix as the vtable. If it does, the symbol was
|
|
// declared as part of the class and not inherited, which means we
|
|
// should not patch it.
|
|
//
|
|
if (!MachoSymbolIsDefined (Symbol)) {
|
|
ClassName = MachoGetClassNameFromVtableName (VtableName);
|
|
|
|
Success = MachoGetFunctionPrefixFromClassName (
|
|
ClassName,
|
|
sizeof (FunctionPrefix),
|
|
FunctionPrefix
|
|
);
|
|
if (!Success) {
|
|
return FALSE;
|
|
}
|
|
|
|
Result = AsciiStrCmp (Name, FunctionPrefix);
|
|
if (Result == 0) {
|
|
//
|
|
// The VTable's class declares a method without providing an
|
|
// implementation.
|
|
//
|
|
ASSERT (FALSE);
|
|
return FALSE;
|
|
}
|
|
}
|
|
//
|
|
// 6) The child symbol is unresolved and different from its parent, so
|
|
// we need to patch it up. We do this by modifying the relocation
|
|
// entry of the vtable entry to point to the symbol of the parent
|
|
// vtable entry. If that symbol does not exist (i.e. we got the data
|
|
// from a link state object's vtable representation), then we create a
|
|
// new symbol in the symbol table and point the relocation entry to
|
|
// that.
|
|
//
|
|
// NOTE: The original logic has been altered significantly. Instead of
|
|
// declaring a symbol as "replaced" and either changing the
|
|
// associated relocation's index to the parent's or adding a new symbol
|
|
// based on a match, the symbol is actually overwritten. This looks
|
|
// fine for the rest of the control flow. The symbol name is not
|
|
// changed for the symbol value is already resolved and nothing but a
|
|
// VTable Relocation should reference it.
|
|
//
|
|
InternalSolveSymbolValue64 (ParentEntry->Address, Symbol);
|
|
//
|
|
// The C++ ABI requires that functions be aligned on a 2-byte boundary:
|
|
// http://www.codesourcery.com/public/cxx-abi/abi.html#member-pointers
|
|
// If the LSB of any virtual function's link address is 1, then the
|
|
// compiler has violated that part of the ABI, and we're going to panic
|
|
// in _ptmf2ptf() (in OSMetaClass.h). Better to panic here with some
|
|
// context.
|
|
//
|
|
Name = ParentEntry->Name;
|
|
ASSERT (MachoIsSymbolNamePureVirtual (Name) || ((Symbol->Value & 1U) == 0));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
STATIC
|
|
BOOLEAN
|
|
InternalInitializeVtableByEntriesAndRelocations64 (
|
|
IN CONST MACH_HEADER_64 *MachHeader,
|
|
IN CONST OC_SYMBOL_TABLE_64 *DefinedSymbols,
|
|
IN MACH_NLIST_64 *Symbols,
|
|
IN CONST CHAR8 *Strings,
|
|
IN CONST MACH_DYSYMTAB_COMMAND *DySymtab,
|
|
IN CONST OC_VTABLE *SuperVtable,
|
|
IN CONST MACH_NLIST_64 *VtableSymbol,
|
|
IN CONST UINT64 *VtableData,
|
|
OUT OC_VTABLE *Vtable
|
|
)
|
|
{
|
|
UINTN NumberOfEntries;
|
|
UINTN Index;
|
|
UINTN EntryOffset;
|
|
UINT64 EntryValue;
|
|
CONST OC_SYMBOL_TABLE_64 *SymbolsWalker;
|
|
CONST OC_SYMBOL_64 *OcSymbol;
|
|
CONST CHAR8 *StringTable;
|
|
MACH_NLIST_64 *Symbol;
|
|
CONST MACH_RELOCATION_INFO *Relocation;
|
|
BOOLEAN Result;
|
|
CONST CHAR8 *Name;
|
|
|
|
NumberOfEntries = 0;
|
|
EntryOffset = VTABLE_HEADER_LEN_64;
|
|
//
|
|
// Assumption: Not ARM (ARM requires an alignment to the function pointer
|
|
// retrieved from VtableData.
|
|
//
|
|
while (TRUE) {
|
|
EntryValue = VtableData[EntryOffset];
|
|
//
|
|
// If we can't find a symbol, it means it is a locally-defined,
|
|
// non-external symbol that has been stripped. We don't patch over
|
|
// locally-defined symbols, so we leave the symbol as NULL and just
|
|
// skip it. We won't be able to patch subclasses with this symbol,
|
|
// but there isn't much we can do about that.
|
|
//
|
|
if (EntryValue != 0) {
|
|
SymbolsWalker = DefinedSymbols;
|
|
|
|
do {
|
|
StringTable = SymbolsWalker->StringTable;
|
|
//
|
|
// Imported symbols are implicitely locally defined and hencefor do not
|
|
// need to be patched.
|
|
//
|
|
for (
|
|
Index = (
|
|
SymbolsWalker->NumberOfSymbols
|
|
- SymbolsWalker->NumberOfCxxSymbols
|
|
);
|
|
Index < SymbolsWalker->NumberOfSymbols;
|
|
++Index
|
|
) {
|
|
OcSymbol = &SymbolsWalker->Symbols[Index];
|
|
|
|
if (OcSymbol->Value == EntryValue) {
|
|
Name = (StringTable + OcSymbol->StringIndex);
|
|
Vtable->Entries[NumberOfEntries].Name = Name;
|
|
Vtable->Entries[NumberOfEntries].Address = OcSymbol->Value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Index == SymbolsWalker->NumberOfSymbols) {
|
|
ASSERT (FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
SymbolsWalker = GET_OC_SYMBOL_TABLE_64_FROM_LINK (
|
|
GetNextNode (
|
|
&DefinedSymbols->Link,
|
|
&SymbolsWalker->Link
|
|
)
|
|
);
|
|
} while (!IsNull (&DefinedSymbols->Link, &SymbolsWalker->Link));
|
|
} else if (NumberOfEntries < SuperVtable->NumberOfEntries) {
|
|
Relocation = (CONST MACH_RELOCATION_INFO *)(
|
|
(UINTN)MachHeader + DySymtab->ExternalRelocationsOffset
|
|
);
|
|
Relocation = MachoGetRelocationByOffset (
|
|
MachHeader,
|
|
DySymtab->NumberOfExternalRelocations,
|
|
Relocation,
|
|
(VtableSymbol->Value + EntryOffset)
|
|
);
|
|
if (Relocation != NULL) {
|
|
Symbol = &Symbols[Relocation->SymbolNumber];
|
|
Result = InternalPatchVtableSymbol (
|
|
MachHeader,
|
|
Symbols,
|
|
DySymtab,
|
|
Strings,
|
|
&SuperVtable->Entries[NumberOfEntries],
|
|
(Strings + VtableSymbol->UnifiedName.StringIndex),
|
|
Symbol
|
|
);
|
|
if (!Result) {
|
|
return FALSE;
|
|
}
|
|
|
|
Name = (Strings + Symbol->UnifiedName.StringIndex);
|
|
Vtable->Entries[NumberOfEntries].Name = Name;
|
|
Vtable->Entries[NumberOfEntries].Address = Symbol->Value;
|
|
} else {
|
|
//
|
|
// When the VTable entry is 0 and it is not referenced by a Relocation,
|
|
// it is the end of the table.
|
|
//
|
|
break;
|
|
}
|
|
}
|
|
|
|
++EntryOffset;
|
|
++NumberOfEntries;
|
|
}
|
|
|
|
Vtable->NumberOfEntries = NumberOfEntries;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
STATIC
|
|
VOID
|
|
InternalConstructVtablePrelinked64 (
|
|
IN CONST MACH_HEADER_64 *MachHeader,
|
|
IN CONST OC_SYMBOL_TABLE_64 *DefinedSymbols,
|
|
IN CONST MACH_NLIST_64 *VtableSymbol,
|
|
IN CONST CHAR8 *Strings,
|
|
OUT OC_VTABLE *Vtable
|
|
)
|
|
{
|
|
CONST MACH_SECTION_64 *Section;
|
|
CONST UINT64 *VtableData;
|
|
UINT64 Value;
|
|
CONST OC_SYMBOL_TABLE_64 *SymbolsWalker;
|
|
CONST CHAR8 *StringTable;
|
|
UINTN Index;
|
|
UINTN CxxIndex;
|
|
CONST OC_SYMBOL_64 *Symbol;
|
|
|
|
ASSERT (VtableSymbol != NULL);
|
|
ASSERT (Strings != NULL);
|
|
ASSERT (Vtable != NULL);
|
|
|
|
Section = MachoGetSectionByIndex64 (MachHeader, VtableSymbol->Section);
|
|
ASSERT (Section != NULL);
|
|
|
|
VtableData = (CONST UINT64 *)(
|
|
(UINTN)MachHeader
|
|
+ (VtableSymbol->Value - Section->Address)
|
|
+ Section->Offset
|
|
);
|
|
Vtable->Name = (Strings + VtableSymbol->UnifiedName.StringIndex);
|
|
//
|
|
// Initialize the VTable by entries.
|
|
//
|
|
SymbolsWalker = DefinedSymbols;
|
|
|
|
do {
|
|
StringTable = SymbolsWalker->StringTable;
|
|
CxxIndex = (
|
|
SymbolsWalker->NumberOfSymbols
|
|
- SymbolsWalker->NumberOfCxxSymbols
|
|
);
|
|
//
|
|
// Assumption: Not ARM (ARM requires an alignment to the function pointer
|
|
// retrieved from VtableData.
|
|
//
|
|
for (
|
|
Index = VTABLE_HEADER_LEN_64;
|
|
(Value = VtableData[Index]) != 0;
|
|
++Index
|
|
) {
|
|
for (
|
|
Index = CxxIndex;
|
|
Index < SymbolsWalker->NumberOfSymbols;
|
|
++Index
|
|
) {
|
|
Symbol = &SymbolsWalker->Symbols[Index];
|
|
|
|
if (Symbol->Value == Value) {
|
|
Vtable->Entries[Index].Address = Symbol->Value;
|
|
Vtable->Entries[Index].Name = (StringTable + Symbol->StringIndex);
|
|
}
|
|
}
|
|
//
|
|
// If we can't find the symbol, it means that the virtual function was
|
|
// defined inline. There's not much I can do about this; it just means
|
|
// I can't patch this function.
|
|
//
|
|
if (Index == SymbolsWalker->NumberOfCxxSymbols) {
|
|
Vtable->Entries[Index].Address = 0;
|
|
Vtable->Entries[Index].Name = NULL;
|
|
}
|
|
}
|
|
|
|
SymbolsWalker = GET_OC_SYMBOL_TABLE_64_FROM_LINK (
|
|
GetNextNode (
|
|
&DefinedSymbols->Link,
|
|
&SymbolsWalker->Link
|
|
)
|
|
);
|
|
} while (!IsNull (&DefinedSymbols->Link, &SymbolsWalker->Link));
|
|
}
|
|
|
|
STATIC
|
|
CONST OC_VTABLE *
|
|
InternalGetOcVtableByName (
|
|
IN CONST OC_VTABLE_ARRAY *Vtables,
|
|
IN CONST CHAR8 *Name
|
|
)
|
|
{
|
|
CONST OC_VTABLE_ARRAY *VtableWalker;
|
|
CONST OC_VTABLE *Vtable;
|
|
UINTN Index;
|
|
INTN Result;
|
|
|
|
VtableWalker = Vtables;
|
|
|
|
do {
|
|
Vtable = GET_FIRST_OC_VTABLE (VtableWalker);
|
|
|
|
for (Index = 0; Index < VtableWalker->NumberOfVtables; ++Index) {
|
|
Result = AsciiStrCmp (Vtable->Name, Name);
|
|
if (Result == 0) {
|
|
return Vtable;
|
|
}
|
|
|
|
Vtable = GET_NEXT_OC_VTABLE (Vtable);
|
|
}
|
|
|
|
VtableWalker = GET_OC_VTABLE_ARRAY_FROM_LINK (
|
|
GetNextNode (&Vtables->Link, &VtableWalker->Link)
|
|
);
|
|
} while (!IsNull (&Vtables->Link, &VtableWalker->Link));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
STATIC
|
|
BOOLEAN
|
|
InternalCreateVtablesNonPrelinked64 (
|
|
IN CONST MACH_HEADER_64 *MachHeader,
|
|
IN CONST OC_DEPENDENCY_DATA *DependencyData,
|
|
IN UINTN NumberOfVtables,
|
|
IN CONST MACH_NLIST_64 *VtableSymbols,
|
|
IN OUT MACH_NLIST_64 *SymbolTable,
|
|
IN CONST CHAR8 *StringTable,
|
|
IN UINTN NumberOfRelocations,
|
|
IN CONST MACH_RELOCATION_INFO *Relocations,
|
|
IN CONST MACH_DYSYMTAB_COMMAND *DySymtab,
|
|
IN UINTN NumberOfSymbols,
|
|
OUT OC_VTABLE *VtableBuffer
|
|
)
|
|
{
|
|
UINTN Index;
|
|
UINTN NumberOfPatchedTables;
|
|
BOOLEAN Result;
|
|
CONST MACH_NLIST_64 *SuperMetaclassPointer;
|
|
CONST MACH_NLIST_64 *VtableSymbol;
|
|
CONST MACH_NLIST_64 *MetaVtableSymbol;
|
|
CONST MACH_NLIST_64 *MetaClass;
|
|
CONST UINT64 *VtableData;
|
|
CONST OC_VTABLE *SuperVtable;
|
|
CONST OC_VTABLE *MetaVtable;
|
|
CONST VOID *SymbolDummy;
|
|
CHAR8 ClassName[SYM_MAX_NAME_LEN];
|
|
CHAR8 SuperClassName[SYM_MAX_NAME_LEN];
|
|
CHAR8 VtableName[SYM_MAX_NAME_LEN];
|
|
CHAR8 SuperVtableName[SYM_MAX_NAME_LEN];
|
|
CHAR8 FinalSymbolName[SYM_MAX_NAME_LEN];
|
|
BOOLEAN SuccessfulIteration;
|
|
|
|
NumberOfPatchedTables = 0;
|
|
|
|
while (NumberOfPatchedTables < NumberOfVtables) {
|
|
SuccessfulIteration = FALSE;
|
|
|
|
for (Index = 0; Index < NumberOfVtables; ++Index) {
|
|
SuperMetaclassPointer = &VtableSymbols[Index];
|
|
//
|
|
// We walk over the super metaclass pointer symbols because classes
|
|
// with them are the only ones that need patching. Then we double the
|
|
// number of vtables we're expecting, because every pointer will have a
|
|
// class vtable and a MetaClass vtable.
|
|
//
|
|
DEBUG_CODE (
|
|
Result = MachoSymbolIsSmcp64 (SuperMetaclassPointer, StringTable);
|
|
ASSERT (Result);
|
|
);
|
|
|
|
Result = MachoGetVtableSymbolsFromSmcp64 (
|
|
SymbolTable,
|
|
StringTable,
|
|
DySymtab,
|
|
SuperMetaclassPointer,
|
|
&VtableSymbol,
|
|
&MetaVtableSymbol
|
|
);
|
|
if (!Result) {
|
|
return FALSE;
|
|
}
|
|
//
|
|
// Get the class name from the smc pointer
|
|
//
|
|
Result = MachoGetClassNameFromSuperMetaClassPointer (
|
|
StringTable,
|
|
SuperMetaclassPointer,
|
|
sizeof (ClassName),
|
|
ClassName
|
|
);
|
|
if (!Result) {
|
|
return FALSE;
|
|
}
|
|
//
|
|
// Get the vtable name from the class name
|
|
//
|
|
Result = MachoGetVtableNameFromClassName (
|
|
ClassName,
|
|
sizeof (VtableName),
|
|
VtableName
|
|
);
|
|
if (!Result) {
|
|
return FALSE;
|
|
}
|
|
//
|
|
// Find the SMCP's meta class symbol
|
|
//
|
|
MetaClass = MachoGetMetaclassSymbolFromSmcpSymbol64 (
|
|
MachHeader,
|
|
NumberOfSymbols,
|
|
SymbolTable,
|
|
StringTable,
|
|
NumberOfRelocations,
|
|
Relocations,
|
|
SuperMetaclassPointer
|
|
);
|
|
if (MetaClass == NULL) {
|
|
return FALSE;
|
|
}
|
|
//
|
|
// Get the super class name from the super metaclass
|
|
//
|
|
Result = MachoGetClassNameFromMetaClassPointer (
|
|
StringTable,
|
|
MetaClass,
|
|
sizeof (SuperClassName),
|
|
SuperClassName
|
|
);
|
|
if (!Result) {
|
|
return FALSE;
|
|
}
|
|
//
|
|
// Get the super vtable if it's been patched
|
|
//
|
|
SuperVtable = InternalGetOcVtableByName (
|
|
DependencyData->Vtables,
|
|
SuperVtableName
|
|
);
|
|
if (SuperVtable == NULL) {
|
|
continue;
|
|
}
|
|
//
|
|
// Get the final symbol's name from the super vtable
|
|
//
|
|
Result = MachoGetFinalSymbolNameFromClassName (
|
|
SuperClassName,
|
|
sizeof (FinalSymbolName),
|
|
FinalSymbolName
|
|
);
|
|
if (!Result) {
|
|
return FALSE;
|
|
}
|
|
//
|
|
// Verify that the final symbol does not exist. First check
|
|
// all the externally defined symbols, then check locally.
|
|
//
|
|
SymbolDummy = InternalOcGetSymbolByName (
|
|
DependencyData->SymbolTable,
|
|
FinalSymbolName,
|
|
TRUE
|
|
);
|
|
if (SymbolDummy == NULL) {
|
|
SymbolDummy = MachoGetLocalDefinedSymbolByName (
|
|
SymbolTable,
|
|
StringTable,
|
|
DySymtab,
|
|
FinalSymbolName
|
|
);
|
|
}
|
|
if (SymbolDummy != NULL) {
|
|
ASSERT (FALSE);
|
|
continue;
|
|
}
|
|
//
|
|
// Patch the class's vtable
|
|
//
|
|
VtableData = (CONST UINT64 *)((UINTN)MachHeader + VtableSymbol->Value);
|
|
Result = InternalInitializeVtableByEntriesAndRelocations64 (
|
|
MachHeader,
|
|
DependencyData->SymbolTable,
|
|
SymbolTable,
|
|
StringTable,
|
|
DySymtab,
|
|
SuperVtable,
|
|
VtableSymbol,
|
|
VtableData,
|
|
VtableBuffer
|
|
);
|
|
if (!Result) {
|
|
return FALSE;
|
|
}
|
|
|
|
VtableBuffer->Name = (StringTable + VtableSymbol->UnifiedName.StringIndex);
|
|
//
|
|
// Add the class's vtable to the set of patched vtables
|
|
// This is done implicitely as we're operating on an array.
|
|
//
|
|
VtableBuffer = GET_NEXT_OC_VTABLE (VtableBuffer);
|
|
//
|
|
// Get the meta vtable name from the class name
|
|
//
|
|
Result = MachoGetMetaVtableNameFromClassName (
|
|
ClassName,
|
|
sizeof (VtableName),
|
|
VtableName
|
|
);
|
|
if (!Result) {
|
|
return FALSE;
|
|
}
|
|
|
|
MetaVtable = InternalGetOcVtableByName (
|
|
DependencyData->Vtables,
|
|
VtableName
|
|
);
|
|
if (MetaVtable != NULL) {
|
|
return FALSE;
|
|
}
|
|
//
|
|
// There is no way to look up a metaclass vtable at runtime, but
|
|
// we know that every class's metaclass inherits directly from
|
|
// OSMetaClass, so we just hardcode that vtable name here.
|
|
//
|
|
SuperVtable = InternalGetOcVtableByName (
|
|
DependencyData->Vtables,
|
|
OS_METACLASS_VTABLE_NAME
|
|
);
|
|
if (SuperVtable == NULL) {
|
|
return FALSE;
|
|
}
|
|
//
|
|
// meta_vtable_sym will be null when we don't support strict
|
|
// patching and can't find the metaclass vtable. If that's the
|
|
// case, we just reduce the expect number of vtables by 1.
|
|
// Only i386 does not support strict patchting.
|
|
//
|
|
VtableData = (CONST UINT64 *)(
|
|
(UINTN)MachHeader + MetaVtableSymbol->Value
|
|
);
|
|
Result = InternalInitializeVtableByEntriesAndRelocations64 (
|
|
MachHeader,
|
|
DependencyData->SymbolTable,
|
|
SymbolTable,
|
|
StringTable,
|
|
DySymtab,
|
|
SuperVtable,
|
|
MetaVtableSymbol,
|
|
VtableData,
|
|
VtableBuffer
|
|
);
|
|
if (!Result) {
|
|
return FALSE;
|
|
}
|
|
|
|
VtableBuffer->Name = (StringTable + MetaVtableSymbol->UnifiedName.StringIndex);
|
|
//
|
|
// Add the MetaClass's vtable to the set of patched vtables
|
|
// This is done implicitely as we're operating on an array.
|
|
//
|
|
VtableBuffer = GET_NEXT_OC_VTABLE (VtableBuffer);
|
|
|
|
++NumberOfPatchedTables;
|
|
SuccessfulIteration = TRUE;
|
|
}
|
|
//
|
|
// Exit when there are unpatched VTables left, but there are none patched
|
|
// in a full iteration.
|
|
//
|
|
if (!SuccessfulIteration) {
|
|
ASSERT (FALSE);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
STATIC
|
|
UINTN
|
|
InternalCreateVtablesPrelinked64 (
|
|
IN CONST MACH_HEADER_64 *MachHeader,
|
|
IN CONST OC_SYMBOL_TABLE_64 *DefinedSymbols,
|
|
OUT OC_VTABLE *VtableBuffer
|
|
)
|
|
{
|
|
UINTN NumberOfVtables;
|
|
|
|
CONST MACH_SYMTAB_COMMAND *Symtab;
|
|
CONST CHAR8 *Strings;
|
|
CONST MACH_NLIST_64 *Symbol;
|
|
UINTN Index;
|
|
|
|
ASSERT (MachHeader != NULL);
|
|
ASSERT (MachHeader->Signature == MACH_HEADER_64_SIGNATURE);
|
|
ASSERT (DefinedSymbols != NULL);
|
|
|
|
NumberOfVtables = 0;
|
|
|
|
Symtab = (CONST MACH_SYMTAB_COMMAND *)(
|
|
MachoGetFirstCommand64 (MachHeader, MACH_LOAD_COMMAND_SYMTAB)
|
|
);
|
|
ASSERT (Symtab != NULL);
|
|
|
|
Strings = (CONST CHAR8 *)((UINTN)MachHeader + Symtab->StringsOffset);
|
|
Symbol = (CONST MACH_NLIST_64 *)((UINTN)MachHeader + Symtab->SymbolsOffset);
|
|
|
|
for (Index = 0; Index < Symtab->NumberOfSymbols; ++Index) {
|
|
if (MachoSymbolIsVtable64 (Symbol, Strings)) {
|
|
InternalConstructVtablePrelinked64 (
|
|
MachHeader,
|
|
DefinedSymbols,
|
|
Symbol,
|
|
Strings,
|
|
VtableBuffer
|
|
);
|
|
VtableBuffer = GET_NEXT_OC_VTABLE (VtableBuffer);
|
|
|
|
++NumberOfVtables;
|
|
}
|
|
|
|
++Symbol;
|
|
}
|
|
|
|
return NumberOfVtables;
|
|
}
|
|
|
|
//
|
|
// 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] MachHeader MACH-O header of the KEXT to relocate.
|
|
@param[in] LinkAddress The address to be linked against.
|
|
@param[in] Vtables List of all dependent VTables.
|
|
@param[in] NumberOfSymbols The number of symbols in SymbolTable.
|
|
@param[in] SymbolTable The KEXT's Symbol Table.
|
|
@param[in] StringTable The KEXT's String Table.
|
|
@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.
|
|
|
|
@retval FALSE The Relocation does not need to be preseved.
|
|
@retval TRUE The Relocation must be preseved.
|
|
|
|
**/
|
|
STATIC
|
|
BOOLEAN
|
|
InternalCalculateTargetsIntel64 (
|
|
IN CONST MACH_HEADER_64 *MachHeader,
|
|
IN UINT64 LinkAddress,
|
|
IN CONST OC_VTABLE_ARRAY *Vtables,
|
|
IN UINTN NumberOfSymbols,
|
|
IN CONST MACH_NLIST_64 *SymbolTable,
|
|
IN CONST CHAR8 *StringTable,
|
|
IN CONST MACH_RELOCATION_INFO *Relocation,
|
|
IN CONST MACH_RELOCATION_INFO *NextRelocation OPTIONAL,
|
|
OUT UINT64 *Target,
|
|
OUT UINT64 *PairTarget,
|
|
OUT CONST OC_VTABLE **Vtable OPTIONAL
|
|
)
|
|
{
|
|
BOOLEAN Success;
|
|
|
|
UINT64 TargetAddress;
|
|
CONST MACH_NLIST_64 *Symbol;
|
|
CONST CHAR8 *Name;
|
|
CONST MACH_SECTION_64 *Section;
|
|
UINT64 PairAddress;
|
|
UINT64 PairDummy;
|
|
UINTN Index;
|
|
CONST OC_VTABLE_ARRAY *VtablesWalker;
|
|
INTN Result;
|
|
CONST OC_VTABLE *VtableWalker;
|
|
|
|
ASSERT (Target != NULL);
|
|
ASSERT (PairTarget != NULL);
|
|
|
|
Section = NULL;
|
|
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 LinkAddress aligned on the
|
|
// section's boundary.
|
|
//
|
|
TargetAddress = LinkAddress;
|
|
//
|
|
// Scattered Relocations are only supported by i386.
|
|
//
|
|
ASSERT (((UINT32)Relocation->Address & MACH_RELOC_SCATTERED) == 0);
|
|
|
|
if (Relocation->Extern != 0) {
|
|
ASSERT (Relocation->SymbolNumber < NumberOfSymbols);
|
|
|
|
Symbol = &SymbolTable[Relocation->SymbolNumber];
|
|
Name = (StringTable + Symbol->UnifiedName.StringIndex);
|
|
//
|
|
// 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.
|
|
//
|
|
if (MachoIsSymbolNamePadslot (Name)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if ((Vtable != NULL) && MachoSymbolIsVtable64 (Symbol, StringTable)) {
|
|
VtablesWalker = Vtables;
|
|
VtableWalker = GET_FIRST_OC_VTABLE (VtablesWalker);
|
|
|
|
do {
|
|
for (Index = 0; Index < VtablesWalker->NumberOfVtables; ++Index) {
|
|
Result = AsciiStrCmp (VtableWalker->Name, Name);
|
|
if (Result == 0) {
|
|
*Vtable = VtableWalker;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Index != VtablesWalker->NumberOfVtables) {
|
|
break;
|
|
}
|
|
|
|
VtablesWalker = GET_OC_VTABLE_ARRAY_FROM_LINK (
|
|
GetNextNode (
|
|
&Vtables->Link,
|
|
&VtablesWalker->Link
|
|
)
|
|
);
|
|
if (IsNull (&Vtables->Link, &VtablesWalker->Link)) {
|
|
ASSERT (FALSE);
|
|
break;
|
|
}
|
|
|
|
VtableWalker = GET_NEXT_OC_VTABLE (VtableWalker);
|
|
} while (TRUE);
|
|
}
|
|
|
|
TargetAddress = Symbol->Value;
|
|
} else {
|
|
Section = MachoGetSectionByIndex64 (MachHeader, Relocation->SymbolNumber);
|
|
ASSERT (Section != NULL);
|
|
}
|
|
|
|
if (Section != NULL) {
|
|
TargetAddress = ALIGN_VALUE (
|
|
(Section->Address + LinkAddress),
|
|
Section->Alignment
|
|
);
|
|
TargetAddress -= Section->Address;
|
|
}
|
|
|
|
if (MachoRelocationIsPairIntel64 (Relocation->Type)) {
|
|
ASSERT (NextRelocation != NULL);
|
|
ASSERT (MachoIsRelocationPairTypeIntel64 (NextRelocation->Type));
|
|
//
|
|
// 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 (
|
|
MachHeader,
|
|
LinkAddress,
|
|
Vtables,
|
|
NumberOfSymbols,
|
|
SymbolTable,
|
|
StringTable,
|
|
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 OC_VTABLE *Vtable,
|
|
IN UINT64 Offset
|
|
)
|
|
{
|
|
UINTN Index;
|
|
CONST OC_VTABLE_ENTRY *Entry;
|
|
|
|
if ((Offset % sizeof (UINT64)) != 0) {
|
|
ASSERT (FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
Index = ((Offset - VTABLE_ENTRY_SIZE_64) / sizeof (UINT64));
|
|
Entry = &Vtable->Entries[Index];
|
|
|
|
if ((Index >= Vtable->NumberOfEntries) || (Entry->Name == NULL)) {
|
|
return FALSE;
|
|
}
|
|
|
|
return MachoIsSymbolNamePureVirtual (Entry->Name);
|
|
}
|
|
|
|
/**
|
|
Relocates Relocation against the specified Symtab's Symbol Table and
|
|
LinkAddress. This logically matches KXLD's x86_64_process_reloc.
|
|
|
|
@param[in] MachHeader MACH-O header of the KEXT to relocate.
|
|
@param[in] LinkAddress The address to be linked against.
|
|
@param[in] Vtables List of all dependent VTables.
|
|
@param[in] NumberOfSymbols The number of symbols in SymbolTable.
|
|
@param[in] SymbolTable The KEXT's Symbol Table.
|
|
@param[in] StringTable The KEXT's String Table.
|
|
@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 CONST MACH_HEADER_64 *MachHeader,
|
|
IN UINT64 LinkAddress,
|
|
IN CONST OC_VTABLE_ARRAY *Vtables,
|
|
IN UINTN NumberOfSymbols,
|
|
IN CONST MACH_NLIST_64 *SymbolTable,
|
|
IN CONST CHAR8 *StringTable,
|
|
IN UINTN RelocationBase,
|
|
IN CONST MACH_RELOCATION_INFO *Relocation,
|
|
IN CONST MACH_RELOCATION_INFO *NextRelocation OPTIONAL
|
|
)
|
|
{
|
|
UINTN ReturnValue;
|
|
|
|
UINT8 Type;
|
|
INT32 *Instruction32Ptr;
|
|
INT32 Instruction32;
|
|
UINT64 *Instruction64Ptr;
|
|
UINT64 Instruction64;
|
|
UINT64 Target;
|
|
BOOLEAN IsPair;
|
|
UINT64 PairTarget;
|
|
CONST OC_VTABLE *Vtable;
|
|
UINT64 LinkPc;
|
|
UINT64 Adjustment;
|
|
UINTN Length;
|
|
UINT32 Address;
|
|
UINT8 *InstructionPtr;
|
|
BOOLEAN PcRelative;
|
|
BOOLEAN IsNormalLocal;
|
|
BOOLEAN Result;
|
|
|
|
ASSERT (RelocationBase != 0);
|
|
ASSERT (Relocation != NULL);
|
|
//
|
|
// Scattered Relocations are only supported by i386.
|
|
//
|
|
ASSERT (((UINT32)Relocation->Address & MACH_RELOC_SCATTERED) == 0);
|
|
|
|
IsPair = FALSE;
|
|
Adjustment = 0;
|
|
IsNormalLocal = FALSE;
|
|
|
|
if ((Relocation->Extern == 0)
|
|
&& (Relocation->SymbolNumber == MACH_RELOC_ABSOLUTE)) {
|
|
//
|
|
// A section-based relocation entry can be skipped for absolute
|
|
// symbols.
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
Address = Relocation->Address;
|
|
Length = Relocation->Size;
|
|
Type = Relocation->Type;
|
|
PcRelative = (Relocation->PcRelative != 0);
|
|
|
|
if (Relocation->Extern == 0) {
|
|
IsNormalLocal = TRUE;
|
|
}
|
|
|
|
ASSERT ((Length == 2) || (Length == 3));
|
|
|
|
LinkPc = (Address + LinkAddress);
|
|
InstructionPtr = (UINT8 *)(RelocationBase + Address);
|
|
|
|
Vtable = NULL;
|
|
Result = InternalCalculateTargetsIntel64 (
|
|
MachHeader,
|
|
LinkAddress,
|
|
Vtables,
|
|
NumberOfSymbols,
|
|
SymbolTable,
|
|
StringTable,
|
|
Relocation,
|
|
NextRelocation,
|
|
&Target,
|
|
&PairTarget,
|
|
&Vtable
|
|
);
|
|
if (!Result) {
|
|
return MAX_UINTN;
|
|
}
|
|
|
|
if (Length == 2) {
|
|
Instruction32Ptr = (INT32 *)InstructionPtr;
|
|
Instruction32 = *Instruction32Ptr;
|
|
|
|
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 = (1ULL << 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:
|
|
{
|
|
ASSERT (PcRelative);
|
|
Adjustment += LinkPc;
|
|
break;
|
|
}
|
|
|
|
case MachX8664RelocSigned:
|
|
case MachX8664RelocSigned1:
|
|
case MachX8664RelocSigned2:
|
|
case MachX8664RelocSigned4:
|
|
{
|
|
ASSERT (PcRelative);
|
|
Adjustment += (IsNormalLocal ? LinkAddress : LinkPc);
|
|
break;
|
|
}
|
|
|
|
case MachX8664RelocGot:
|
|
case MachX8664RelocGotLoad:
|
|
{
|
|
ASSERT (PcRelative);
|
|
Adjustment += LinkPc;
|
|
Target = PairTarget;
|
|
IsPair = TRUE;
|
|
break;
|
|
}
|
|
|
|
case MachX8664RelocSubtractor:
|
|
{
|
|
ASSERT (!PcRelative);
|
|
Instruction32 = (INT32)(Target - PairTarget);
|
|
IsPair = TRUE;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (PcRelative) {
|
|
Result = InternalCalculateDisplacementIntel64 (
|
|
Target,
|
|
Adjustment,
|
|
&Instruction32
|
|
);
|
|
if (!Result) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
*Instruction32Ptr = Instruction32;
|
|
} else {
|
|
Instruction64Ptr = (UINT64 *)InstructionPtr;
|
|
Instruction64 = *Instruction64Ptr;
|
|
|
|
if ((Vtable != NULL)
|
|
&& InternalIsDirectPureVirtualCall64 (Vtable, Instruction64)) {
|
|
return MAX_UINTN;
|
|
}
|
|
|
|
switch (Type) {
|
|
case MachX8664RelocUnsigned:
|
|
{
|
|
ASSERT (!PcRelative);
|
|
Instruction64 += Target;
|
|
break;
|
|
}
|
|
|
|
case MachX8664RelocSubtractor:
|
|
{
|
|
ASSERT (!PcRelative);
|
|
Instruction64 = (Target - PairTarget);
|
|
IsPair = TRUE;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
*Instruction64Ptr = Instruction64;
|
|
}
|
|
|
|
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] MachHeader MACH-O header of the KEXT to relocate.
|
|
@param[in] LinkAddress The address to be linked against.
|
|
@param[in] Vtables The patched VTables of this KEXT and its
|
|
dependencies.
|
|
@param[in] NumberOfSymbols The number of symbols in SymbolTable.
|
|
@param[in] SymbolTable The KEXT's Symbol Table.
|
|
@param[in] StringTable The KEXT's String Table.
|
|
@param[in] RelocationBase The Relocations base address.
|
|
@param[in] SourceRelocations The Relocations source buffer.
|
|
@param[in] NumberOfRelocations 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 CONST MACH_HEADER_64 *MachHeader,
|
|
IN UINT64 LinkAddress,
|
|
IN CONST OC_VTABLE_ARRAY *Vtables,
|
|
IN UINTN NumberOfSymbols,
|
|
IN CONST MACH_NLIST_64 *SymbolTable,
|
|
IN CONST CHAR8 *StringTable,
|
|
IN UINTN RelocationBase,
|
|
IN CONST MACH_RELOCATION_INFO *SourceRelocations,
|
|
IN OUT UINTN *NumberOfRelocations,
|
|
OUT MACH_RELOCATION_INFO *TargetRelocations
|
|
)
|
|
{
|
|
UINT32 PreservedRelocations;
|
|
|
|
UINTN Index;
|
|
CONST MACH_RELOCATION_INFO *NextRelocation;
|
|
UINTN Result;
|
|
MACH_RELOCATION_INFO *Relocation;
|
|
|
|
ASSERT (NumberOfSymbols > 0);
|
|
ASSERT (SymbolTable != NULL);
|
|
ASSERT (StringTable != NULL);
|
|
ASSERT (RelocationBase != 0);
|
|
ASSERT (SourceRelocations != NULL);
|
|
ASSERT (NumberOfRelocations != NULL);
|
|
ASSERT (NumberOfRelocations > 0);
|
|
ASSERT (TargetRelocations != NULL);
|
|
|
|
PreservedRelocations = 0;
|
|
|
|
for (Index = 0; Index < *NumberOfRelocations; ++Index) {
|
|
NextRelocation = &SourceRelocations[Index + 1];
|
|
//
|
|
// The last Relocation does not have a successor.
|
|
//
|
|
if (Index == (*NumberOfRelocations - 1)) {
|
|
NextRelocation = NULL;
|
|
}
|
|
//
|
|
// Relocate the relocation.
|
|
//
|
|
Result = InternalRelocateRelocationIntel64 (
|
|
MachHeader,
|
|
LinkAddress,
|
|
Vtables,
|
|
NumberOfSymbols,
|
|
SymbolTable,
|
|
StringTable,
|
|
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];
|
|
//
|
|
// Scattered Relocations are only supported by i386.
|
|
//
|
|
ASSERT (((UINT32)Relocation->Address & MACH_RELOC_SCATTERED) == 0);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
*NumberOfRelocations = PreservedRelocations;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// MACH header
|
|
//
|
|
|
|
/**
|
|
Strip superfluous Load Commands from the MACH-O header. This includes the
|
|
Code Signature Load Command which must be removed for the binary has been
|
|
modified by the prelinking routines.
|
|
|
|
@param[in,out] MachHeader MACH-O header to strip the Load Commands from.
|
|
|
|
**/
|
|
STATIC
|
|
VOID
|
|
InternalStripLoadCommands64 (
|
|
IN OUT MACH_HEADER_64 *MachHeader
|
|
)
|
|
{
|
|
STATIC CONST MACH_LOAD_COMMAND_TYPE LoadCommandsToStrip[] = {
|
|
MACH_LOAD_COMMAND_CODE_SIGNATURE,
|
|
MACH_LOAD_COMMAND_DYLD_INFO,
|
|
MACH_LOAD_COMMAND_DYLD_INFO_ONLY,
|
|
MACH_LOAD_COMMAND_FUNCTION_STARTS,
|
|
MACH_LOAD_COMMAND_DATA_IN_CODE,
|
|
MACH_LOAD_COMMAND_DYLIB_CODE_SIGN_DRS
|
|
};
|
|
|
|
UINTN Index;
|
|
UINTN Index2;
|
|
MACH_LOAD_COMMAND *LoadCommand;
|
|
UINTN SizeOfLeftCommands;
|
|
//
|
|
// Delete the Code Signature Load Command if existent as we modified the
|
|
// binary, as well as linker metadata not needed for runtime operation.
|
|
//
|
|
LoadCommand = MachHeader->Commands;
|
|
SizeOfLeftCommands = MachHeader->CommandsSize;
|
|
|
|
for (Index = 0; Index < MachHeader->NumberOfCommands; ++Index) {
|
|
//
|
|
// LC_UNIXTHREAD and LC_MAIN are technically stripped in KXLD, but they are
|
|
// not supposed to be present in the first place.
|
|
//
|
|
ASSERT ((LoadCommand->Type != MACH_LOAD_COMMAND_UNIX_THREAD)
|
|
&& (LoadCommand->Type != MACH_LOAD_COMMAND_MAIN));
|
|
|
|
SizeOfLeftCommands -= LoadCommand->Size;
|
|
|
|
for (Index2 = 0; Index < ARRAY_SIZE (LoadCommandsToStrip); ++Index2) {
|
|
if (LoadCommand->Type == LoadCommandsToStrip[Index2]) {
|
|
if (Index != (MachHeader->NumberOfCommands - 1)) {
|
|
//
|
|
// If the current Load Command is not the last one, relocate the
|
|
// subsequent ones.
|
|
//
|
|
CopyMem (
|
|
LoadCommand,
|
|
NEXT_MACH_LOAD_COMMAND (LoadCommand),
|
|
SizeOfLeftCommands
|
|
);
|
|
}
|
|
|
|
--MachHeader->NumberOfCommands;
|
|
MachHeader->CommandsSize -= LoadCommand->Size;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
LoadCommand = NEXT_MACH_LOAD_COMMAND (LoadCommand);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Prelinks the specified KEXT against the specified LinkAddress and the data
|
|
of its dependencies.
|
|
|
|
@param[in,out] MachHeader MACH-O header of the KEXT to prelink.
|
|
@param[in] LinkAddress The address this KEXT shall be linked against.
|
|
@param[in] DependencyData List of data of all dependencies.
|
|
@param[in] NumberOfVtables Number of entries in VtableSymbols.
|
|
@param[in] VtableSymbols List of VTable symbols in this KEXT.
|
|
@param[out] OutSymbols Buffer to output external symbols to.
|
|
@param[out] OutVtables Buffer to output patched vtables into.
|
|
|
|
@retval Returned is whether the prelinking process has been successful.
|
|
The state of the KEXT is undefined in case this routine fails.
|
|
|
|
**/
|
|
STATIC
|
|
BOOLEAN
|
|
InternalPrelinkKext64 (
|
|
IN OUT MACH_HEADER_64 *MachHeader,
|
|
IN UINT64 LinkAddress,
|
|
IN OC_DEPENDENCY_DATA *DependencyData,
|
|
IN UINTN NumberOfVtables,
|
|
IN MACH_NLIST_64 *VtableSymbols,
|
|
OUT OC_SYMBOL_TABLE_64 *OutSymbols,
|
|
OUT OC_VTABLE *OutVtables
|
|
)
|
|
{
|
|
MACH_SYMTAB_COMMAND *Symtab;
|
|
MACH_DYSYMTAB_COMMAND *DySymtab;
|
|
|
|
MACH_SEGMENT_COMMAND_64 *Segment;
|
|
MACH_SECTION_64 *Section;
|
|
|
|
UINTN Index;
|
|
BOOLEAN Result;
|
|
UINTN Size;
|
|
|
|
MACH_NLIST_64 *Symbol;
|
|
MACH_NLIST_64 *SymbolTable;
|
|
CONST CHAR8 *StringTable;
|
|
UINTN NumberOfSymbols;
|
|
|
|
UINTN NumberOfRelocations;
|
|
UINTN NumberOfRelocations2;
|
|
UINTN RelocationBase;
|
|
CONST MACH_RELOCATION_INFO *Relocations;
|
|
MACH_RELOCATION_INFO *TargetRelocation;
|
|
MACH_SEGMENT_COMMAND_64 *FirstSegment;
|
|
|
|
VOID *LinkEdit;
|
|
UINTN SymbolTableOffset;
|
|
UINTN SymbolTableSize;
|
|
UINTN RelocationsOffset;
|
|
UINTN RelocationsSize;
|
|
UINTN StringTableOffset;
|
|
UINTN StringTableSize;
|
|
|
|
ASSERT (MachHeader != NULL);
|
|
ASSERT (MachHeader->Signature == MACH_HEADER_64_SIGNATURE);
|
|
ASSERT (LinkAddress != 0);
|
|
ASSERT (DependencyData != NULL);
|
|
ASSERT (DependencyData->SymbolTable != NULL);
|
|
ASSERT (DependencyData->Vtables != NULL);
|
|
//
|
|
// Only perform actions when the kext is flag'd to be dynamically linked.
|
|
//
|
|
if ((MachHeader->Flags & MACH_HEADER_FLAG_DYNAMIC_LINKER_LINK) == 0) {
|
|
ASSERT (FALSE);
|
|
return FALSE;
|
|
}
|
|
//
|
|
// Strip superfluous Load Commands.
|
|
//
|
|
InternalStripLoadCommands64 (MachHeader);
|
|
//
|
|
// Retrieve the symbol tables required for most following operations.
|
|
//
|
|
DySymtab = (MACH_DYSYMTAB_COMMAND *)(
|
|
MachoGetFirstCommand64 (MachHeader, MACH_LOAD_COMMAND_DYSYMTAB)
|
|
);
|
|
ASSERT (DySymtab != NULL);
|
|
|
|
Symtab = (MACH_SYMTAB_COMMAND *)(
|
|
MachoGetFirstCommand64 (MachHeader, MACH_LOAD_COMMAND_SYMTAB)
|
|
);
|
|
ASSERT (Symtab != NULL);
|
|
|
|
StringTable = (CONST CHAR8 *)((UINTN)MachHeader + Symtab->StringsOffset);
|
|
//
|
|
// Solve indirect symbols.
|
|
//
|
|
SymbolTable = (MACH_NLIST_64 *)(
|
|
(UINTN)MachHeader + DySymtab->IndirectSymbolsOffset
|
|
);
|
|
for (Index = 0; Index < DySymtab->NumberOfIndirectSymbols; ++Index) {
|
|
Symbol = &SymbolTable[DySymtab->IndirectSymbolsOffset + Index];
|
|
//
|
|
// Indirect symbols are solved via their value as a String Table index.
|
|
//
|
|
Result = InternalSolveSymbol64 (
|
|
DependencyData->SymbolTable,
|
|
SymbolTable,
|
|
StringTable,
|
|
DySymtab,
|
|
(StringTable + Symbol->Value),
|
|
&SymbolTable[Index]
|
|
);
|
|
if (!Result) {
|
|
ASSERT (FALSE);
|
|
return FALSE;
|
|
}
|
|
}
|
|
//
|
|
// Solve undefined symbols.
|
|
//
|
|
SymbolTable = (MACH_NLIST_64 *)((UINTN)MachHeader + Symtab->SymbolsOffset);
|
|
|
|
for (Index = 0; Index < DySymtab->NumberOfUndefinedSymbols; ++Index) {
|
|
Symbol = &SymbolTable[DySymtab->UndefinedSymbolsIndex + Index];
|
|
//
|
|
// Undefined symbols are solved via their name.
|
|
//
|
|
Result = InternalSolveSymbol64 (
|
|
DependencyData->SymbolTable,
|
|
SymbolTable,
|
|
StringTable,
|
|
DySymtab,
|
|
(StringTable + Symbol->UnifiedName.StringIndex),
|
|
Symbol
|
|
);
|
|
if (!Result) {
|
|
ASSERT (FALSE);
|
|
return FALSE;
|
|
}
|
|
}
|
|
//
|
|
// Create and patch the KEXT's VTables.
|
|
//
|
|
Relocations = (CONST MACH_RELOCATION_INFO *)(
|
|
(UINTN)MachHeader + DySymtab->ExternalRelocationsOffset
|
|
);
|
|
Result = InternalCreateVtablesNonPrelinked64 (
|
|
MachHeader,
|
|
DependencyData,
|
|
NumberOfVtables,
|
|
VtableSymbols,
|
|
SymbolTable,
|
|
StringTable,
|
|
DySymtab->NumberOfExternalRelocations,
|
|
Relocations,
|
|
DySymtab,
|
|
Symtab->NumberOfSymbols,
|
|
OutVtables
|
|
);
|
|
if (!Result) {
|
|
return FALSE;
|
|
}
|
|
//
|
|
// Relocate local and external symbols.
|
|
//
|
|
for (Index = 0; Index < DySymtab->NumberOfLocalSymbols; ++Index) {
|
|
Result = MachoRelocateSymbol64 (
|
|
MachHeader,
|
|
LinkAddress,
|
|
&SymbolTable[DySymtab->LocalSymbolsIndex + Index]
|
|
);
|
|
if (!Result) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
for (Index = 0; Index < DySymtab->NumberOfExternalSymbols; ++Index) {
|
|
Result = MachoRelocateSymbol64 (
|
|
MachHeader,
|
|
LinkAddress,
|
|
&SymbolTable[DySymtab->ExternalSymbolsIndex + Index]
|
|
);
|
|
if (!Result) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
//
|
|
// 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
|
|
//
|
|
NumberOfSymbols = Symtab->NumberOfSymbols;
|
|
NumberOfSymbols -= DySymtab->NumberOfUndefinedSymbols;
|
|
SymbolTableOffset = 0;
|
|
SymbolTableSize = (NumberOfSymbols * sizeof (MACH_NLIST_64));
|
|
RelocationsOffset = (SymbolTableOffset + SymbolTableSize);
|
|
StringTableSize = Symtab->StringsSize;
|
|
//
|
|
// For the allocation, assume all relocations will be preserved to simplify
|
|
// the code, the memory is only temporarily allocated anyway.
|
|
//
|
|
NumberOfRelocations = DySymtab->NumberOfLocalRelocations;
|
|
NumberOfRelocations += DySymtab->NumberOfExternalRelocations;
|
|
RelocationsSize = (NumberOfRelocations * sizeof (MACH_RELOCATION_INFO));
|
|
|
|
LinkEdit = AllocatePool (SymbolTableSize + RelocationsSize + StringTableSize);
|
|
if (LinkEdit == NULL) {
|
|
ASSERT (FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
FirstSegment = MachoGetFirstSegment64 (MachHeader);
|
|
//
|
|
// 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
|
|
);
|
|
RelocationBase = ((UINTN)MachHeader + FirstSegment->FileOffset);
|
|
//
|
|
// Relocate and copy local and external relocations.
|
|
//
|
|
Relocations = (CONST MACH_RELOCATION_INFO *)(
|
|
(UINTN)MachHeader + DySymtab->LocalRelocationsOffset
|
|
);
|
|
NumberOfRelocations = DySymtab->NumberOfLocalRelocations;
|
|
Result = InternalRelocateAndCopyRelocations64 (
|
|
MachHeader,
|
|
LinkAddress,
|
|
DependencyData->Vtables,
|
|
Symtab->NumberOfSymbols,
|
|
SymbolTable,
|
|
StringTable,
|
|
RelocationBase,
|
|
Relocations,
|
|
&NumberOfRelocations,
|
|
&TargetRelocation[0]
|
|
);
|
|
if (!Result) {
|
|
ASSERT (FALSE);
|
|
FreePool (LinkEdit);
|
|
return FALSE;
|
|
}
|
|
|
|
Relocations = (MACH_RELOCATION_INFO *)(
|
|
(UINTN)MachHeader + DySymtab->ExternalRelocationsOffset
|
|
);
|
|
NumberOfRelocations2 = DySymtab->NumberOfExternalRelocations;
|
|
Result = InternalRelocateAndCopyRelocations64 (
|
|
MachHeader,
|
|
LinkAddress,
|
|
DependencyData->Vtables,
|
|
Symtab->NumberOfSymbols,
|
|
SymbolTable,
|
|
StringTable,
|
|
RelocationBase,
|
|
Relocations,
|
|
&NumberOfRelocations2,
|
|
&TargetRelocation[NumberOfRelocations]
|
|
);
|
|
if (!Result) {
|
|
ASSERT (FALSE);
|
|
FreePool (LinkEdit);
|
|
return FALSE;
|
|
}
|
|
NumberOfRelocations += NumberOfRelocations2;
|
|
//
|
|
// Expose the external Symbol Table if requested.
|
|
//
|
|
if (OutSymbols != NULL) {
|
|
InternalFillSymbolTable64 (
|
|
DySymtab->NumberOfExternalSymbols,
|
|
&SymbolTable[DySymtab->ExternalSymbolsIndex],
|
|
StringTable,
|
|
OutSymbols
|
|
);
|
|
}
|
|
//
|
|
// Copy the entire symbol table excluding the area for undefined symbols.
|
|
//
|
|
Size = (DySymtab->UndefinedSymbolsIndex * sizeof (MACH_NLIST_64));
|
|
|
|
if (DySymtab->UndefinedSymbolsIndex != 0) {
|
|
CopyMem (
|
|
(VOID *)((UINTN)LinkEdit + SymbolTableOffset),
|
|
(VOID *)&SymbolTable[0],
|
|
Size
|
|
);
|
|
}
|
|
|
|
NumberOfSymbols = DySymtab->UndefinedSymbolsIndex;
|
|
NumberOfSymbols += DySymtab->NumberOfUndefinedSymbols;
|
|
if (NumberOfSymbols != Symtab->NumberOfSymbols) {
|
|
CopyMem (
|
|
(VOID *)((UINTN)LinkEdit + SymbolTableOffset + Size),
|
|
(VOID *)&SymbolTable[NumberOfSymbols],
|
|
((Symtab->NumberOfSymbols - NumberOfSymbols) * sizeof (MACH_NLIST_64))
|
|
);
|
|
}
|
|
|
|
RelocationsSize = (NumberOfRelocations * sizeof (MACH_RELOCATION_INFO));
|
|
//
|
|
// 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 = SymbolTableOffset;
|
|
Symtab->NumberOfSymbols -= DySymtab->NumberOfUndefinedSymbols;
|
|
Symtab->StringsOffset = StringTableOffset;
|
|
|
|
DySymtab->LocalRelocationsOffset = RelocationsOffset;
|
|
DySymtab->NumberOfLocalRelocations = NumberOfRelocations;
|
|
//
|
|
// Clear dynamic linker information.
|
|
//
|
|
DySymtab->LocalSymbolsIndex = 0;
|
|
DySymtab->NumberOfLocalSymbols = 0;
|
|
DySymtab->ExternalSymbolsIndex = 0;
|
|
DySymtab->NumberOfExternalRelocations = 0;
|
|
DySymtab->UndefinedSymbolsIndex = 0;
|
|
DySymtab->NumberOfUndefinedSymbols = 0;
|
|
DySymtab->IndirectSymbolsOffset = 0;
|
|
DySymtab->NumberOfIndirectSymbols = 0;
|
|
//
|
|
// Copy the new __LINKEDIT segment into the binary and fix its Load Command.
|
|
//
|
|
Segment = MachoGetSegmentByName64 (MachHeader, "__LINKEDIT");
|
|
ASSERT (Segment != NULL);
|
|
|
|
Segment->FileSize = (SymbolTableSize + RelocationsSize + StringTableSize);
|
|
Segment->Size = ALIGN_VALUE (Segment->FileSize, BASE_4KB);
|
|
CopyMem (
|
|
(VOID *)((UINTN)MachHeader + Segment->FileOffset),
|
|
LinkEdit,
|
|
Segment->FileSize
|
|
);
|
|
FreePool (LinkEdit);
|
|
//
|
|
// Adapt the link addresses of all Segments and their Sections.
|
|
//
|
|
for (
|
|
Segment = FirstSegment;
|
|
Segment != NULL;
|
|
Segment = MachoGetNextSegment64 (MachHeader, Segment)
|
|
) {
|
|
Section = Segment->Sections;
|
|
|
|
for (Index = 0; Index < Segment->NumberOfSections; ++Index) {
|
|
Section->Address = ALIGN_VALUE (
|
|
(Section->Address + LinkAddress),
|
|
Section->Alignment
|
|
);
|
|
++Section;
|
|
}
|
|
|
|
Segment->VirtualAddress += LinkAddress;
|
|
}
|
|
//
|
|
// Adapt the MACH-O header to signal being prelinked.
|
|
//
|
|
MachHeader->Flags = MACH_HEADER_FLAG_NO_UNDEFINED_REFERENCES;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
STATIC
|
|
INTN
|
|
EFIAPI
|
|
AsciiKextNameCmp (
|
|
IN CONST CHAR8 *FirstString,
|
|
IN CONST CHAR8 *SecondString
|
|
)
|
|
{
|
|
//
|
|
// ASSERT both strings are less long than PcdMaximumAsciiStringLength
|
|
//
|
|
ASSERT (AsciiStrSize (FirstString));
|
|
ASSERT (AsciiStrSize (SecondString));
|
|
|
|
while ((*FirstString != '<') && (*FirstString == *SecondString)) {
|
|
FirstString++;
|
|
SecondString++;
|
|
}
|
|
|
|
if ((*FirstString == '<') && (*SecondString == '0')) {
|
|
return 0;
|
|
}
|
|
|
|
return *FirstString - *SecondString;
|
|
}
|
|
|
|
#define OS_BUNDLE_LIBRARIES_STR "OSBundleLibraries"
|
|
#define OS_BUNDLE_IDENTIFIER_STR "CFBundleIdentifier"
|
|
#define OS_BUNDLE_VERSION_STR "CFBundleVersion"
|
|
#define OS_BUNDLE_COMPATIBLE_VERSION_STR "OSBundleCompatibleVersion"
|
|
|
|
STATIC
|
|
CONST CHAR8 *
|
|
InternalFindPrelinkedKextPlist (
|
|
IN CONST CHAR8 *PrelinkedPlist,
|
|
IN CONST CHAR8 *BundleId
|
|
)
|
|
{
|
|
CONST CHAR8 *Walker;
|
|
CONST CHAR8 *Walker2;
|
|
CONST CHAR8 *BundleIdKey;
|
|
INTN Result;
|
|
UINTN DictLevel;
|
|
//
|
|
// Iterate through all OS Bundle IDs.
|
|
//
|
|
BundleIdKey = "<key>" OS_BUNDLE_IDENTIFIER_STR "<";
|
|
|
|
for (
|
|
Walker = AsciiStrStr (PrelinkedPlist, BundleIdKey);
|
|
Walker != NULL;
|
|
Walker = AsciiStrStr (Walker, BundleIdKey)
|
|
) {
|
|
Walker2 = AsciiStrStr (Walker, "<string>");
|
|
ASSERT (Walker2 != NULL);
|
|
Walker2 += (ARRAY_SIZE ("<string>") - 1);
|
|
|
|
Result = AsciiKextNameCmp (Walker2, BundleId);
|
|
if (Result == 0) {
|
|
//
|
|
// The OS Bundle ID is in the first level of the KEXT PLIST dictionary.
|
|
//
|
|
DictLevel = 1;
|
|
|
|
for (
|
|
Walker -= (ARRAY_SIZE ("<dict>") - 1);
|
|
TRUE;
|
|
--Walker
|
|
) {
|
|
ASSERT (Walker >= PrelinkedPlist);
|
|
//
|
|
// Find the KEXT PLIST dictionary entry.
|
|
//
|
|
Result = AsciiStrnCmp (
|
|
Walker,
|
|
"</dict>",
|
|
(ARRAY_SIZE ("</dict>") - 1)
|
|
);
|
|
if (Result == 0) {
|
|
++DictLevel;
|
|
Walker -= ((ARRAY_SIZE ("<dict>") - 1) - 1);
|
|
continue;
|
|
}
|
|
|
|
Result = AsciiStrnCmp (
|
|
Walker,
|
|
"<dict>",
|
|
(ARRAY_SIZE ("<dict>") - 1)
|
|
);
|
|
if (Result == 0) {
|
|
--DictLevel;
|
|
|
|
if (DictLevel == 0) {
|
|
//
|
|
// Return the content within the KEXT PLIST dictionary.
|
|
//
|
|
return (Walker + (ARRAY_SIZE ("<dict>") - 1));
|
|
}
|
|
|
|
Walker -= ((ARRAY_SIZE ("<dict>") - 1) - 1);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
Walker = Walker2;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
STATIC
|
|
UINT64
|
|
InternalGetNewPrelinkedKextLoadAddress (
|
|
IN CONST MACH_HEADER_64 *KernelHeader,
|
|
IN CONST MACH_SEGMENT_COMMAND_64 *PrelinkedKextsSegment,
|
|
IN CONST CHAR8 *PrelinkedPlist
|
|
)
|
|
{
|
|
UINT64 Address;
|
|
|
|
CONST CHAR8 *Walker;
|
|
CHAR8 *Walker2;
|
|
UINT64 TempAddress;
|
|
CONST MACH_HEADER_64 *KextHeader;
|
|
|
|
Address = 0;
|
|
|
|
for (
|
|
Walker = AsciiStrStr (PrelinkedPlist, "<key>_PrelinkExecutableLoadAddr<");
|
|
Walker != NULL;
|
|
Walker = AsciiStrStr (Walker, "<key>_PrelinkExecutableLoadAddr<")
|
|
) {
|
|
Walker += (ARRAY_SIZE ("<key>_PrelinkExecutableLoadAddr</key>") - 1);
|
|
Walker = AsciiStrStr (Walker, "<integer");
|
|
ASSERT (Walker != NULL);
|
|
Walker += (ARRAY_SIZE ("<integer") - 1);
|
|
//
|
|
// Skip "size" and any other attributes.
|
|
//
|
|
Walker = AsciiStrStr (Walker, ">");
|
|
ASSERT (Walker != NULL);
|
|
++Walker;
|
|
//
|
|
// Temporarily terminate after the integer definition.
|
|
//
|
|
Walker2 = AsciiStrStr (Walker, "<");
|
|
ASSERT (Walker2 != NULL);
|
|
*Walker2 = '\0';
|
|
//
|
|
// Assert that the prelinked PLIST always uses hexadecimal integers.
|
|
//
|
|
while ((*Walker == ' ') || (*Walker == '\t')) {
|
|
++Walker;
|
|
}
|
|
ASSERT ((Walker[1] == 'x') || (Walker[1] == 'X'));
|
|
TempAddress = AsciiStrHexToUint64 (Walker);
|
|
ASSERT (TempAddress != 0);
|
|
ASSERT (TempAddress != MAX_UINT64);
|
|
Walker = (Walker2 + (ARRAY_SIZE ("0x0</integer>") - 1));
|
|
//
|
|
// Restore the previously replaced opening brace.
|
|
//
|
|
*Walker2 = '<';
|
|
|
|
if (TempAddress > Address) {
|
|
Address = TempAddress;
|
|
}
|
|
}
|
|
|
|
ASSERT (Address != 0);
|
|
|
|
Address -= PrelinkedKextsSegment->VirtualAddress;
|
|
Address += ((UINTN)KernelHeader + PrelinkedKextsSegment->FileOffset);
|
|
KextHeader = (CONST MACH_HEADER_64 *)Address;
|
|
|
|
return ALIGN_VALUE (MachoGetLastAddress64 (KextHeader), BASE_4KB);
|
|
}
|
|
|
|
STATIC
|
|
CONST CHAR8 *
|
|
InternalKextCollectDependencies (
|
|
IN CONST CHAR8 **BundleLibrariesStr,
|
|
OUT UINTN *NumberOfDependencies
|
|
)
|
|
{
|
|
CONST CHAR8 *Dependencies;
|
|
UINTN NumDependencies;
|
|
|
|
CONST CHAR8 *Walker;
|
|
CHAR8 *DictEnd;
|
|
|
|
Dependencies = NULL;
|
|
NumDependencies = 0;
|
|
DictEnd = NULL;
|
|
|
|
Walker = *BundleLibrariesStr;
|
|
Walker += (ARRAY_SIZE (OS_BUNDLE_LIBRARIES_STR "</key>") - 1);
|
|
//
|
|
// Retrieve the opening of the OSBundleLibraries key.
|
|
//
|
|
Walker = AsciiStrStr (Walker, "<dict");
|
|
ASSERT (Walker != NULL);
|
|
Walker += (ARRAY_SIZE ("<dict") - 1);
|
|
//
|
|
// Verify the dict is not an empty single tag.
|
|
//
|
|
while ((*Walker == ' ') || (*Walker == '\t')) {
|
|
++Walker;
|
|
}
|
|
if (*Walker != '/') {
|
|
ASSERT (*Walker == '>');
|
|
++Walker;
|
|
//
|
|
// Locate the closure of the OSBundleLibraries dict. Dicts inside
|
|
// OSBundleLibraries are not valid, so searching for the next match is
|
|
// fine.
|
|
//
|
|
DictEnd = AsciiStrStr (Walker, "</dict>");
|
|
ASSERT (DictEnd != NULL);
|
|
//
|
|
// Temporarily terminate the PLIST string at the beginning of the
|
|
// OSBundleLibraries closing tag so that AsciiStrStr() calls do not exceed
|
|
// its bounds.
|
|
//
|
|
DictEnd[0] = '\0';
|
|
//
|
|
// Cache the result to retrieve the dependencies from the prelinked PLIST
|
|
// later.
|
|
//
|
|
Dependencies = AsciiStrStr (Walker, "<key>");
|
|
ASSERT (Dependencies != NULL);
|
|
|
|
for (
|
|
Walker = Dependencies;
|
|
Walker != NULL;
|
|
Walker = AsciiStrStr (Walker, "<key>")
|
|
) {
|
|
++NumDependencies;
|
|
//
|
|
// A dependency must have a non-empty name to be valid.
|
|
// A valid string-value for the version identifies is mandatory.
|
|
//
|
|
Walker += (ARRAY_SIZE ("<key>x</key><string>x</string>") - 1);
|
|
}
|
|
//
|
|
// Restore the previously replaced opening brace.
|
|
//
|
|
DictEnd[0] = '<';
|
|
}
|
|
|
|
if (DictEnd != NULL) {
|
|
*BundleLibrariesStr = (DictEnd + (ARRAY_SIZE ("</dict>") - 1));
|
|
} else {
|
|
*BundleLibrariesStr = Walker;
|
|
}
|
|
|
|
*NumberOfDependencies = NumDependencies;
|
|
|
|
return Dependencies;
|
|
}
|
|
|
|
STATIC
|
|
UINT16
|
|
InternalStringToKextVersionWorker (
|
|
IN CONST CHAR8 **VersionString
|
|
)
|
|
{
|
|
UINT16 Version;
|
|
|
|
CONST CHAR8 *VersionStart;
|
|
CHAR8 *VersionEnd;
|
|
CONST CHAR8 *VersionReturn;
|
|
|
|
VersionReturn = NULL;
|
|
|
|
VersionStart = *VersionString;
|
|
VersionEnd = AsciiStrStr (VersionStart, ".");
|
|
|
|
if (VersionEnd != NULL) {
|
|
*VersionEnd = '\0';
|
|
}
|
|
|
|
Version = (UINT16)AsciiStrDecimalToUintn (VersionStart);
|
|
|
|
if (VersionEnd != NULL) {
|
|
*VersionEnd = '.';
|
|
VersionReturn = (VersionEnd + 1);
|
|
ASSERT (*VersionReturn != '\0');
|
|
}
|
|
|
|
*VersionString = VersionReturn;
|
|
|
|
return Version;
|
|
}
|
|
|
|
STATIC
|
|
UINT64
|
|
InternalStringToKextVersion (
|
|
IN CONST CHAR8 *VersionString
|
|
)
|
|
{
|
|
OC_KEXT_VERSION KextVersion;
|
|
CONST CHAR8 *Walker;
|
|
|
|
KextVersion.Value = 0;
|
|
|
|
Walker = VersionString;
|
|
|
|
KextVersion.Bits.Major = InternalStringToKextVersionWorker (&Walker);
|
|
ASSERT (KextVersion.Bits.Major != 0);
|
|
ASSERT (Walker != NULL);
|
|
|
|
KextVersion.Bits.Minor = InternalStringToKextVersionWorker (&Walker);
|
|
ASSERT (KextVersion.Bits.Minor != 0);
|
|
|
|
if (Walker == NULL) {
|
|
return KextVersion.Value;
|
|
}
|
|
|
|
KextVersion.Bits.Revision = InternalStringToKextVersionWorker (&Walker);
|
|
ASSERT (KextVersion.Bits.Revision != 0);
|
|
|
|
if (Walker == NULL) {
|
|
return KextVersion.Value;
|
|
}
|
|
|
|
switch (Walker[0]) {
|
|
case 'd':
|
|
{
|
|
KextVersion.Bits.Stage = OcKextVersionStageDevelopment;
|
|
break;
|
|
}
|
|
|
|
case 'a':
|
|
{
|
|
KextVersion.Bits.Stage = OcKextVersionStageAlpha;
|
|
break;
|
|
}
|
|
|
|
case 'b':
|
|
{
|
|
KextVersion.Bits.Stage = OcKextVersionStageBeta;
|
|
break;
|
|
}
|
|
|
|
case 'f':
|
|
{
|
|
KextVersion.Bits.Stage = OcKextVersionStageCandidate;
|
|
|
|
if (Walker[1] == 'c') {
|
|
++Walker;
|
|
}
|
|
ASSERT ((Walker[1] >= '0') && (Walker[1] <= '9'));
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
++Walker;
|
|
|
|
KextVersion.Bits.StageLevel = (UINT16)AsciiStrDecimalToUintn (Walker);
|
|
ASSERT (KextVersion.Bits.StageLevel != 0);
|
|
|
|
return KextVersion.Value;
|
|
}
|
|
|
|
STATIC
|
|
OC_DEPENDENCY_INFO_ENTRY *
|
|
InternalKextCollectInformation (
|
|
IN CONST CHAR8 *Plist,
|
|
IN UINT64 RequestedVersion OPTIONAL
|
|
)
|
|
{
|
|
OC_DEPENDENCY_INFO_ENTRY *DependencyEntry;
|
|
OC_DEPENDENCY_INFO *DependencyInfo;
|
|
|
|
CONST CHAR8 *Walker;
|
|
INTN Result;
|
|
|
|
OC_DEPENDENCY_INFO DependencyHdr;
|
|
CONST CHAR8 *Dependencies;
|
|
|
|
ZeroMem (&DependencyHdr, sizeof (DependencyHdr));
|
|
Dependencies = NULL;
|
|
|
|
for (
|
|
Walker = AsciiStrStr (Plist, "<key>");
|
|
Walker != NULL;
|
|
Walker = AsciiStrStr (Walker, "<key>")
|
|
) {
|
|
if ((DependencyHdr.Name != NULL)
|
|
&& (DependencyHdr.Version.Value != 0)
|
|
&& (DependencyHdr.CompatibleVersion.Value != 0)
|
|
&& (Dependencies != NULL)
|
|
) {
|
|
DependencyEntry = AllocatePool (
|
|
sizeof (*DependencyEntry)
|
|
+ (DependencyHdr.NumberOfDependencies
|
|
* sizeof (*DependencyInfo->Dependencies))
|
|
);
|
|
if (DependencyEntry != NULL) {
|
|
DependencyEntry->Signature = OC_DEPENDENCY_INFO_ENTRY_SIGNATURE;
|
|
DependencyEntry->Link.ForwardLink = NULL;
|
|
CopyMem (
|
|
&DependencyEntry->Info,
|
|
&DependencyHdr,
|
|
sizeof (DependencyEntry->Info)
|
|
);
|
|
DependencyEntry->Data.SymbolTable = NULL;
|
|
DependencyEntry->Data.Vtables = NULL;
|
|
DependencyInfo = &DependencyEntry->Info;
|
|
//
|
|
// Temporarily store the Dependency strings there until resolution.
|
|
//
|
|
DependencyInfo->Dependencies[0] = (OC_DEPENDENCY_INFO_ENTRY *)(
|
|
Dependencies
|
|
);
|
|
}
|
|
|
|
return DependencyEntry;
|
|
}
|
|
|
|
Walker += (ARRAY_SIZE ("<key>") - 1);
|
|
//
|
|
// Retrieve the KEXT's name.
|
|
//
|
|
if (DependencyHdr.Name == NULL) {
|
|
Result = AsciiStrnCmp (
|
|
Walker,
|
|
(OS_BUNDLE_IDENTIFIER_STR "<"),
|
|
(ARRAY_SIZE (OS_BUNDLE_IDENTIFIER_STR "<") - 1)
|
|
);
|
|
if (Result == 0) {
|
|
Walker += (ARRAY_SIZE (OS_BUNDLE_IDENTIFIER_STR "</key>") - 1);
|
|
Walker = AsciiStrStr (Walker, "<string>");
|
|
ASSERT (Walker != NULL);
|
|
Walker += (ARRAY_SIZE ("<string>") - 1);
|
|
|
|
DependencyHdr.Name = Walker;
|
|
|
|
Walker += (ARRAY_SIZE ("x</string>") - 1);
|
|
continue;
|
|
}
|
|
}
|
|
//
|
|
// Retrieve the KEXT's version.
|
|
//
|
|
if (DependencyHdr.Version.Value == 0) {
|
|
Result = AsciiStrnCmp (
|
|
Walker,
|
|
(OS_BUNDLE_COMPATIBLE_VERSION_STR "<"),
|
|
(ARRAY_SIZE (OS_BUNDLE_COMPATIBLE_VERSION_STR "<") - 1)
|
|
);
|
|
if (Result == 0) {
|
|
Walker += (ARRAY_SIZE (OS_BUNDLE_COMPATIBLE_VERSION_STR "</key>") - 1);
|
|
Walker = AsciiStrStr (Walker, "<string>");
|
|
ASSERT (Walker != NULL);
|
|
Walker += (ARRAY_SIZE ("<string>") - 1);
|
|
|
|
DependencyHdr.Version.Value = InternalStringToKextVersion (Walker);
|
|
if ((DependencyHdr.Version.Value == 0)
|
|
|| ((RequestedVersion != 0)
|
|
&& (RequestedVersion > DependencyHdr.Version.Value))
|
|
) {
|
|
return NULL;
|
|
}
|
|
|
|
Walker += (ARRAY_SIZE ("x</string>") - 1);
|
|
continue;
|
|
}
|
|
}
|
|
//
|
|
// Retrieve the KEXT's compatible version.
|
|
//
|
|
if (DependencyHdr.CompatibleVersion.Value == 0) {
|
|
Result = AsciiStrnCmp (
|
|
Walker,
|
|
(OS_BUNDLE_COMPATIBLE_VERSION_STR "<"),
|
|
(ARRAY_SIZE (OS_BUNDLE_COMPATIBLE_VERSION_STR "<") - 1)
|
|
);
|
|
if (Result == 0) {
|
|
Walker += (ARRAY_SIZE (OS_BUNDLE_COMPATIBLE_VERSION_STR "</key>") - 1);
|
|
Walker = AsciiStrStr (Walker, "<string>");
|
|
ASSERT (Walker != NULL);
|
|
Walker += (ARRAY_SIZE ("<string>") - 1);
|
|
|
|
DependencyHdr.CompatibleVersion.Value = InternalStringToKextVersion (
|
|
Walker
|
|
);
|
|
if ((DependencyHdr.Version.Value == 0)
|
|
|| ((RequestedVersion != 0)
|
|
&& (RequestedVersion < DependencyHdr.CompatibleVersion.Value))
|
|
) {
|
|
return NULL;
|
|
}
|
|
|
|
Walker += (ARRAY_SIZE ("x</string>") - 1);
|
|
continue;
|
|
}
|
|
}
|
|
//
|
|
// Retrieve the KEXT's dependency information.
|
|
//
|
|
if (Dependencies == NULL) {
|
|
Result = AsciiStrnCmp (
|
|
Walker,
|
|
(OS_BUNDLE_LIBRARIES_STR "<"),
|
|
(ARRAY_SIZE (OS_BUNDLE_LIBRARIES_STR "<") - 1)
|
|
);
|
|
if (Result == 0) {
|
|
Dependencies = InternalKextCollectDependencies (
|
|
&Walker,
|
|
&DependencyHdr.NumberOfDependencies
|
|
);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
STATIC
|
|
VOID
|
|
InternalRemoveDependency (
|
|
IN UINTN NumberOfRequests,
|
|
IN OUT OC_KEXT_REQUEST *Requests,
|
|
IN CONST OC_KEXT_REQUEST *Request
|
|
)
|
|
{
|
|
UINTN Index;
|
|
UINTN Index2;
|
|
CONST OC_DEPENDENCY_INFO_ENTRY *RemoveEntry;
|
|
OC_KEXT_REQUEST *CurrentRequest;
|
|
OC_DEPENDENCY_INFO_ENTRY *Entry;
|
|
CONST OC_DEPENDENCY_INFO *Info;
|
|
|
|
RemoveEntry = (CONST OC_DEPENDENCY_INFO_ENTRY *)Request->Private;
|
|
|
|
for (Index = 0; Index < NumberOfRequests; ++Index) {
|
|
CurrentRequest = &Requests[Index];
|
|
Entry = (OC_DEPENDENCY_INFO_ENTRY *)CurrentRequest->Private;
|
|
|
|
if (Entry == RemoveEntry) {
|
|
continue;
|
|
}
|
|
|
|
Info = (CONST OC_DEPENDENCY_INFO *)&Entry->Info;
|
|
|
|
for (Index2 = 0; Index2 < Info->NumberOfDependencies; ++Index2) {
|
|
if (Info->Dependencies[Index2] == RemoveEntry) {
|
|
InternalRemoveDependency (NumberOfRequests, Requests, CurrentRequest);
|
|
}
|
|
}
|
|
|
|
if (Entry->Data.SymbolTable != NULL) {
|
|
FreePool (Entry->Data.SymbolTable);
|
|
}
|
|
|
|
if (Entry->Data.Vtables != NULL) {
|
|
FreePool (Entry->Data.Vtables);
|
|
}
|
|
|
|
FreePool (Entry);
|
|
CurrentRequest->Private = NULL;
|
|
}
|
|
}
|
|
|
|
STATIC
|
|
VOID
|
|
InternalConstructDependencyArraysWorker (
|
|
IN UINTN NumberOfDependencies,
|
|
IN OC_DEPENDENCY_INFO_ENTRY **Dependencies,
|
|
OUT OC_DEPENDENCY_DATA *DependencyData,
|
|
IN BOOLEAN IsTopLevel
|
|
)
|
|
{
|
|
OC_DEPENDENCY_INFO_ENTRY *Dependency;
|
|
OC_DEPENDENCY_INFO *Info;
|
|
UINTN Index;
|
|
|
|
for (Index = 0; Index < NumberOfDependencies; ++Index) {
|
|
Dependency = Dependencies[Index];
|
|
Info = &Dependency->Info;
|
|
|
|
if (Dependency->Data.SymbolTable->Link.ForwardLink != NULL) {
|
|
//
|
|
// This dependency already has been added to the lost and henceforth its
|
|
// dependencies as well.
|
|
//
|
|
continue;
|
|
}
|
|
|
|
InternalConstructDependencyArraysWorker (
|
|
Info->NumberOfDependencies,
|
|
Info->Dependencies,
|
|
DependencyData,
|
|
FALSE
|
|
);
|
|
|
|
InsertHeadList (
|
|
&DependencyData->SymbolTable->Link,
|
|
&Dependency->Data.SymbolTable->Link
|
|
);
|
|
InsertHeadList (
|
|
&DependencyData->Vtables->Link,
|
|
&Dependency->Data.Vtables->Link
|
|
);
|
|
|
|
Dependency->Data.SymbolTable->IsIndirect = !IsTopLevel;
|
|
}
|
|
}
|
|
|
|
STATIC
|
|
VOID
|
|
InternalConstructDependencyArrays (
|
|
IN UINTN NumberOfDependencies,
|
|
IN OC_DEPENDENCY_INFO_ENTRY **Dependencies,
|
|
OUT OC_DEPENDENCY_DATA *DependencyData
|
|
)
|
|
{
|
|
OC_DEPENDENCY_INFO *Info;
|
|
|
|
Info = &Dependencies[0]->Info;
|
|
|
|
DependencyData->SymbolTable = Dependencies[0]->Data.SymbolTable;
|
|
DependencyData->Vtables = Dependencies[0]->Data.Vtables;
|
|
|
|
InitializeListHead (&DependencyData->SymbolTable->Link);
|
|
InitializeListHead (&DependencyData->Vtables->Link);
|
|
DependencyData->SymbolTable->IsIndirect = FALSE;
|
|
//
|
|
// Process the first dependency's dependencies as it itself has already been
|
|
// processed implicitly above.
|
|
//
|
|
InternalConstructDependencyArraysWorker (
|
|
Info->NumberOfDependencies,
|
|
Info->Dependencies,
|
|
DependencyData,
|
|
FALSE
|
|
);
|
|
InternalConstructDependencyArraysWorker (
|
|
(NumberOfDependencies - 1),
|
|
&Dependencies[1],
|
|
DependencyData,
|
|
TRUE
|
|
);
|
|
}
|
|
|
|
STATIC
|
|
VOID
|
|
InternalDestructDependencyArraysWorker (
|
|
OUT LIST_ENTRY *ListEntry
|
|
)
|
|
{
|
|
do {
|
|
//
|
|
// Only clear ForwardLink as BackLink is not explicitly checked.
|
|
//
|
|
ListEntry->ForwardLink = NULL;
|
|
ListEntry = ListEntry->BackLink;
|
|
} while (ListEntry->ForwardLink != NULL);
|
|
}
|
|
|
|
STATIC
|
|
VOID
|
|
InternalDestructDependencyArrays (
|
|
OUT CONST OC_DEPENDENCY_DATA *DependencyData
|
|
)
|
|
{
|
|
InternalDestructDependencyArraysWorker (
|
|
&DependencyData->SymbolTable->Link
|
|
);
|
|
InternalDestructDependencyArraysWorker (
|
|
&DependencyData->Vtables->Link
|
|
);
|
|
}
|
|
|
|
VOID
|
|
OcPrelinkKexts64 (
|
|
IN CONST MACH_HEADER_64 *KernelHeader,
|
|
IN UINTN NumberOfRequests,
|
|
IN OUT OC_KEXT_REQUEST *Requests
|
|
)
|
|
{
|
|
UINTN Index;
|
|
OC_KEXT_REQUEST *Request;
|
|
OC_DEPENDENCY_INFO_ENTRY *DependencyInfo;
|
|
OC_DEPENDENCY_INFO *Info;
|
|
OC_DEPENDENCY_DATA DependencyData;
|
|
BOOLEAN Result;
|
|
UINT64 LoadAddress;
|
|
CONST MACH_SEGMENT_COMMAND_64 *PrelinkInfoSegment;
|
|
CONST MACH_SECTION_64 *PrelinkInfoSection;
|
|
CONST MACH_SEGMENT_COMMAND_64 *PrelinkTextSegment;
|
|
//
|
|
// Get the Prelinked PLIST data.
|
|
//
|
|
PrelinkInfoSegment = MachoGetSegmentByName64 (
|
|
KernelHeader,
|
|
"__PRELINK_INFO"
|
|
);
|
|
ASSERT (PrelinkInfoSegment != NULL);
|
|
|
|
PrelinkInfoSection = MachoGetSectionByName64 (
|
|
KernelHeader,
|
|
PrelinkInfoSegment,
|
|
"__info"
|
|
);
|
|
ASSERT (PrelinkInfoSection != NULL);
|
|
//
|
|
// Get the segment containing the prelinked KEXT binaries.
|
|
//
|
|
PrelinkTextSegment = MachoGetSegmentByName64 (
|
|
KernelHeader,
|
|
"__PRELINK_TEXT"
|
|
);
|
|
ASSERT (PrelinkTextSegment != NULL);
|
|
//
|
|
// Collect the KEXT information for all KEXTs to be prelinked. Do not solve
|
|
// dependencies yet so we can verify successful dependency against other
|
|
// KEXTs to be prelinked in one go.
|
|
//
|
|
for (Index = 0; Index < NumberOfRequests; ++Index) {
|
|
Request = &Requests[Index];
|
|
Request->Private = InternalKextCollectInformation (Request->Plist, 0);
|
|
}
|
|
//
|
|
// Resolve dependencies.
|
|
//
|
|
for (Index = 0; Index < NumberOfRequests; ++Index) {
|
|
Request = &Requests[Index];
|
|
if (Request->Private != NULL) {
|
|
// resolve deps
|
|
if (FALSE) {
|
|
InternalRemoveDependency (Index, Requests, Request);
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// Link the KEXTs.
|
|
//
|
|
for (Index = 0; Index < NumberOfRequests; ++Index) {
|
|
Request = &Requests[Index];
|
|
DependencyInfo = (OC_DEPENDENCY_INFO_ENTRY *)Request->Private;
|
|
Info = &DependencyInfo->Info;
|
|
//
|
|
// Retrieve a loading address for the KEXT.
|
|
//
|
|
LoadAddress = InternalGetNewPrelinkedKextLoadAddress (
|
|
KernelHeader,
|
|
PrelinkTextSegment,
|
|
(CONST CHAR8 *)(
|
|
(UINTN)KernelHeader + PrelinkInfoSection->Offset
|
|
)
|
|
);
|
|
if (LoadAddress == 0) {
|
|
InternalRemoveDependency (Index, Requests, Request);
|
|
continue;
|
|
}
|
|
InternalConstructDependencyArrays (
|
|
Info->NumberOfDependencies,
|
|
Info->Dependencies,
|
|
&DependencyData
|
|
);
|
|
Result = InternalPrelinkKext64 (
|
|
NULL,
|
|
LoadAddress,
|
|
&DependencyData,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
);
|
|
InternalDestructDependencyArrays (&DependencyData);
|
|
|
|
if (!Result) {
|
|
InternalRemoveDependency (Index, Requests, Request);
|
|
}
|
|
}
|
|
}
|