KernelCollection.c 27.0 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
/** @file
  Kernel collection support.

  Copyright (c) 2020, vit9696. 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/AppleCompressedBinaryImage.h>
#include <IndustryStandard/AppleFatBinaryImage.h>

#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/OcAppleKernelLib.h>
#include <Library/OcCompressionLib.h>
#include <Library/OcFileLib.h>
#include <Library/OcGuardLib.h>

#include "PrelinkedInternal.h"
30 31
#include "ProcessorBind.h"
#include "Uefi/UefiInternalFormRepresentation.h"
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71

STATIC
UINTN
InternalKcGetKextFilesetSize (
  IN OUT PRELINKED_CONTEXT  *Context
  )
{
  PRELINKED_KEXT  *PrelinkedKext;
  LIST_ENTRY      *Kext;
  UINTN           Size;
  UINTN           CommandSize;

  Size = 0;

  Kext = GetFirstNode (&Context->InjectedKexts);
  while (!IsNull (&Context->InjectedKexts, Kext)) {
    PrelinkedKext = GET_INJECTED_KEXT_FROM_LINK (Kext);

    //
    // Each command must be NUL-terminated and 8-byte aligned.
    //
    CommandSize = sizeof (MACH_FILESET_ENTRY_COMMAND) + AsciiStrSize (PrelinkedKext->Identifier);
    Size       += ALIGN_VALUE (CommandSize, 8);

    Kext = GetNextNode (&Context->InjectedKexts, Kext);
  }

  return Size;
}

STATIC
VOID
InternalKcWriteCommandHeaders (
  IN OUT PRELINKED_CONTEXT  *Context,
  IN OUT MACH_HEADER_64     *MachHeader
  )
{
  PRELINKED_KEXT           *PrelinkedKext;
  LIST_ENTRY               *Kext;
  MACH_LOAD_COMMAND_PTR    Command;
72
  MACH_HEADER_64           *KextHeader;
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
  UINTN                    StringSize;

  Command.Address = (UINTN) MachHeader->Commands + MachHeader->CommandsSize;

  Kext       = GetFirstNode (&Context->InjectedKexts);

  while (!IsNull (&Context->InjectedKexts, Kext)) {
    PrelinkedKext = GET_INJECTED_KEXT_FROM_LINK (Kext);

    StringSize = AsciiStrSize (PrelinkedKext->Identifier);

    //
    // Write 8-byte aligned fileset command.
    //
    Command.FilesetEntry->CommandType = MACH_LOAD_COMMAND_FILESET_ENTRY;
88
    Command.FilesetEntry->CommandSize = (UINT32) ALIGN_VALUE (sizeof (MACH_FILESET_ENTRY_COMMAND) + StringSize, 8);
89
    Command.FilesetEntry->VirtualAddress = PrelinkedKext->Context.VirtualBase;
90 91 92 93 94
    KextHeader = MachoGetMachHeader64(&PrelinkedKext->Context.MachContext);
    ASSERT (KextHeader != NULL);
    ASSERT ((UINTN) KextHeader > (UINTN) Context->Prelinked
      && (UINTN) KextHeader < (UINTN) Context->Prelinked + Context->PrelinkedSize);
    Command.FilesetEntry->FileOffset     = (UINTN) KextHeader - (UINTN) Context->Prelinked;
95 96 97 98 99 100 101 102 103 104 105 106 107 108
    Command.FilesetEntry->EntryId.Offset = OFFSET_OF (MACH_FILESET_ENTRY_COMMAND, Payload);
    Command.FilesetEntry->Reserved       = 0;
    CopyMem (Command.FilesetEntry->Payload, PrelinkedKext->Identifier, StringSize);
    ZeroMem (
      &Command.FilesetEntry->Payload[StringSize],
      Command.FilesetEntry->CommandSize - Command.FilesetEntry->EntryId.Offset - StringSize
      );

    //
    // Refresh Mach-O header constants to include the new command.
    //
    MachHeader->NumCommands++;
    MachHeader->CommandsSize += Command.FilesetEntry->CommandSize;

109 110 111 112 113
    //
    // Proceed to the next command.
    //
    Command.Address += Command.FilesetEntry->CommandSize;

114 115 116 117 118 119 120 121 122 123 124 125 126
    Kext = GetNextNode (&Context->InjectedKexts, Kext);
  }

  //
  // Write a segment covering all the kexts.
  //
  Command.Segment64->CommandType       = MACH_LOAD_COMMAND_SEGMENT_64;
  Command.Segment64->CommandSize       = sizeof (MACH_SEGMENT_COMMAND_64);
  CopyMem (Command.Segment64->SegmentName, KC_MOSCOW_SEGMENT, sizeof (KC_MOSCOW_SEGMENT));
  ZeroMem (
    &Command.Segment64->SegmentName[sizeof (KC_MOSCOW_SEGMENT)],
    sizeof (Command.Segment64->SegmentName) - sizeof (KC_MOSCOW_SEGMENT)
    );
127 128 129 130 131 132 133 134
  Command.Segment64->VirtualAddress    = Context->KextsVmAddress;
  Command.Segment64->FileOffset        = Context->KextsFileOffset;
  //
  // In KC mode, PrelinkedLastAddress and PrelinkedLastLoadAddress are equal
  // before KEXTs are injected.
  //
  Command.Segment64->Size              = Context->PrelinkedLastAddress - Context->KextsVmAddress;
  Command.Segment64->FileSize          = Context->PrelinkedSize - Context->KextsFileOffset;
135 136 137 138 139 140 141 142 143 144 145 146
  Command.Segment64->MaximumProtection = MACH_SEGMENT_VM_PROT_READ
    | MACH_SEGMENT_VM_PROT_WRITE | MACH_SEGMENT_VM_PROT_EXECUTE;
  Command.Segment64->InitialProtection = MACH_SEGMENT_VM_PROT_READ
    | MACH_SEGMENT_VM_PROT_WRITE | MACH_SEGMENT_VM_PROT_EXECUTE;
  Command.Segment64->NumSections       = 0;
  Command.Segment64->Flags             = 0;

  //
  // Refresh Mach-O header constants to include the new segment command.
  //
  MachHeader->NumCommands++;
  MachHeader->CommandsSize += sizeof (MACH_SEGMENT_COMMAND_64);
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184

  //
  // Proceed to the next command.
  //
  Command.Address += Command.FilesetEntry->CommandSize;

  //
  // Write new __PRELINKED_INFO segment, as the first writeable segment is used by EfiBoot
  // as a relocation base. Relocation in KC mode is very simplified, and this segment PADDR
  // is simply used for all the relocations in DYSYMTAB. Unless we leave it untouched, sliding
  // will "relocate" invalid memory, as new plist is normally put at the end of KC memory.
  //
  // Note, that we might need to resolve Context->PrelinkedInfoSegment if regions move upper
  // and current __PRELINKED_INFO gets shifted.
  //
  CopyMem (
    (VOID *) Command.Address,
    Context->PrelinkedInfoSegment,
    Context->PrelinkedInfoSegment->CommandSize
    );

  //
  // Must choose a different name to avoid collisions.
  //
  CopyMem (Context->PrelinkedInfoSegment->SegmentName, "__KREMLIN_START", sizeof ("__KREMLIN_START"));
  CopyMem (Context->PrelinkedInfoSection->SectionName, "__kremlin_start", sizeof ("__kremlin_start"));

  //
  // Refresh Mach-O header constants to include the new segment command.
  //
  MachHeader->NumCommands++;
  MachHeader->CommandsSize += Context->PrelinkedInfoSegment->CommandSize;

  //
  // Update __PRELINKED_INFO pointers to get them updated at a later stage.
  //
  Context->PrelinkedInfoSegment = (MACH_SEGMENT_COMMAND_64 *) Command.Address;
  Context->PrelinkedInfoSection = &Context->PrelinkedInfoSegment->Sections[0];
185 186 187
}

EFI_STATUS
188
KcRebuildMachHeader (
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
  IN OUT PRELINKED_CONTEXT  *Context
  )
{
  MACH_HEADER_64           *MachHeader;
  MACH_SEGMENT_COMMAND_64  *TextSegment;
  UINTN                    CurrentSize;
  UINTN                    FilesetSize;
  UINTN                    RequiredSize;

  MachHeader = MachoGetMachHeader64 (
    &Context->PrelinkedMachContext
    );

  CurrentSize  = MachHeader->CommandsSize + sizeof (*MachHeader);
  FilesetSize  = InternalKcGetKextFilesetSize (Context);
204
  RequiredSize = FilesetSize + sizeof (MACH_SEGMENT_COMMAND_64) + Context->PrelinkedInfoSegment->CommandSize;
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244

  TextSegment = MachoGetSegmentByName64 (
    &Context->PrelinkedMachContext,
    KC_TEXT_SEGMENT
    );

  DEBUG ((
    DEBUG_INFO,
    "OCAK: KC TEXT is %u bytes with %u Mach-O headers need %u\n",
    (UINT32) (TextSegment != NULL ? TextSegment->FileSize : 0),
    (UINT32) CurrentSize,
    (UINT32) RequiredSize
    ));

  if (TextSegment == NULL
    || TextSegment->FileOffset != 0
    || TextSegment->FileSize != TextSegment->Size
    || TextSegment->FileSize < CurrentSize) {
    return EFI_INVALID_PARAMETER;
  }

  if (FilesetSize == 0) {
    return EFI_SUCCESS; ///< Just in case.
  }

  if (CurrentSize + RequiredSize > TextSegment->FileSize) {
    //
    // We do not have enough memory in the header, free some memory by merging
    // kext segments (__REGION###) into a single RWX segment.
    // - This is not bad for security as it is actually how it is done before
    //   11.0, where OSKext::setVMAttributes sets the proper memory permissions
    //   on every kext soon after the kernel loads.
    // - This is not bad for compatibility as the only thing referencing these
    //   segments is dyld fixups command, and it does not matter whether it
    //   references the base segment or points to the middle of it.
    // The actual reason this was added might actually be because of the ARM
    // transition, where you can restrict the memory permissions till the next
    // hardware reset (like on iOS) and they now really try to require W^X:
    // https://developer.apple.com/videos/play/wwdc2020/10686/
    //
245
    if (!MachoMergeSegments (&Context->PrelinkedMachContext, KC_REGION_SEGMENT_PREFIX)) {
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
      DEBUG ((DEBUG_INFO, "OCAK: Segment expansion failure\n"));
      return EFI_UNSUPPORTED;
    }

    CurrentSize = MachHeader->CommandsSize + sizeof (*MachHeader);
  }

  //
  // If we do not fit even after the expansion, we are really doomed.
  // This should never happen.
  //
  if (CurrentSize + RequiredSize > TextSegment->FileSize) {
    DEBUG ((DEBUG_INFO, "OCAK: Used header %u is still too large\n", (UINT32) CurrentSize));
    return EFI_UNSUPPORTED;
  }

  //
  // At this step we have memory for all the new commands.
  // Just write the here.
  //
  InternalKcWriteCommandHeaders (Context, MachHeader);

  return EFI_SUCCESS;
}
270 271

UINT32
272
KcGetSegmentFixupChainsSize (
273 274 275 276
  IN UINT32  SegmentSize
  )
{
  CONST MACH_DYLD_CHAINED_STARTS_IN_SEGMENT *Dummy;
277

278
  ASSERT (SegmentSize % MACHO_PAGE_SIZE == 0);
279 280

  if (SegmentSize > PRELINKED_KEXTS_MAX_SIZE) {
281 282 283 284 285 286 287
    return 0;
  }

  return (UINT32) (sizeof (*Dummy)
    + (SegmentSize / MACHO_PAGE_SIZE) * sizeof (Dummy->PageStart[0]));
}

288 289 290 291 292
EFI_STATUS
KcInitKextFixupChains (
  IN OUT PRELINKED_CONTEXT  *Context,
  IN     UINT32             SegChainSize,
  IN     UINT32             ReservedSize
293 294
  )
{
295 296 297 298 299 300
  BOOLEAN                                Result;
  CONST MACH_LINKEDIT_DATA_COMMAND       *DyldChainedFixups;
  CONST MACHO_DYLD_CHAINED_FIXUPS_HEADER *DyldChainedFixupsHdr;
  MACH_DYLD_CHAINED_STARTS_IN_IMAGE      *DyldChainedStarts;
  UINT32                                 DyldChainedStartsSize;
  UINT32                                 SegIndex;
301

302 303 304 305
  ASSERT (Context != NULL);
  ASSERT (ReservedSize % MACHO_PAGE_SIZE == 0);

  ASSERT (Context->KextsFixupChains != NULL);
306 307 308

  ASSERT (SegChainSize != 0);
  ASSERT (
309
    SegChainSize >= KcGetSegmentFixupChainsSize (ReservedSize)
310 311 312 313 314 315 316 317 318
    );
  //
  // Context initialisation guarantees the command size is a multiple of 8.
  //
  STATIC_ASSERT (
    OC_ALIGNOF (MACH_LINKEDIT_DATA_COMMAND) <= sizeof (UINT64),
    "Alignment is not guaranteed."
    );
  
319
  DyldChainedFixups = (CONST MACH_LINKEDIT_DATA_COMMAND *) MachoGetNextCommand (
320 321 322 323 324 325 326 327
    &Context->PrelinkedMachContext,
    MACH_LOAD_COMMAND_DYLD_CHAINED_FIXUPS,
    NULL
    );
  //
  // Show that StartsInImage is aligned relative to __LINKEDIT start so we only
  // need to check DataOffset below.
  //
328
  ASSERT ((Context->LinkEditSegment->FileOffset % MACHO_PAGE_SIZE) == 0);
329 330 331 332 333 334 335 336
  STATIC_ASSERT (
    OC_TYPE_ALIGNED (MACHO_DYLD_CHAINED_FIXUPS_HEADER, MACHO_PAGE_SIZE),
    "Alignment is not guaranteed."
    );

  if (DyldChainedFixups == NULL
   || DyldChainedFixups->CommandSize != sizeof (*DyldChainedFixups)
   || DyldChainedFixups->DataSize < sizeof (MACHO_DYLD_CHAINED_FIXUPS_HEADER)
337 338
   || DyldChainedFixups->DataOffset < Context->LinkEditSegment->FileOffset
   || (Context->LinkEditSegment->FileOffset + Context->LinkEditSegment->FileSize)
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
        - DyldChainedFixups->DataOffset < DyldChainedFixups->DataSize
   || !OC_TYPE_ALIGNED (MACHO_DYLD_CHAINED_FIXUPS_HEADER, DyldChainedFixups->DataOffset)) {
    DEBUG ((DEBUG_WARN, "ChainedFixups insane\n"));
    return EFI_UNSUPPORTED;
  }

  DyldChainedFixupsHdr = (CONST MACHO_DYLD_CHAINED_FIXUPS_HEADER *) (VOID *) (
    Context->Prelinked + DyldChainedFixups->DataOffset
    );
  if (DyldChainedFixupsHdr->FixupsVersion != 0) {
    DEBUG ((DEBUG_WARN, "Unrecognised version\n"));
  }

  STATIC_ASSERT (
    OC_ALIGNOF (MACHO_DYLD_CHAINED_FIXUPS_HEADER) >= OC_ALIGNOF (MACH_DYLD_CHAINED_STARTS_IN_IMAGE),
    "Alignment is not guaranteed."
    );

  STATIC_ASSERT (
    sizeof (MACHO_DYLD_CHAINED_FIXUPS_HEADER) >= sizeof (MACH_DYLD_CHAINED_STARTS_IN_IMAGE),
    "The subtraction below is unsafe."
    );

  if (DyldChainedFixupsHdr->StartsOffset < sizeof (MACHO_DYLD_CHAINED_FIXUPS_HEADER)
   || DyldChainedFixupsHdr->StartsOffset > DyldChainedFixups->DataSize - sizeof (MACH_DYLD_CHAINED_STARTS_IN_IMAGE)
   || !OC_TYPE_ALIGNED (MACH_DYLD_CHAINED_STARTS_IN_IMAGE, DyldChainedFixupsHdr->StartsOffset)) {
     DEBUG ((DEBUG_WARN, "ChainedFixupsHdr insane\n"));
     return EFI_UNSUPPORTED;
  }

  DyldChainedStarts = (MACH_DYLD_CHAINED_STARTS_IN_IMAGE *) (VOID *) (
    (UINTN) DyldChainedFixupsHdr + DyldChainedFixupsHdr->StartsOffset
371 372
    );

373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
  Result = OcOverflowMulAddU32 (
    DyldChainedStarts->NumSegments,
    sizeof (*DyldChainedStarts->SegInfoOffset),
    sizeof (*DyldChainedStarts),
    &DyldChainedStartsSize
    );
  if (Result || DyldChainedStartsSize > DyldChainedFixups->DataSize - DyldChainedFixupsHdr->StartsOffset) {
    DEBUG ((DEBUG_WARN, "DyldChainedFixups insane\n"));
    return EFI_UNSUPPORTED;
  }

  for (SegIndex = 0; SegIndex < DyldChainedStarts->NumSegments; ++SegIndex) {
    if (DyldChainedStarts->SegInfoOffset[SegIndex] == 0) {
      break;
    }
  }

  if (SegIndex == DyldChainedStarts->NumSegments) {
    DEBUG ((DEBUG_WARN, "No free start\n"));
    return EFI_UNSUPPORTED;
  }

  ASSERT ((UINTN) Context->KextsFixupChains > (UINTN) DyldChainedStarts);
  DyldChainedStarts->SegInfoOffset[SegIndex] = (UINT32) (
    (UINTN) Context->KextsFixupChains - (UINTN) DyldChainedStarts
    );

  Context->KextsFixupChains->Size            = SegChainSize;
  Context->KextsFixupChains->PageSize        = MACHO_PAGE_SIZE;
  Context->KextsFixupChains->PointerFormat   = MACH_DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE;
  Context->KextsFixupChains->SegmentOffset   = Context->KextsVmAddress - Context->VirtualBase;
  Context->KextsFixupChains->MaxValidPointer = 0;
  Context->KextsFixupChains->PageCount       = (UINT16) (ReservedSize / MACHO_PAGE_SIZE);
406 407
  //
  // Initialise all pages with no associated fixups.
408
  // Sets all PageStarts to MACH_DYLD_CHAINED_PTR_START_NONE.
409
  //
410 411 412 413 414 415 416
  SetMem (
    Context->KextsFixupChains->PageStart,
    (UINT32) Context->KextsFixupChains->PageCount
      * sizeof (Context->KextsFixupChains->PageStart[0]),
    0xFF
    );
  return EFI_SUCCESS;
417 418 419 420 421
}

/*
  Indexes RelocInfo into the fixup chains SegChain of Segment.

422
  @param[in,out] Context      Prelinked context.
423 424 425
  @param[in]     MachContext  The context of the Mach-O RelocInfo belongs to. It
                              must have been prelinked by OcAppleKernelLib. The
                              image must reside in Segment.
426 427 428
  @param[in]     RelocInfo    The relocation to add a fixup of.
  @param[in]     RelocBase    The relocation base address.
*/
429
STATIC
430 431
VOID
InternalKcConvertRelocToFixup (
432 433 434 435
  IN OUT PRELINKED_CONTEXT           *Context,
  IN     OC_MACHO_CONTEXT            *MachContext,
  IN     CONST MACH_RELOCATION_INFO  *RelocInfo,
  IN     UINT64                      RelocBase
436 437 438 439 440 441 442 443 444 445 446
  )
{
  UINT8                                        *SegmentData;
  UINT8                                        *SegmentPageData;

  UINT64                                       RelocAddress;
  UINT32                                       RelocOffsetInSeg;
  VOID                                         *RelocDest;

  UINT16                                       NewFixupPage;
  UINT16                                       NewFixupPageOffset;
447
  MACH_DYLD_CHAINED_PTR_64_KERNEL_CACHE_REBASE NewFixup;
448 449 450

  UINT16                                       IterFixupPageOffset;
  VOID                                         *IterFixupData;
451
  MACH_DYLD_CHAINED_PTR_64_KERNEL_CACHE_REBASE IterFixup;
452 453 454 455
  UINT16                                       NextIterFixupPageOffset;

  UINT16                                       FixupDelta;

456
  ASSERT (Context != NULL);
457 458
  ASSERT (MachContext != NULL);
  ASSERT (RelocInfo != NULL);
459 460 461

  ASSERT (Context->KextsFixupChains != NULL);
  ASSERT (Context->KextsFixupChains->PageSize == MACHO_PAGE_SIZE);
462 463 464 465 466
  //
  // The entire KEXT and thus its relocations must be in Segment.
  // Mach-O images are limited to 4 GB size by OcMachoLib, so the cast is safe.
  //
  RelocAddress     = RelocBase + (UINT32) RelocInfo->Address;
467
  RelocOffsetInSeg = (UINT32) (RelocAddress - Context->KextsVmAddress);
468 469 470 471 472
  //
  // For now we assume we prelinked already and the relocations are sane.
  //
  ASSERT (RelocInfo->Extern == 0);
  ASSERT (RelocInfo->Type == MachX8664RelocUnsigned);
473 474 475
  ASSERT (RelocAddress >= Context->KextsVmAddress);
  ASSERT (RelocOffsetInSeg <= Context->PrelinkedSize - Context->KextsFileOffset);
  ASSERT (Context->KextsFileOffset - RelocOffsetInSeg >= 8);
476 477 478
  //
  // Create a new fixup based on the data of RelocInfo.
  //
479
  SegmentData = Context->Prelinked + Context->KextsFileOffset;
480 481 482 483 484 485 486
  RelocDest   = SegmentData + RelocOffsetInSeg;
  //
  // It has been observed all fields but target and next are 0 for the kernel
  // KC. For isAuth, this is because x86 does not support Pointer
  // Authentication.
  //
  ZeroMem (&NewFixup, sizeof (NewFixup));
487 488 489 490
  //
  // This 1MB here is a bit of a hack. I think it is just the same thing
  // as KERNEL_BASE_PADDR in OcAfterBootCompatLib.
  //
491
  NewFixup.Target = ReadUnaligned64 (RelocDest) - KERNEL_FIXUP_OFFSET;
492 493 494 495

  NewFixupPage       = (UINT16) (RelocOffsetInSeg / MACHO_PAGE_SIZE);
  NewFixupPageOffset = (UINT16) (RelocOffsetInSeg % MACHO_PAGE_SIZE);

496
  IterFixupPageOffset = Context->KextsFixupChains->PageStart[NewFixupPage];
497 498 499 500 501

  if (IterFixupPageOffset == MACH_DYLD_CHAINED_PTR_START_NONE) {
    //
    // The current page has no fixups, just assign and terminate this one.
    //
502
    Context->KextsFixupChains->PageStart[NewFixupPage] = NewFixupPageOffset;
503 504 505 506 507 508 509 510
    //
    // Fixup.next is 0 (i.e. the chain is terminated) already.
    //
  } else if (NewFixupPageOffset < IterFixupPageOffset) {
    //
    // RelocInfo preceeds the first fixup of the page - prepend the new fixup.
    //
    NewFixup.Next = IterFixupPageOffset - NewFixupPageOffset;
511
    Context->KextsFixupChains->PageStart[NewFixupPage] = NewFixupPageOffset;
512 513 514 515 516 517 518 519 520 521 522 523 524
  } else {
    SegmentPageData = SegmentData + NewFixupPage * MACHO_PAGE_SIZE;
    //
    // Find the last fixup of this page that preceeds RelocInfo.
    // FIXME: Consider sorting the relocations in descending order first to
    //        then always prepend the new fixup.
    //
    NextIterFixupPageOffset = IterFixupPageOffset;
    do {
      IterFixupPageOffset = NextIterFixupPageOffset;
      IterFixupData       = SegmentPageData + IterFixupPageOffset;

      CopyMem (&IterFixup, IterFixupData, sizeof (IterFixup));
525
      NextIterFixupPageOffset = (UINT16) (IterFixupPageOffset + IterFixup.Next);
526 527 528 529 530 531 532 533
    } while (NextIterFixupPageOffset < NewFixupPageOffset && IterFixup.Next != 0);

    FixupDelta = NewFixupPageOffset - IterFixupPageOffset;
    //
    // Our new fixup needs to point to the first fixup following it or terminate
    // if the previous one was the last.
    //
    if (IterFixup.Next != 0) {
534
      ASSERT (IterFixup.Next >= FixupDelta);
535 536 537 538 539 540 541 542 543 544 545 546 547
      NewFixup.Next = IterFixup.Next - FixupDelta;
    } else {
      NewFixup.Next = 0;
    }
    //
    // The last fixup preceeding RelocInfo must point to our new fixup.
    //
    IterFixup.Next = FixupDelta;
    CopyMem (IterFixupData, &IterFixup, sizeof (IterFixup));
  }

  CopyMem (RelocDest, &NewFixup, sizeof (NewFixup));
}
548 549

