カーネル読み込みメモリ空間を確保する (OpenCore編)

macOSが起動する時、ブートローダがカーネルをメモリに読み込みます。この時、連続した十分な大きさのメモリ領域が確保できないと、起動に失敗して禁止マークが出ます。この状況への対処方法が、OpenCoreではCloverよりも充実しています。

以前の記事では、メモリの使用状況(メモリマップ)を確認して、空き領域を起動オプションのslide値で指定することで、カーネル用メモリを確実に確保する方法を紹介しました。今回はそのOpenCore編です。


以下のOpenCoreのガイドで、この問題への対処方法が詳しく説明されています。以下はこのサイトの抄訳です。

KASLR Slide値を設定する

このページでは、 “Couldn’t allocate runtime area” (ランタイム領域を割り当てられませんでした) というエラーを理解し、修正したいユーザーのために説明します。これは Z390, X99, X299 などで最もよく現れるエラーです。このページはOpenCoreだけでなく、Cloverも対象にします。

KASLRとは何か?

KASLRは、Kernel address space layout randomizationの略で、セキュリティ目的で使用されます。これにより攻撃者がメモリ内の重要なオブジェクトがどこにあるのかを把握するのを難しくします。

HackintoshでKASLRが問題になるのは、マザーボードに連続した十分なサイズのメモリー空間がなく、カーネルが完全に収まらない場合です。そこで、slide=xxx を使って、KASLRをキャンセルしてメモリーアドレスを固定していました。このパラメータを設定すると、macOSに起動ごとに動作するランダムな領域にカーネル読み込む代わりに、動作することがわかっている場所を使用します。

このガイドが必要な人達

カーネルを読み込むメモリ空間が足りなかったり、細分化されたメモリー空間に読み込み場所が移動してしまったユーザーのためのガイドです。その場合、このようなエラーが出ます。

Error allocating 0x1197b pages at 0x0000000017a80000 alloc type 2
Couldn't allocate runtime area

以下のようなエラーが出ることもあります。

Only 244/256 slide values are usable!

または、macOSの実行中にカーネルパニックが発生することもあります。

panic(cpu 6 caller 0xffffff801fc057ba): a freed zone element has been modified in zone kalloc.4096: expected 0x3f00116dbe8a46f6 but found 0x3f00116d00000000

これらのエラーの1番の特徴は、発生にランダム性があることです。大抵は20回も起動を繰り返せば、1回くらいはエラーを出さないで起動します。

問題解決の方法

これを修正するのは非常に簡単で、その手順はCloverユーザでもOpenCoreユーザーでも同じです。Clover ユーザに必要なものは以下です。

  • Clover Shell : shell64.efi などと呼ばれているファイルを、EFI/CLOVER/tools の下に置きます。
  • OcQuirks : Aptioの修正、OsxAptioFixDrvX, AptioMemoryFixなどと混在させないでください。このガイドでは OcQuirks のみサポートします。EFI/CLOVER/drivers/UEFI の中に置きます。
  • OpenRuntime.efi : OcQuirksのパッケージに含まれます。EFI/CLOVER/drivers/UEFI の中に入れます。
  • OcQuirks.plist : (これもOcQuirksに含まれてます) EFI/CLOVER/drivers/UEFI の中に入れます。

一方、OpenCoreユーザに必要なものは以下です。

  • OpenRuntime
  • OpenShell これをconfig.plistのRoot -> Misc -> Toolsで有効にしておきます。

そして、config.plist -> Booter (OpenCore用) かOcQuirks.plist (Clover用) で以下の設定をします。

  • AvoidRuntimeDefrag: YES
    日付、時刻、NVRAM、電源制御などのUEFIランタイムサービスの修正
  • DevirtualiseMmio : YES
    Stolen Memory のサイズを削減し、slide=N値のオプションを拡張し、Z390のメモリ割り当ての問題を修正するのに非常に役立ちます。
  • EnableSafeModeSlide : YES
    slide 値をセーフモードで使用できるようにします。
  • ProtectUefiServices : NO
    UEFI サービスがファームウェアによってオーバーライドされないように保護します。主に VM、300 シリーズ、および Ice Lake や Comet Lake のような新しいシステムに関連します。
  • ProvideCustomSlide : YES
    これにより、カーネルが、読み込みに適してメモリー空間のみを選択し、起動に失敗する可能性のある場所を避けるようになります。読み込み場所のランダム性は維持していますが、ランダムに選択する際に、不適切なメモリ領域を除外するようになります。(訳注:これが従来のslide設定の役割を果たしてくれるようです。ただし、slideを固定するのではなく、使用可能なslide値を自動で探して、その中からランダムに選んでくれているようです。)
  • RebuildAppleMemoryMap : YES
    macOSと互換性のあるメモリマップを生成します。いくつかのラップトップのOEMファームウェアで失敗することがありますので、早期段階でブートに失敗する場合は、これを無効にします。

