スクエニ ITエンジニア ブログ

SecureBootにてkernel lockdownを外す話

はじめに

Linuxを今時のPCに入れて使ってるのにUEFI+SecureBoot経由じゃないのは小学生までと思うネバー・フレンズ・Tです。今回は早速SecureBootを使うようになって困った件と回避策について語ってみます。

SecureBootではdyndbg機能が動かない!?

なにかのデバイスをLinuxマシンに追加しようとして、うまく動かなくて大ハマリした経験はLinux使いの人だと誰しもあると思います。弊社でも山ほどの物理マシンに搭載したLinux機がありますので、そういう問題に直面することがあります。まあ、大体はベンダのサポートの皆様にヘルプを要請するとなんとかなることが多いですが、なんでもサポート契約のあるような機材ばかりと思うなよ?という状況に遭遇することがあります。例えば一時的に会社の民生品をつないで、動作させなければならない場合などでしょうか。

ここで、外付けデバイスの調子が悪い場合に役に立つのがLinux kernelの持つデバイスドライバに搭載されているデバッグ用のログ出力(dyndbg機能) となります。ところが、今時のUEFI+SecureBootの環境と、特定のディストリビューションの組み合わせでは、dyndbg機能が封じられてしまっているという問題に直面することがあります。

dyndbg機能が動かないことを確かめる

実際にUEFI+SecureBootで起動したDebian unstableでやってみます。現在SecureBootが有効か?をmokutilで確認してから、kernelのnet/ipv4/ping.cのデバッグ用ログを有効化してみます。

$ uname -a
Linux debian-sid 5.16.0-4-amd64 #1 SMP PREEMPT Debian 5.16.12-1 (2022-03-08) x86_64 GNU/Linux
$ lsb_release -dirc
Distributor ID: Debian
Description:    Debian GNU/Linux bookworm/sid
Release:        unstable
Codename:       sid
$ sudo apt install mokutil
$ mokutil --sb-state
SecureBoot enabled

Debianで、SecureBootが有効ですね。では早速デバッグ用ログを有効化します。

# echo 'file net/ipv4/ping.c +p' > /sys/kernel/debug/dynamic_debug/control
bash: /sys/kernel/debug/dynamic_debug/control: Operation not permitted

root成りしたのに操作拒否られるってどういうこと!?このままでは納得行かないので、早速ログにヒントが無いかを確認します。

# journalctl | tail 
...中略...
Mar 06 14:32:58 debian-sid kernel: Lockdown: bash: debugfs access is restricted; see man kernel_lockdown.7

Lockdownとな? 早速man kernel_lockdown.7して見てみます。manによると、Secure BootモードでLinuxを立ち上げた際に、debug機能などkernelに影響を及ぼすような一部の操作を封印する機能のようです。

kernelのことわりに干渉できるほどの強力な力は禁忌として封印というのは、厨二病なエンジニアのハートをくすぐるシチュエーションです。主人公ピンチのときの能力解除イベントとか熱いですよね!

kernel lockdownを解除しよう!

kernel lockdownを解除するには、2通りの方法があります。

  1. UEFI側でSecure Bootを根こそぎOFFにする。
  2. ブートローダにお願いしてkernelにSecureBootではないモードで動くようにしてもらう

残念ですが、kernel lockdownの解除はいずれもリブートを伴うようです。まあ、面倒な手続きなしに解除できたらそれはそれで情報セキュリティ強化の施策としては弱い気がするので仕方ありません。1のやり方は、とても簡単ですが何らかの理由(デュアルブートとか、特殊な機材相手)でUEFI側のSecureBootを解除したくない事情もあると思います。そもそも負けた気がするのでパスしましょう!今回は2のやり方を実施してみます。

さっそくブートローダにお願いをしてみます。Debianの場合、デフォルトのUEFI+SecureBoot対応ブートローダはshim+grub2の組み合わせです。一応確認しておきましょう。

$ sudo ls /boot/efi/EFI/debian
BOOTX64.CSV  fbx64.efi	grub.cfg  grubx64.efi  mmx64.efi  shimx64.efi

shimx64.efiがshimであり、UEFI側からSecureBootの状態で最初に起動されるブートローダ(1st stage boot loader)です。そのあとgrubx64.efiが呼び出されgrub.cfgの内容に従ってLinuxの/bootディレクトリ以下のgrub.cfgを探しあて、その内容に従いLinux kernelであるところの/boot/vmlinuz-5.16.0-4-amd64を起動します。

UEFIをサポートしているPCではNVRAM上にEFI Variableという記憶領域が用意されています。Debianではこちらを使ってshim側と連携し、kernelのブート時の振る舞いを変更できます。この時便利なツールとしてmokutilがあります。後のshimによる解除手続きにて、ブート時にパスワードを指示にしたがって打ち込む必要がありますので、説明のため"123456789"としています。さらに納得感を得るためEFI Variableがどう変わるか?もあわせて示してみます。EFI Variableを覗き見るツールはefivarとなります。