/*
550
  Indexes all relocations of MachContext into the kernel described by Context.
551

552
  @param[in,out] Context      Prelinked context.
553 554 555 556 557
  @param[in]     MachContext  The context of the Mach-O to index. It must have
                              been prelinked by OcAppleKernelLib. The image
                              must reside in Segment.
*/
VOID
558 559 560
KcKextIndexFixups (
  IN OUT PRELINKED_CONTEXT  *Context,
  IN     OC_MACHO_CONTEXT   *MachContext
561 562 563 564
  )
{
  CONST MACH_DYSYMTAB_COMMAND   *DySymtab;
  CONST MACH_SEGMENT_COMMAND_64 *FirstSegment;
565
  MACH_HEADER_64                *MachHeader;
566 567 568
  CONST MACH_RELOCATION_INFO    *Relocations;
  UINT32                        RelocIndex;

569
  ASSERT (Context != NULL);
570 571 572
  ASSERT (MachContext != NULL);

  MachHeader = MachoGetMachHeader64 (MachContext);
573

574
  //
575 576 577
  // Kexts with fixups are now dylibs in cache.
  // This is required for OSKext_protect to work properly
  // as the kernel map that operates on vm_map no longer has kext addresses.
578
  //
579 580
  MachHeader->Flags |= MACH_HEADER_FLAG_DYLIB_IN_CACHE;

581 582 583 584 585
  //
  // FIXME: The current OcMachoLib was not written with post-linking
  // re-initialisation in mind. We really don't want to sanitise everything
  // again, so avoid the dedicated API for now.
  //
586
  DySymtab = (MACH_DYSYMTAB_COMMAND *) MachoGetNextCommand (
587 588 589 590
    MachContext,
    MACH_LOAD_COMMAND_DYSYMTAB,
    NULL
    );
591 592 593 594 595 596 597
  //
  // If DYSYMTAB does not exist, the KEXT must have already not been flagged for
  // DYLD linkage as otherwise prelinking would have failed.
  //
  if (DySymtab == NULL) {
    return;
  }
598 599 600 601 602 603 604 605 606 607 608

  FirstSegment = MachoGetNextSegment64 (MachContext, NULL);
  //
  // DYSYMTAB and at least one segment must exist, otherwise prelinking would
  // have failed.
  //
  ASSERT (DySymtab != NULL);
  ASSERT (FirstSegment != NULL);
  //
  // The Mach-O file to index must be included in Segment.
  //
609 610
  ASSERT (FirstSegment->VirtualAddress >= Context->KextsVmAddress);
  ASSERT (MachoGetLastAddress64 (MachContext) <= Context->PrelinkedLastAddress);
611 612 613 614 615 616 617 618
  //
  // Prelinking must have eliminated all external relocations.
  //
  ASSERT (DySymtab->NumExternalRelocations == 0);
  //
  // Convert all relocations to fixups.
  //
  Relocations = (MACH_RELOCATION_INFO *) (
619
    (UINTN) MachHeader + DySymtab->LocalRelocationsOffset
620 621
    );

622
  DEBUG ((
623
    DEBUG_INFO,
624 625 626 627 628
    "OCAK: Local relocs %u on %LX\n",
    DySymtab->NumOfLocalRelocations,
    FirstSegment->VirtualAddress
    ));

629 630
  for (RelocIndex = 0; RelocIndex < DySymtab->NumOfLocalRelocations; ++RelocIndex) {
    InternalKcConvertRelocToFixup (
631
      Context,
632 633 634 635 636 637
      MachContext,
      &Relocations[RelocIndex],
      FirstSegment->VirtualAddress
      );
  }
}
638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669