BIOS設定

BIOSを以下のように設定します。

  • BIOSを更新する (初期のBIOSにはメモリマップの問題があることが知られているので、非常に重要です, 特にZ390で重要です。)
  • CMOSを工場出荷時設定にリセットする
  • とても必要とされる以下のBIOS設定をします。
    Above4GDecoding : 有効にします。デバイスが4GB以上のメモリ領域を使用できるようになり、macOSカーネルが収まるメモリー空間が増えます。
    Boot Options -> Windows8.1/10 mode : これにより古いレガシーなゴミのようなコードがロードされなくなります。誤解されがちですが、other OSというもう一つの選択肢は、古いバージョンのWindowsを起動するための選択肢であり、LinuxやmacOSのための選択肢ではありません。(訳注:ASUSのZ490マザーボードにCatalinaを入れたマシンでは、other OSにしないと起動しませんでした。)
  • BIOS内の不要なデバイスをできるだけ多く無効にします。これにより、、起動時のマップの細分化がが減少するので、起動失敗の可能性が減ります。
    CSM : 無効にします。有効化してしまうと、レガシーサポートのため、不要なゴミの束が追加され、またUEFIシェルが起動できなくなります。
    Intel SGX : 無効にします。SGXとはSoftware Guard Extensionsの略です。有効にしても、多くのメモリ空間を占有するだけで、macOSでは何もしません。
    Parallel Port と Serial Port:無効にします。macOSではParallel portは認識しませんし、Serialを必要とする人はいないです。
    iGPU : やむを得ない場合は、これを無効にすることで、メモリを大幅に解放できます。
    Thunderbolt : 無効にします。ほとんどのマザーボードはTBを搭載していませんし、搭載していても使用しないなら、無効にすることでメモリ空間を確保できます。
    LED ligiting : 無効にします。もう光らせなくても良いでしょう。
    Legacy USB : 無効にします。これもレガシーなガラクタです。

テストブート

上記のように、EFI、config.plist、BIOSの設定を調整したら、これで起動を試してください。これでめでたく解決したら、この先の作業は不要です。まだ問題が発生する場合は、次のステップで、もっとディープな作業、つまりslide値の計算をしましょう。

slide値を探す

ブートマネージャでEFIシェルを開き、memmapを実行します。すべてのページとそのサイズのリストが以下のように表示されます。ここからが楽しみの始まりです。

Type      Start            End              # Pages          Attributes
RT_Data	  0000000000000000 0000000000000FFF 0000000000000001 800000000000000F
Available 0000000000001000 0000000000057FFF 0000000000000057 000000000000000F
Reserved  0000000000058000 0000000000058FFF 0000000000000001 000000000000000F
Available 0000000000059000 000000000008FFFF 0000000000000037 000000000000000F
RT_Code   0000000000090000 0000000000090FFF 0000000000000001 800000000000000F
Available 0000000000091000 000000000009DFFF 000000000000000D 000000000000000F
Reserved  000000000009E000 000000000009FFFF 0000000000000002 000000000000000F
Available 0000000000100000 000000005B635FFF 000000000005B536 000000000000000F
BS_Data   000000005B636000 000000005B675FFF 0000000000000040 000000000000000F
Available 000000005B676000 000000006AF77FFF 000000000000F902 000000000000000F
LoaderCode000000006AF78000 000000006B155FFF 00000000000001DE 000000000000000F
BS_Data   000000006B156000 000000006B523FFF 00000000000003CE 000000000000000F
ACPI_NVS  000000006B524000 000000006B524FFF 0000000000000001 000000000000000F
BS_Data   000000006B526000 000000006B625FFF 0000000000000100 000000000000000F
Available 000000006B626000 000000006B634FFF 000000000000000F 000000000000000F

(訳注:このあと、slide値を決める手順が書かれています。どういうわけか、一番高い番地のAvailableを第一候補として〜15ページしかないのに〜、slideが255を超えるので諦めて、順当に0x100000番地からの領域、すなわちslide=0を選択しています。ということで少し遠回りしている感じもしますし、また、こちらで解説した内容と同じなので省略します。またRedditの/r/hackintoshのdiscordにもツールらしきものがあるらしく、その説明もありますが、これも省略します。)

DevirtualiseMmioを使う

訳注:この先はあまり理解できませんでした。推測を交えて内容をまとめると、次のようなことだと思います。推測部分は間違っているかもしれません。

config.plistでDevirtualiseMmioをtrueにする

DevirtualiseMmioのデフォルトはfalseですが、これをtrueにします。これにより、カーネルを読み込む場所のメモリー使用が64から256MB程度節約できて、KASLRの失敗を防止できます。