$ sudo apt install efivar
$ efivar -l | fgrep -i mok
605dab50-e046-4300-abb6-3dd810dd8b23-MokListXRT
605dab50-e046-4300-abb6-3dd810dd8b23-MokListRT
$ sudo mokutil --disable-validation
password length: 8~6
input password: 123456789
input password again: 123456789
$ efivar -l | fgrep -i mok
605dab50-e046-4300-abb6-3dd810dd8b23-MokSB  <--新しくできる
605dab50-e046-4300-abb6-3dd810dd8b23-MokListXRT
605dab50-e046-4300-abb6-3dd810dd8b23-MokListRT
$ sudo efivar -n 605dab50-e046-4300-abb6-3dd810dd8b23-MokSB
MokSB
GUID: 605dab50-e046-4300-abb6-3dd810dd8b23
Name: "MokSB"
Attributes:
        Non-Volatile
        Boot Service Access
        Runtime Service Access
Value:
00000000  00 00 00 00 09 00 00 00  31 00 32 00 33 00 34 00  |........1.2.3.4.|
00000010  35 00 36 00 37 00 38 00  39 00 00 00 96 55 00 00  |5.6.7.8.9....U..|
00000020  88 ff ff ff ff ff ff ff                           |........        |

きちんと"605dab50-e046-4300-abb6-3dd810dd8b23-MokSB"という変数に平文でパスワードが打ち込まれています。なお、“MokSB"の前の"605dab50-e046-4300-abb6-3dd810dd8b23"はGUIDと呼ばれ、用途によって異なるIDになっています。GUIDの用途については、efivarである程度知ることができます。

$ efivar -L                                                               
{00000000-0000-0000-0000-000000000000} {zero} efi_guid_zero zeroed sentinal guid                      
{ff3e5307-9fd0-48c9-85f1-8ad56c701e01} {sha384} efi_guid_sha384 SHA-384                               
{826ca512-cf10-4ac9-b187-be01496631bd} {sha1} efi_guid_sha1 SHA-1                                     
{a7717414-c616-4977-9420-844712a735bf} {rsa2048_sha256_cert} efi_guid_rsa2048_sha256_cert RSA 2048 with SHA-256 Certificate                                                                                 
{82988420-7467-4490-9059-feb448dd1963} {lenovo_me_config} efi_guid_lenovo_me_config Lenovo ME Configuration Menu                                                                                            
{c1c41626-504c-4092-aca9-41f936934328} {sha256} efi_guid_sha256 SHA-256                               
{126a762d-5758-4fca-8531-201a7f57f850} {lenovo_boot_menu} efi_guid_lenovo_boot_menu Lenovo Boot Menu  
{0b6e5233-a65c-44c9-9407-d9ab83bfc8bd} {sha224} efi_guid_sha224 SHA-224                               
{67f8444f-8743-48f1-a328-1eaab8736080} {rsa2048_sha1} efi_guid_rsa2048_sha1 RSA 2048 with SHA-1       
{605dab50-e046-4300-abb6-3dd810dd8b23} {shim} efi_guid_shim shim              
...中略...

というわけで、MokSBの"605dab50-e046-4300-abb6-3dd810dd8b23"は"efi_guid_shim shim"とありますから、ブートローダのshimとのやりとり用途の変数になります。

これで準備完了ですので、早速リブートしてみます。すると、いつものgrub2のブート画面とは異なる画面がshimにより現れます。

キー待ち

ここでなにかキーを押して下さい。画面の下の方にあるカウントダウンが0になるまでにキーを押さないと、再度mokutilにてパスワード打ち込みからやり直しとなってしまいます。キーを押すと、メニュー画面が現れます。

Mokメニュー

“Change Secure Boot state"をカーソルキーの上下で選んでRetrunキーを押下します。 するとパスワードを尋ねる画面が現れます。

Password画面