UINT32
KcGetKextSize (
  IN PRELINKED_CONTEXT  *Context,
  IN UINT64             SourceAddress
  )
{
  MACH_HEADER_64          *KcHeader;
  MACH_SEGMENT_COMMAND_64 *Segment;

  ASSERT (Context != NULL);
  ASSERT (Context->IsKernelCollection);

  KcHeader = MachoGetMachHeader64 (&Context->PrelinkedMachContext);
  ASSERT (KcHeader != NULL);
  //
  // Find the KC segment that contains the KEXT at SourceAddress.
  //
  for (
    Segment = MachoGetNextSegment64 (&Context->PrelinkedMachContext, NULL);
    Segment != NULL;
    Segment = MachoGetNextSegment64 (&Context->PrelinkedMachContext, Segment)
    )
  {
    if (SourceAddress >= Segment->VirtualAddress
     && SourceAddress - Segment->VirtualAddress < Segment->Size) {
      //
      // Ensure SourceAddress lies in file memory.
      //
      if (SourceAddress - Segment->VirtualAddress > Segment->FileSize) {
        return 0;
      }
670

671
      //
672
      // All kexts in KC use joint __LINKEDIT with the kernel.
673
      //
674
      return (UINT32) (Context->LinkEditSegment->VirtualAddress
675
        - SourceAddress + Context->LinkEditSegment->Size);
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
    }
  }

  return 0;
}

