/** @file
  Copyright (C) 2019, vit9696. All rights reserved.

  All rights reserved.

  This program and the accompanying materials
  are licensed and made available under the terms and conditions of the BSD License
  which accompanies this distribution.  The full text of the license may be found at
  http://opensource.org/licenses/bsd-license.php

  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/

#include <Base.h>

#include <IndustryStandard/AppleKmodInfo.h>

#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/OcAppleKernelLib.h>
#include <Library/OcMachoLib.h>
#include <Library/OcMiscLib.h>
#include <Library/OcXmlLib.h>

#include "MkextInternal.h"
#include "PrelinkedInternal.h"

STATIC
BOOLEAN
GetTextBaseOffset (
  IN  OC_MACHO_CONTEXT  *ExecutableContext,
  OUT UINT64            *Address,
  OUT UINT64            *Offset
  )
{
  MACH_SEGMENT_COMMAND     *Segment32;
  MACH_SECTION             *Section32;
  MACH_SEGMENT_COMMAND_64  *Segment64;

  UINT64  VirtualAddress;
  UINT64  FileOffset;

  //
  // 32-bit can be of type MH_OBJECT, which has all sections in a single unnamed segment.
  // We'll fallback to that if there is no __TEXT segment.
  //
  if (ExecutableContext->Is32Bit) {
    Segment32 = MachoGetNextSegment32 (ExecutableContext, NULL);
    if (Segment32 == NULL) {
      return FALSE;
    }

    if (AsciiStrCmp (Segment32->SegmentName, "") == 0) {
      Section32 = MachoGetSectionByName32 (
                    ExecutableContext,
                    Segment32,
                    "__text"
                    );
      if (Section32 == NULL) {
        return FALSE;
      }

      VirtualAddress = Section32->Address;
      FileOffset     = Section32->Offset;
    } else {
      Segment32 = MachoGetSegmentByName32 (
                    ExecutableContext,
                    "__TEXT"
                    );
      if ((Segment32 == NULL) || (Segment32->VirtualAddress < Segment32->FileOffset)) {
        return FALSE;
      }

      VirtualAddress = Segment32->VirtualAddress;
      FileOffset     = Segment32->FileOffset;
    }
  } else {
    Segment64 = MachoGetSegmentByName64 (
                  ExecutableContext,
                  "__TEXT"
                  );
    if ((Segment64 == NULL) || (Segment64->VirtualAddress < Segment64->FileOffset)) {
      return FALSE;
    }

    VirtualAddress = Segment64->VirtualAddress;
    FileOffset     = Segment64->FileOffset;
  }

  *Address = VirtualAddress;
  *Offset  = FileOffset;

  return TRUE;
}

EFI_STATUS
PatcherInitContextFromPrelinked (
  IN OUT PATCHER_CONTEXT    *Context,
  IN OUT PRELINKED_CONTEXT  *Prelinked,
  IN     CONST CHAR8        *Name
  )
{
  PRELINKED_KEXT  *Kext;

  Kext = InternalCachedPrelinkedKext (Prelinked, Name);
  if (Kext == NULL) {
    return EFI_NOT_FOUND;
  }

  CopyMem (Context, &Kext->Context, sizeof (*Context));
  return EFI_SUCCESS;
}

EFI_STATUS
PatcherInitContextFromMkext (
  IN OUT PATCHER_CONTEXT  *Context,
  IN OUT MKEXT_CONTEXT    *Mkext,
  IN     CONST CHAR8      *Name
  )
{
  MKEXT_KEXT  *Kext;

  Kext = InternalCachedMkextKext (Mkext, Name);
  if (Kext == NULL) {
    return EFI_NOT_FOUND;
  }

  return PatcherInitContextFromBuffer (Context, &Mkext->Mkext[Kext->BinaryOffset], Kext->BinarySize, Mkext->Is32Bit);
}

EFI_STATUS
PatcherInitContextFromBuffer (
  IN OUT PATCHER_CONTEXT  *Context,
  IN OUT UINT8            *Buffer,
  IN     UINT32           BufferSize,
  IN     BOOLEAN          Is32Bit
  )
{
  EFI_STATUS        Status;
  OC_MACHO_CONTEXT  InnerContext;

  UINT64  VirtualAddress;
  UINT64  FileOffset;

  ASSERT (Context != NULL);
  ASSERT (Buffer != NULL);
  ASSERT (BufferSize > 0);

  //
  // This interface is still used for the kernel due to the need to patch
  // standalone kernel outside of prelinkedkernel in e.g. 10.9.
  // Once 10.9 support is dropped one could call PatcherInitContextFromPrelinked
  // and request PRELINK_KERNEL_IDENTIFIER.
  //

  if (!MachoInitializeContext (&Context->MachContext, Buffer, BufferSize, 0, BufferSize, Is32Bit)) {
    DEBUG ((
      DEBUG_INFO,
      "OCAK: %a-bit patcher init from buffer %p %u has unsupported mach-o\n",
      Is32Bit ? "32" : "64",
      Buffer,
      BufferSize
      ));
    return EFI_INVALID_PARAMETER;
  }

  if (!GetTextBaseOffset (&Context->MachContext, &VirtualAddress, &FileOffset)) {
    return EFI_NOT_FOUND;
  }

  Context->Is32Bit            = Is32Bit;
  Context->VirtualBase        = VirtualAddress;
  Context->FileOffset         = FileOffset;
  Context->VirtualKmod        = 0;
  Context->KxldState          = NULL;
  Context->KxldStateSize      = 0;
  Context->IsKernelCollection = FALSE;

  KextFindKmodAddress (
    &Context->MachContext,
    0,
    BufferSize,
    &Context->VirtualKmod
    );

  if (!Context->Is32Bit) {
    Status = InternalConnectExternalSymtab (
               &Context->MachContext,
               &InnerContext,
               Buffer,
               BufferSize,
               NULL
               );
    if (EFI_ERROR (Status)) {
      return Status;
    }
  }

  DEBUG ((
    DEBUG_VERBOSE,
    "OCAK: %a-bit patcher base 0x%llX kmod 0x%llX file 0x%llX\n",
    Is32Bit ? "32" : "64",
    Context->VirtualBase,
    Context->VirtualKmod,
    Context->FileOffset
    ));

  return EFI_SUCCESS;
}

EFI_STATUS
PatcherGetSymbolAddress (
  IN OUT PATCHER_CONTEXT  *Context,
  IN     CONST CHAR8      *Name,
  IN OUT UINT8            **Address
  )
{
  return PatcherGetSymbolAddressValue (Context, Name, Address, NULL);
}

EFI_STATUS
PatcherGetSymbolValue (
  IN OUT PATCHER_CONTEXT  *Context,
  IN     CONST CHAR8      *Name,
  IN OUT UINT64           *Value
  )
{
  return PatcherGetSymbolAddressValue (Context, Name, NULL, Value);
}

EFI_STATUS
PatcherGetSymbolAddressValue (
  IN OUT PATCHER_CONTEXT  *Context,
  IN     CONST CHAR8      *Name,
  IN OUT UINT8            **Address,
  IN OUT UINT64           *Value
  )
{
  MACH_NLIST_ANY  *Symbol;
  CONST CHAR8     *SymbolName;
  UINT64          SymbolAddress;
  UINT32          Offset;
  UINT32          Index;

  Index  = 0;
  Offset = 0;
  while (TRUE) {
    //
    // Try the usual way first via SYMTAB.
    //
    Symbol = MachoGetSymbolByIndex (&Context->MachContext, Index);
    if (Symbol == NULL) {
      //
      // If we have KxldState, use it.
      //
      if ((Index == 0) && (Context->KxldState != NULL)) {
        SymbolAddress = InternalKxldSolveSymbol (
                          Context->Is32Bit,
                          Context->KxldState,
                          Context->KxldStateSize,
                          Name
                          );
        //
        // If we have a symbol, get its ondisk offset.
        //
        if ((SymbolAddress != 0) && MachoSymbolGetDirectFileOffset (&Context->MachContext, SymbolAddress, &Offset, NULL)) {
          //
          // Proceed to success.
          //
          break;
        }
      }

      return EFI_NOT_FOUND;
    }

    SymbolName = MachoGetSymbolName (&Context->MachContext, Symbol);
    if ((SymbolName != NULL) && (AsciiStrCmp (Name, SymbolName) == 0)) {
      //
      // Once we have a symbol, get its ondisk offset.
      //
      if (MachoSymbolGetFileOffset (&Context->MachContext, Symbol, &Offset, NULL)) {
        //
        // Proceed to success.
        //
        SymbolAddress = Context->Is32Bit ? Symbol->Symbol32.Value : Symbol->Symbol64.Value;
        break;
      }

      return EFI_INVALID_PARAMETER;
    }

    Index++;
  }

  if (Address != NULL) {
    *Address = (UINT8 *)MachoGetFileData (&Context->MachContext) + Offset;
  }

  if (Value != NULL) {
    *Value = SymbolAddress;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
PatcherApplyGenericPatch (
  IN OUT PATCHER_CONTEXT        *Context,
  IN     PATCHER_GENERIC_PATCH  *Patch
  )
{
  EFI_STATUS  Status;
  UINT8       *Base;
  UINT32      Size;
  UINT32      ReplaceCount;

  Base = (UINT8 *)MachoGetMachHeader (&Context->MachContext);
  Size = MachoGetInnerSize (&Context->MachContext);
  if (Patch->Base != NULL) {
    Status = PatcherGetSymbolAddress (Context, Patch->Base, &Base);
    if (EFI_ERROR (Status)) {
      DEBUG ((
        DEBUG_INFO,
        "OCAK: %a-bit %a base lookup failure %r\n",
        Context->Is32Bit ? "32" : "64",
        Patch->Comment != NULL ? Patch->Comment : "Patch",
        Status
        ));
      return Status;
    }

    Size -= (UINT32)(Base - (UINT8 *)MachoGetMachHeader (&Context->MachContext));
  }

  if (Patch->Find == NULL) {
    if (Size < Patch->Size) {
      DEBUG ((
        DEBUG_INFO,
        "OCAK: %a-bit %a is borked, not found\n",
        Context->Is32Bit ? "32" : "64",
        Patch->Comment != NULL ? Patch->Comment : "Patch"
        ));
      return EFI_NOT_FOUND;
    }

    CopyMem (Base, Patch->Replace, Patch->Size);
    return EFI_SUCCESS;
  }

  if ((Patch->Limit > 0) && (Patch->Limit < Size)) {
    Size = Patch->Limit;
  }

  ReplaceCount = ApplyPatch (
                   Patch->Find,
                   Patch->Mask,
                   Patch->Size,
                   Patch->Replace,
                   Patch->ReplaceMask,
                   Base,
                   Size,
                   Patch->Count,
                   Patch->Skip
                   );

  DEBUG ((
    DEBUG_INFO,
    "OCAK: %a-bit %a replace count - %u\n",
    Context->Is32Bit ? "32" : "64",
    Patch->Comment != NULL ? Patch->Comment : "Patch",
    ReplaceCount
    ));

  if ((ReplaceCount > 0) && (Patch->Count > 0) && (ReplaceCount != Patch->Count)) {
    DEBUG ((
      DEBUG_INFO,
      "OCAK: %a-bit %a performed only %u replacements out of %u\n",
      Context->Is32Bit ? "32" : "64",
      Patch->Comment != NULL ? Patch->Comment : "Patch",
      ReplaceCount,
      Patch->Count
      ));
  }

  if (ReplaceCount > 0) {
    return EFI_SUCCESS;
  }

  return EFI_NOT_FOUND;
}

EFI_STATUS
PatcherExcludePrelinkedKext (
  IN     CONST CHAR8        *Identifier,
  IN OUT PATCHER_CONTEXT    *PatcherContext,
  IN OUT PRELINKED_CONTEXT  *PrelinkedContext
  )
{
  MACH_SEGMENT_COMMAND_ANY  *Segment;
  VOID                      *KextData;
  UINT64                    AddressMax;
  UINT64                    VirtualAddress;
  UINT64                    FileOffset;
  UINT64                    Size;
  UINT64                    MaxSize;

  UINT32       KextCount;
  UINT32       Index;
  UINT32       Index2;
  XML_NODE     *KextPlist;
  UINT32       KextPlistCount;
  CONST CHAR8  *KextPlistKey;
  XML_NODE     *KextPlistValue;
  CONST CHAR8  *KextIdentifier;
  EFI_STATUS   Status;

  ASSERT (Identifier       != NULL);
  ASSERT (PatcherContext   != NULL);
  ASSERT (PrelinkedContext != NULL);

  Segment        = NULL;
  VirtualAddress = 0;
  AddressMax     = 0;
  Size           = 0;
  while ((Segment = MachoGetNextSegment (&PatcherContext->MachContext, Segment)) != NULL) {
    VirtualAddress = PatcherContext->Is32Bit ? Segment->Segment32.VirtualAddress : Segment->Segment64.VirtualAddress;

    if ((AddressMax != 0) && (AddressMax != VirtualAddress)) {
      break;
    }

    Size       = PatcherContext->Is32Bit ? Segment->Segment32.Size : Segment->Segment64.Size;
    AddressMax = MAX (VirtualAddress + Size, AddressMax);
  }

  MaxSize = AddressMax - PatcherContext->VirtualBase;

  //
  // Get file offset for 32-bit.
  //
  if (PatcherContext->Is32Bit) {
    if (!GetTextBaseOffset (&PatcherContext->MachContext, &VirtualAddress, &FileOffset)) {
      return EFI_UNSUPPORTED;
    }

    VirtualAddress += FileOffset;
  } else {
    VirtualAddress = PatcherContext->VirtualBase;
  }

  //
  // Zero out kext memory through PatcherContext->MachContext.
  //
  KextData = MachoGetFilePointerByAddress (
               &PatcherContext->MachContext,
               VirtualAddress,
               NULL
               );
  if (KextData == NULL) {
    return EFI_UNSUPPORTED;
  }

  DEBUG ((
    DEBUG_INFO,
    "OCAK: Excluding %a - VirtualBase %Lx, MaxSize %Lx\n",
    Identifier,
    PatcherContext->VirtualBase,
    MaxSize
    ));
  ZeroMem (KextData, (UINTN)MaxSize);

  //
  // Find kext info to be removed in prelinked context.
  //
  KextCount      = XmlNodeChildren (PrelinkedContext->KextList);
  KextPlist      = NULL;
  KextPlistKey   = NULL;
  KextIdentifier = NULL;

  for (Index = 0; Index < KextCount; ++Index) {
    KextPlist = PlistNodeCast (XmlNodeChild (PrelinkedContext->KextList, Index), PLIST_NODE_TYPE_DICT);
    if (KextPlist == NULL) {
      continue;
    }

    KextPlistCount = PlistDictChildren (KextPlist);
    for (Index2 = 0; Index2 < KextPlistCount; ++Index2) {
      KextPlistKey = PlistKeyValue (PlistDictChild (KextPlist, Index2, &KextPlistValue));
      if (KextPlistKey == NULL) {
        continue;
      }

      if (AsciiStrCmp (KextPlistKey, INFO_BUNDLE_IDENTIFIER_KEY) == 0) {
        KextIdentifier = XmlNodeContent (KextPlistValue);
        if ((PlistNodeCast (KextPlistValue, PLIST_NODE_TYPE_STRING) == NULL) || (KextIdentifier == NULL)) {
          DEBUG ((
            DEBUG_INFO,
            "OCAK: Plist value cannot be interpreted as string, or current kext identifier is null (dict index %u, plist %p, plist index %u)\n",
            Index2,
            KextPlist,
            Index
            ));
          return EFI_NOT_FOUND;
        }

        if (AsciiStrCmp (KextIdentifier, Identifier) == 0) {
          //
          // Erase kext.
          //
          Status = InternalDropCachedPrelinkedKext (PrelinkedContext, KextIdentifier);
          if (EFI_ERROR (Status)) {
            DEBUG ((
              DEBUG_INFO,
              "OCAK: Failed to drop %a under dict index %u, plist %p, plist index %u - %r\n",
              KextIdentifier,
              Index2,
              KextPlist,
              Index,
              Status
              ));
            return Status;
          }

          DEBUG ((
            DEBUG_INFO,
            "OCAK: Erasing %a from prelinked kext under dict index %u, plist %p, plist index %u\n",
            Identifier,
            Index2,
            KextPlist,
            Index
            ));
          XmlNodeRemoveByIndex (PrelinkedContext->KextList, Index);
          return EFI_SUCCESS;
        }
      }
    }
  }

  return EFI_NOT_FOUND;
}

EFI_STATUS
PatcherExcludeMkextKext (
  IN OUT MKEXT_CONTEXT  *MkextContext,
  IN     CONST CHAR8    *Identifier
  )
{
  EFI_STATUS           Status;
  MKEXT_V2_FILE_ENTRY  *MkextV2FileEntry;
  MKEXT_HEADER_ANY     *MkextHeader;
  CONST CHAR8          *KextIdentifier;
  BOOLEAN              IsKextMatch;

  UINT32       PlistBundlesCount;
  XML_NODE     *PlistBundle;
  UINT32       PlistBundleIndex;
  UINT32       PlistBundleCount;
  CONST CHAR8  *PlistBundleKey;
  XML_NODE     *PlistBundleKeyValue;

  UINT32  Index;
  UINT32  PlistOffset;
  UINT32  PlistSize;
  UINT32  BinOffset;
  UINT32  BinSize;

  ASSERT (MkextContext     != NULL);
  ASSERT (Identifier       != NULL);

  MkextHeader = MkextContext->MkextHeader;
  IsKextMatch = FALSE;

  //
  // Mkext v1.
  //
  if (MkextContext->MkextVersion == MKEXT_VERSION_V1) {
    Status = InternalGetMkextV1KextOffsets (MkextContext, Identifier, &Index, &PlistOffset, &PlistSize, &BinOffset, &BinSize);
    if (EFI_ERROR (Status)) {
      return Status;
    }

    DEBUG ((
      DEBUG_INFO,
      "OCAK: Excluding mkext v1 %a - plist %x (%x), binary %x (%x)\n",
      Identifier,
      PlistOffset,
      PlistSize,
      BinOffset,
      BinSize
      ));

    //
    // Zero out kext memory and kext list entry.
    // A completely zero entry will be skipped by XNU, although a non-fatal error will be logged.
    //
    ZeroMem (&MkextContext->Mkext[PlistOffset], PlistSize);
    ZeroMem (&MkextContext->Mkext[BinOffset], BinSize);
    ZeroMem (&MkextHeader->V1.Kexts[Index], sizeof (MkextHeader->V1.Kexts[Index]));

    //
    // Mkext v2.
    //
  } else if (MkextContext->MkextVersion == MKEXT_VERSION_V2) {
    //
    // Enumerate bundle dicts and find kext to be removed.
    //
    PlistBundlesCount = XmlNodeChildren (MkextContext->MkextKexts);
    for (Index = 0; Index < PlistBundlesCount; Index++) {
      PlistBundle = PlistNodeCast (XmlNodeChild (MkextContext->MkextKexts, Index), PLIST_NODE_TYPE_DICT);
      if (PlistBundle == NULL) {
        continue;
      }

      PlistBundleCount = PlistDictChildren (PlistBundle);
      for (PlistBundleIndex = 0; PlistBundleIndex < PlistBundleCount; PlistBundleIndex++) {
        PlistBundleKey = PlistKeyValue (PlistDictChild (PlistBundle, PlistBundleIndex, &PlistBundleKeyValue));
        if ((PlistBundleKey == NULL) || (PlistBundleKeyValue == NULL)) {
          continue;
        }

        if (AsciiStrCmp (PlistBundleKey, INFO_BUNDLE_IDENTIFIER_KEY) == 0) {
          KextIdentifier = XmlNodeContent (PlistBundleKeyValue);
          if ((PlistNodeCast (PlistBundleKeyValue, PLIST_NODE_TYPE_STRING) == NULL) || (KextIdentifier == NULL)) {
            DEBUG ((
              DEBUG_INFO,
              "OCAK: Plist value cannot be interpreted as string, or current kext identifier is null (dict index %u, plist %p, plist index %u)\n",
              PlistBundleIndex,
              PlistBundle,
              Index
              ));
            return EFI_NOT_FOUND;
          }
        }

        if (AsciiStrCmp (PlistBundleKey, MKEXT_EXECUTABLE_KEY) == 0) {
          if (!PlistIntegerValue (PlistBundleKeyValue, &BinOffset, sizeof (BinOffset), TRUE)) {
            DEBUG ((
              DEBUG_INFO,
              "OCAK: Plist value cannot be interpreted as integer (dict index %u, plist %p, plist index %u)\n",
              PlistBundleIndex,
              PlistBundle,
              Index
              ));
            return EFI_NOT_FOUND;
          }
        }
      }

      if (  (KextIdentifier != NULL)
         && (AsciiStrCmp (KextIdentifier, Identifier) == 0)
         && (BinOffset > 0)
         && (BinOffset < MkextContext->MkextSize - sizeof (MKEXT_V2_FILE_ENTRY)))
      {
        IsKextMatch = TRUE;
        break;
      }

      KextIdentifier = NULL;
      BinOffset      = 0;
    }

    //
    // Bundle was not found, or invalid.
    //
    if (!IsKextMatch) {
      return EFI_NOT_FOUND;
    }

    //
    // Parse v2 binary header.
    // We cannot support compressed binaries.
    //
    MkextV2FileEntry = (MKEXT_V2_FILE_ENTRY *)&MkextContext->Mkext[BinOffset];
    if (MkextV2FileEntry->CompressedSize != 0) {
      return EFI_UNSUPPORTED;
    }

    BinSize = SwapBytes32 (MkextV2FileEntry->FullSize);

    DEBUG ((
      DEBUG_INFO,
      "OCAK: Excluding mkext v2 %a - dict index %u, plist %p, plist index %u, binary %x (%x)\n",
      Identifier,
      PlistBundleIndex,
      PlistBundle,
      Index,
      BinOffset,
      BinSize
      ));

    //
    // Erase kext data and drop from plist.
    //
    ZeroMem (&MkextContext->Mkext[BinOffset], BinSize + sizeof (MKEXT_V2_FILE_ENTRY));
    InternalDropCachedMkextKext (MkextContext, KextIdentifier);
    XmlNodeRemoveByIndex (MkextContext->MkextKexts, Index);

    //
    // Unsupported version.
    //
  } else {
    return EFI_UNSUPPORTED;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
PatcherBlockKext (
  IN OUT PATCHER_CONTEXT  *Context
  )
{
  UINT8   *MachBase;
  UINT32  MachSize;
  UINT64  KmodOffset;
  UINT64  StartAddr;
  UINT64  TmpOffset;
  UINT8   *KmodInfo;
  UINT8   *PatchAddr;

  //
  // Kernel has 0 kmod.
  //
  if ((Context->VirtualKmod == 0) || (Context->VirtualBase > Context->VirtualKmod)) {
    return EFI_UNSUPPORTED;
  }

  MachBase = (UINT8 *)MachoGetMachHeader (&Context->MachContext);
  MachSize = MachoGetInnerSize (&Context->MachContext);

  //
  // Determine offset of kmod within file.
  //
  KmodOffset = Context->VirtualKmod - Context->VirtualBase;
  if (  BaseOverflowAddU64 (KmodOffset, Context->FileOffset, &KmodOffset)
     || BaseOverflowAddU64 (KmodOffset, Context->Is32Bit ? sizeof (KMOD_INFO_32_V1) : sizeof (KMOD_INFO_64_V1), &TmpOffset)
     || (TmpOffset > MachSize))
  {
    return EFI_INVALID_PARAMETER;
  }

  KmodInfo = MachBase + KmodOffset;
  if (Context->Is32Bit) {
    StartAddr = ((KMOD_INFO_32_V1 *)KmodInfo)->StartAddr;
  } else {
    StartAddr = ((KMOD_INFO_64_V1 *)KmodInfo)->StartAddr;
    if (Context->IsKernelCollection) {
      StartAddr = KcFixupValue (StartAddr, NULL);
    }
  }

  if ((StartAddr == 0) || (Context->VirtualBase > StartAddr)) {
    return EFI_INVALID_PARAMETER;
  }

  TmpOffset = StartAddr - Context->VirtualBase;
  if (  BaseOverflowAddU64 (TmpOffset, Context->FileOffset, &TmpOffset)
     || (TmpOffset > MachSize - 6))
  {
    return EFI_BUFFER_TOO_SMALL;
  }

  DEBUG ((
    DEBUG_VERBOSE,
    "OCAK: %a-bit blocker start @ 0x%llX\n",
    Context->Is32Bit ? "32" : "64",
    TmpOffset
    ));

  PatchAddr = MachBase + TmpOffset;

  //
  // mov eax, KMOD_RETURN_FAILURE
  // ret
  //
  PatchAddr[0] = 0xB8;
  PatchAddr[1] = KMOD_RETURN_FAILURE;
  PatchAddr[2] = 0x00;
  PatchAddr[3] = 0x00;
  PatchAddr[4] = 0x00;
  PatchAddr[5] = 0xC3;

  return EFI_SUCCESS;
}

BOOLEAN
KextFindKmodAddress (
  IN  OC_MACHO_CONTEXT  *ExecutableContext,
  IN  UINT64            LoadAddress,
  IN  UINT32            Size,
  OUT UINT64            *Kmod
  )
{
  BOOLEAN         Is32Bit;
  MACH_NLIST_ANY  *Symbol;
  CONST CHAR8     *SymbolName;
  UINT64          Address;
  UINT64          FileOffset;
  UINT32          Index;

  Is32Bit = ExecutableContext->Is32Bit;
  Index   = 0;

  while (TRUE) {
    Symbol = MachoGetSymbolByIndex (ExecutableContext, Index);
    if (Symbol == NULL) {
      *Kmod = 0;
      return TRUE;
    }

    if (((Is32Bit ? Symbol->Symbol32.Type : Symbol->Symbol64.Type) & MACH_N_TYPE_STAB) == 0) {
      SymbolName = MachoGetSymbolName (ExecutableContext, Symbol);
      if (SymbolName && (AsciiStrCmp (SymbolName, "_kmod_info") == 0)) {
        if (!MachoIsSymbolValueInRange (ExecutableContext, Symbol)) {
          return FALSE;
        }

        break;
      }
    }

    Index++;
  }

  if (!GetTextBaseOffset (ExecutableContext, &Address, &FileOffset)) {
    return FALSE;
  }

  if (  BaseOverflowTriAddU64 (Address, LoadAddress, Is32Bit ? Symbol->Symbol32.Value : Symbol->Symbol64.Value, &Address)
     || (Address > LoadAddress + Size - (Is32Bit ? sizeof (KMOD_INFO_32_V1) : sizeof (KMOD_INFO_64_V1)))
     || (Is32Bit && (Address > MAX_UINT32)))
  {
    return FALSE;
  }

  *Kmod = Address;
  return TRUE;
}