MMIOはMemory Mapped IOのことです。入出力デバイスのコントロールをメモリーのアドレス線を利用して行うのがMMIOです。上でのべたEFI Shellのmemmapコマンドによると、E0000000番地からFFFFFFFF番地までの512MBにMMIOが割り当てられています。インテルチップセットのマザーボードなら同様のようです。この番地には、メモリーが割り当てられていたとしても、IOへのアクセスになってしまい使えません。

Type Start            End              # Pages          Attributes
MMIO 00000000E0000000-00000000EFFFFFFF 0000000000010000 800000000000100D
MMIO 00000000FE000000-00000000FE010FFF 0000000000000011 8000000000000001
MMIO 00000000FEC00000-00000000FEC00FFF 0000000000000001 800000000000100D
MMIO 00000000FED00000-00000000FED03FFF 0000000000000004 800000000000100D
MMIO 00000000FEE00000-00000000FEE00FFF 0000000000000001 8000000000000001
MMIO 00000000FF000000-00000000FFFFFFFF 0000000000001000 800000000000100D

(以下はフォーラムで教えていただいた図をもとに考えた説明です、間違っていたらすみません)EFIによってOSが起動する際に、通常はMMIOに論理アドレスを与えて(仮想化して)、論理アドレスからMMIOを使用できる状態にするようです。ただMMIOの仮想化を行わなくても(de-virtualiseしても)、物理アドレスにアクセスできるプロセスからはMMIOを使えるのでそれほど問題はないようです。MMIOを仮想化しない場合は、MMIOの部分もメモリーにマップできるので、64MBから256MBの空き領域が稼げるようです。DevirtualiseMmioフラグをtureにすると、カーネルが読み込まれる領域を少しでも多く確保できるので、起動失敗の確率を下げられます。

Kaby Lake以前のCPUでは、デフォルト通りfalseが、Coffee Lake, Comet Lakeなどではtrueにすると良いとされていますが、手元のCoffee Lake-Sではfalseでも動きました。通常はfalseで、起動失敗する場合にはtrueを試すのが良いと思います。

ちなみにどうでも良いことですが、virtualize (仮想化) をvirtualiseと書くのは英国風らしいです。

config.plistにMmioWhitelistを指定する

DevirtualiseMmioをtrueにした場合に、メモリは節約できるものの、Threadripper TRX40 19H などの一部のシステムで起動しなくなることがあるらしいです。そのような場合、DevirtualiseMmioを適用しない領域を指定するのが、これがMmioWhitelistです。これの作り方について説明がありますが省略します。ほとんどのシステムではMnioWhitelistは不要とのことです。

ここまでが抄訳です。


結局どうしたら良いのか

OpenCoreでどう対応すべきかについてまとめます。上記のガイドでは、slide値を求める方法は書いてありますが、ブートオプションなどで設定する方法にまでは書かれていませんでした。当然のことで省略されたのかもしれないですし、slideを使うことをあまり推奨していないのかもしれません。

OpenCoreのReference Manual (0.5.8) の最初の方(15ページ)には、最初にすべきことの一つに、「slideを使うな」と書いてあります。config.plistの設定からも、NVRAMのboot argumentからも削除しておくようにとのことです。そして、「No slide values are usable! Use custom slide!」というエラーが出てから検討してくださいとあります。slideは、KASLRを回避する上、SIPを緩める必要があるので、セキュリティに影響を与えるという考えなのかもしれません。まずはslideを使わなくても済む方法を試みて、それでもダメな場合だけにslideを使いましょうとの方針だと思います。

ということで、このガイドのステップをまとめると、以下になります。まずはマザーボードの設定です。

  • BIOSを更新する
  • Above 4G Decodingを有効にする
  • CSMを無効にする
  • Boot Options -> Windows8.1/10 mode にする(訳注:前述のようにother OSを選択しないと起動しないことがあります)
  • Parallel Port と Serial Portを無効にします

可能ならば以下も設定します。

  • Intel SGX : 使用していないなら無効にします。
  • Thunderbolt : 使用していないなら無効にします。
  • LED ligiting : 無効にします。
  • Legacy USB : 無効にします。

そしてconfig.plistで以下のように設定します。

  • AvoidRuntimeDefrag: true
  • DevirtualiseMmio : true
  • EnableSafeModeSlide : true
  • ProtectUefiServices : false
  • ProvideCustomSlide : true
  • RebuildAppleMemoryMap : true

設定は、環境に合わせて必要なものを選択して、正常に起動するまで少しずつ足して行くのが良いと思います。

まとめ

KASLRに関係した理由でmacOSが起動しないことへの対処法を紹介しました。基本的には、メモリー空間を確保する処置をして、KASLRの選択範囲を狭めることで対応しています。BIOS設定とOpenCoreの機能で大体の場合に対応できて、slideによるカーネル読み込み番地固定は最終手段のようです。