EFI_STATUS
KcKextApplyFileDelta (
  IN OUT OC_MACHO_CONTEXT  *Context,
  IN     UINT32            Delta
  )
{
  MACH_HEADER_64          *KextHeader;
  MACH_LOAD_COMMAND       *Command;
  UINTN                   TopOfCommands;
  MACH_SEGMENT_COMMAND_64 *Segment;
  MACH_SYMTAB_COMMAND     *Symtab;
  MACH_DYSYMTAB_COMMAND   *DySymtab;
  UINT32                  SectIndex;

  ASSERT (Context != NULL);
  ASSERT (Delta > 0);

  KextHeader = MachoGetMachHeader64 (Context);
  ASSERT (KextHeader != NULL);

  TopOfCommands = ((UINTN) KextHeader->Commands + KextHeader->CommandsSize);
  //
  // Iterate over all Load Commands to rebase them based on type.
  //
  for (
    Command = KextHeader->Commands;
    (UINTN) Command < TopOfCommands;
    Command = NEXT_MACH_LOAD_COMMAND (Command)
    ) {
    switch (Command->CommandType) {
      case MACH_LOAD_COMMAND_SEGMENT_64:
        if (Command->CommandSize < sizeof (MACH_SEGMENT_COMMAND_64)) {
          return EFI_UNSUPPORTED;
        }

        Segment = (MACH_SEGMENT_COMMAND_64 *) (VOID *) Command;
        //
        // Rebase the segment's sections.
        //
        for (SectIndex = 0; SectIndex < Segment->NumSections; ++SectIndex) {
          if (Segment->Sections[SectIndex].Offset != 0) {
            Segment->Sections[SectIndex].Offset += Delta;
          }
        }
        //
        // Rebase the segment itself.
        //
729
        if (Segment->FileOffset != 0 || Segment->FileSize != 0) {
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
          Segment->FileOffset += Delta;
        }

        break;

      case MACH_LOAD_COMMAND_SYMTAB:
        if (Command->CommandSize != sizeof (MACH_SYMTAB_COMMAND)) {
          return EFI_UNSUPPORTED;
        }

        Symtab = (MACH_SYMTAB_COMMAND *) (VOID *) Command;
        //
        // Rebase all SYMTAB offsets.
        //
        if (Symtab->SymbolsOffset != 0) {
          Symtab->SymbolsOffset += Delta;
        }

        if (Symtab->StringsOffset != 0) {
          Symtab->StringsOffset += Delta;
        }

        break;

      case MACH_LOAD_COMMAND_DYSYMTAB:
        if (Command->CommandSize != sizeof (MACH_DYSYMTAB_COMMAND)) {
          return EFI_UNSUPPORTED;
        }

        DySymtab = (MACH_DYSYMTAB_COMMAND *) (VOID *) Command;
        //
        // Rebase DYSYMTAB fields that make sense in a prelinked context.
        //
        if (DySymtab->IndirectSymbolsOffset != 0) {
          DySymtab->IndirectSymbolsOffset += Delta;
        }
        //
        // KC KEXTs use chained fixups indexed by the KC header.
        //
        DySymtab->NumOfLocalRelocations  = 0;
        DySymtab->LocalRelocationsOffset = 0;

        break;

      default:
        //
        // Other common segments do not contain offsets or they are unused.
        //
        break;
    }
  }

782 783 784 785 786 787
  //
  // Update the container offset to make sure we can link against this
  // kext later as well.
  //
  Context->ContainerOffset = Delta;

788 789
  return EFI_SUCCESS;
}
790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826