画面に"Enter Password Charactor N:"(Nは数字)と現れます。Nはmokutilで設定したパスワード(今回は"123456789”)のN文字目という意味です。8と表示されているので、パスワード文字列の8番目である8を押してReturnキーを押下します。この画面、パスワードの指定された文字を押したあとに都度Retrunキー押下が必要です。こちらを数回繰り返します。 うまく指示通りにパスワードの文字を入力できると、次の画面になります。

SecureBoot解除

Yes/Noを聞いてきますので、カーソルキー上下で"Yes"を選択してReturnキー押下します。

Reboot要求

“Reboot"をカーソルの上下で選んでReturnキーを押下するとブートが始まります。

dyndbg機能が無事動作した

いつものgrub2が表示される直前に一瞬ですが、“Booting in insecure mode"なるメッセージが表示されます。これが出たらgrub2以降はSecure Bootが外れた状態で起動されます。

早速、ログインして確かめてみましょう。まずは、EFI Variableの変化を確認します。

$ mokutil --sb-state
SecureBoot enabled
SecureBoot validation is disabled in shim
$ efivar -l | fgrep -i mok
605dab50-e046-4300-abb6-3dd810dd8b23-MokSBStateRT <--増えてる
605dab50-e046-4300-abb6-3dd810dd8b23-MokListXRT
605dab50-e046-4300-abb6-3dd810dd8b23-MokListRT
$ efivar -n 605dab50-e046-4300-abb6-3dd810dd8b23-MokSBStateRT
GUID: 605dab50-e046-4300-abb6-3dd810dd8b23
Name: "MokSBStateRT"
Attributes:
	Boot Service Access
	Runtime Service Access
Value:
00000000  01                                                |.               |

mokutilによれば、“SecureBoot validation is disabled in shim"とあるので、なにかSecure Bootのチェックが外されたとのメッセージです。また、リブート前のMokSB変数は消えてしまい、変わりに、MokSBStateRTが新しく追加され、値が0x01となりました。kernelが起動するときに、MokSBStateRT==0x01であると、SecureBootでは無いことにしてほしいという意味としてkernelでも利用されています。

早速、dyndbg機能を有効化してみましょう。

# echo 'file net/ipv4/ping.c +p' > /sys/kernel/debug/dynamic_debug/control
# ping 8.8.8.8
<ctrl-C>
# journalctl -f 
Mar 06 15:52:58 debian-sid-secure kernel: ping_unhash(isk=0000000004946f007,isk->num=0)
Mar 06 15:52:58 debian-sid-secure kernel: ping_unhash(isk=000000000883aae94,isk->num=0)
...中略..

無事、net/ipv4/ping.cに埋め込まれているdyndbgのログが残るようになりました。

また、ここでは詳しくは述べませんが、SecureBootモードでできなかったことは全部できるようになります。

追加でできるようになる例:

  1. 未サイン状態のkernelのブート
  2. 未サイン状態のkernelのモジュールのロード 等

kernel lockdownで困ったこと無いよ?

debian系列以外のディストリビューション(例:CentOS等)を使っている人はSecureBoot配下でも本問題に遭遇したことないよ?という方もいらっしゃると思います。それもそのはず。debian系列のディストリビューションのkernelには、以下の独自パッチがあたっているため発生します。

具体的には、

$ apt-get source linux-image-5.16.0-4-unsigned
$ cd linux-image-5.16.0-4/debian/patches/featcures/all/lockdown/
$ ls 
arm64-add-kernel-config-option-to-lock-down-when.patch
efi-add-an-efi_secure_boot-flag-to-indicate-secure-b.patch
efi-lock-down-the-kernel-if-booted-in-secure-boot-mo.patch
mtd-disable-slram-and-phram-when-locked-down.patch

となります。こちらの一連のパッチが、SecureBootであることをEFI Variableから読み取って強制的にkernel lockdown を発動するようになっています。

Sysreq+xでkernel lockdownが外れるという噂を聞いたよ?

きちんとman kernel_lockdown.7を読んだ賢明な皆様におかれましては、本問題に遭遇した直後にマニュアル通りSysReq+xキーを連打したかと思います。でも一向に解除される気配がありません。最初は自分もkernel lockdownのガードの固さに絶望して「動け、動け、動け、動いてよ!」とSysReq+xキーをガチャガチャしてました。

実は以前のkernelでは実際にSysReq+xで外れるようになっていたらしい(linux-kernel-5.4系あたり)のですが、/proc/sysrq-triggerに’x’を書き込んでも外れてしまうじゃん?というので解除の方法として残しておくにはふさわしくない…という結論となり、 Bug 947021 で、Debian側でも本操作は永久に葬られてしまったとのことです。

以上から、今度はmanの方が間違いとなります。こちらについても、 Bug 989580 にてbug報告している人がいるので、いつかmanも修正されるでしょうね。さらに、実際に本manページのupstreamである man kernel_lockdown.7 側では Patch: Remove additional text alluding to lifting via SysRq のmanに対するパッチの通り、キー操作で外れるという記述はすでに削除済になってます。

おわりに

ここでは、UEFI+SecureBootでのkernel lockdownの解除についてお話しました。さあ、これで皆さんもkernelの制限を解除してデバッグし放題ライフをお過ごしください。

この記事を書いた人

記事一覧
SQUARE ENIXでは一緒に働く仲間を募集しています!
興味をお持ちいただけたら、ぜひ採用情報ページもご覧下さい!