UINT64
KcFixupValue (
  IN UINT64             Value,
  IN CONST CHAR8        *Name OPTIONAL
  )
{
  MACH_DYLD_CHAINED_PTR_64_KERNEL_CACHE_REBASE  *Rebase;

  //
  // For all non-kernel (which uses own relocation) all virtual tables
  // and several other symbols will contain fixups exclusively.
  // For now we will just detect them by the kernel address
  // as it is faster than compare Kext->Identifier and Context->IsKernelCollection.
  //
  if ((Value & KERNEL_ADDRESS_MASK) != KERNEL_ADDRESS_BASE
    && (Value & KERNEL_ADDRESS_MASK) != KERNEL_ADDRESS_KEXT) {
    //
    // FIXME: This needs a bit more love with aliasing and alignment.
    // Some day, when Intel rewrites EDK II.
    //
    Rebase = (MACH_DYLD_CHAINED_PTR_64_KERNEL_CACHE_REBASE *)(UINTN) &Value;
    DEBUG_CODE_BEGIN ();
    if (Rebase->CacheLevel != 0
      || Rebase->Diversity != 0
      || Rebase->AddrDiv != 0
      || Rebase->Key != 0
      || Rebase->IsAuth != 0) {
      DEBUG ((DEBUG_INFO, "OCAK: Invalid fixup %Lx for %a\n", Value, Name != NULL ? Name : "<none>"));
    }
    DEBUG_CODE_END ();

    Value = Rebase->Target + KERNEL_FIXUP_OFFSET + KERNEL_ADDRESS_BASE;
  }

  return Value;
}