TT Lab
はじめる
学ぶ 学習パス コース

閉域網へのGPUドライバ搬入と導入

ドライバより先に壊れるもの、カーネルヘッダ

TT Labで続きを見る

一言でいうと

ドライバーの持ち込みで、最もよく壊れるのは、ドライバーではなく、カーネルヘッダーです。DKMSは、実行中のカーネルと正確に同じバージョンのビルドツリーで、モジュールをビルドして、持ち込んだヘッダーが1文字でも違えば、コンパイルを始める前に止まります。Secure Bootが有効なサーバーなら、そうして作ったモジュールに署名して、そのキーをファームウェアに登録する段階まで、持ち込みの手続きに入ります。

なぜ必要なのか

前のモジュールでは、ダミーの.debで、ドライバーの依存関係とインストール順序を練習しました。実際のNVIDIAドライバーは、再配布できないからです。ところが、現場の事故は、たいていその次に起きます。「ドライバーパッケージはインストールされたのに、nvidia-smiがcouldn't communicate with the NVIDIA driver」。カーネルモジュールが作られていないのです。原因をたどると、ヘッダーがなかったり、取得したヘッダーがlinux-headers-generic(最新のカーネルを指すメタパッケージ)で、実行中のカーネルと違っていたり、モジュールは作られたのに、Secure Bootが署名のないモジュールを拒否したりした場合です。

どう動くのか

DKMSのルール: DKMSのドキュメントによると、ソースは/usr/src/<PACKAGE_NAME>-<PACKAGE_VERSION>/に置き、その中のdkms.confには、PACKAGE_NAME・PACKAGE_VERSIONが必ず必要です。モジュールが複数なら、BUILT_MODULE_NAME[#](.koなし)が必要で、DEST_MODULE_LOCATION[#]は、/kernelで始まる必要がありますが、Ubuntu・Fedora・RHELなどでは、無視されます(ディストリビューションが決めた場所に入れます)。AUTOINSTALL="yes"なら、新しいカーネルが入るとき、autoinstallがもう一度ビルドします。MAKE[#]を書かなければ、カーネルのビルドツリーのmakeが使われて、KERNELRELEASEが付きます。CLEANは、最近のDKMSが、使わないようにと警告します。

コマンドの順序: dkms addがソースを登録し、dkms build -k <커널>が(プレースホルダーはカーネルです)、/var/lib/dkms/<이름>/<버전>/<커널>/<아키텍처>/module/に(プレースホルダーは、名前、バージョン、カーネル、アーキテクチャです)モジュールを作成して、dkms installが、カーネルのモジュールツリーに移します。対象のカーネルのヘッダーがないと、ドキュメントに書かれたとおり、Your kernel headers for kernel <버전> cannot be found at /lib/modules/<버전>/build or /lib/modules/<버전>/sourceで止まります(プレースホルダーはバージョンです。dkmsのソースの終了コードは21ですが、このラボのVMのdkms 3.0.11は、コマンド全体を1で終えました。スクリプトは、コードの値ではなく、0ではないかを見てください)。dkms statusの形は、版ごとに少し違います。最近の版は、이름/버전, 커널, 아키텍처: installedです(プレースホルダーは、名前、バージョン、カーネル、アーキテクチャです)。

ヘッダーはカーネル文字列のとおりに: Ubuntuのヘッダーは、linux-headers-<uname -r>(フレーバーごと)と、それが要求するlinux-headers-<버전>(共通)に分かれます(プレースホルダーはバージョンです)。持ち込み一覧は、uname -rから始まる必要があります。linux-headers-genericは便利ですが、取得する日の最新のカーネルに従うので、エアギャップ環境のサーバーのカーネルと、ずれやすいです。

vermagic: モジュールには、どのカーネル用にビルドされたかを書いた、vermagic文字列が入っていて、modinfo -F vermagicで見られます。別のカーネル用のモジュールは、ロードされません。modprobeは、depmodが作成した一覧で、依存モジュールまで読み込み、insmodは、ファイル1つを読み込むだけです。

Secure BootとMOK: Ubuntuのドキュメントによると、Secure Bootが有効なシステムでは、DKMSは、新しいモジュールを、MOK(Machine Owner Key)で自動的に署名します。キーは、/var/lib/shim-signed/mok/MOK.priv・MOK.derで、なければ作成して、登録要求を出します。登録は、次の起動の、MokManager画面で、パスワードを入力して終わります。リモートでしかアクセスできないエアギャップ環境のサーバーなら、コンソール作業を、持ち込みの日程に入れる必要があるという意味です。mokutil --sb-stateが状態を、mokutil --importが、DERキーの登録要求を作成します。強制モードで、署名のないモジュールは、Key was rejected by serviceで拒否され、強制しないカーネルは、module verification failed: signature and/or required key missing - tainting kernelを残して、読み込みます。

現場での姿

このラボのVM(Ubuntu 24.04、カーネル6.8.0-139-generic、dkms 3.0.11)で測ってみました。ヘッダーがないとき、存在しないカーネルでビルドさせると、Error! Your kernel headers for kernel 6.8.0-9999-generic cannot be found at ...の次の行に、Please install the linux-headers-6.8.0-9999-generic package or use the --kernelsourcedir optionが出ました。持ち込んだヘッダーでビルドしたモジュールは、/lib/modules/6.8.0-139-generic/updates/dkms/airgap_hello.ko.zstに、圧縮されて入りました。興味深いのは、署名です。Secure Bootを使えないVM(mokutil --sb-stateがEFI variables are not supported on this system)なのに、DKMSはモジュールに署名して、modinfo -F signerは、<호스트이름> Secure Boot Module Signature keyでした(プレースホルダーはホスト名です)。ところが、ロードするとき、カーネルは、module verification failed: signature and/or required key missing - tainting kernelを残しました。署名はあるけれど、そのキーが、カーネルが信頼するキーの一覧にないからです。強制モードだったなら、ここで拒否されたはずで、そのため、MOKの登録が、持ち込みの手続きに入ります。

エアギャップ環境のGPUサーバーのカーネルが、持ち込みの間に更新されると、ドライバーはそのままなのに、次の起動からモジュールがなくなります。AUTOINSTALLが、新しいカーネルでもう一度ビルドしようとしても、そのカーネルのヘッダーが、持ち込まれていないからです。カーネルパッケージと、そのカーネルのヘッダーは、常に同じ持ち込みのまとまりで入れる必要があります。このラボのVMは、Secure Bootを有効にできないので、署名の段階は、記録だけで確認します。その限界は、ラボの案内にも書きました。また、このVMのクラウドイメージには、実行中のカーネル(測った日は6.8.0-139-generic)のヘッダーが、linux-headers-virtualを通じて、最初からインストールされていました。エアギャップ環境のサーバーは、たいていそうではないので、ラボの準備ステップが、そのヘッダーを削除して、「ヘッダーのないサーバー」から始まるようにしました。

次のラボですること

実行中のカーネルを確認して、そのカーネルのヘッダーの2つのパッケージを、インストールせずに取得して、ハッシュの一覧を作成します。外部をふさいで、持ち込んだ.debでヘッダーをインストールしたあと、20行のGPLモジュールを、DKMSでadd・build・installして、パラメータとともにロードします。vermagic・署名・Secure Bootの状態を記録して、ヘッダーがないカーネルでビルドさせて、DKMSのエラーを採取します。

参考ドキュメント: dkms(8)・Ubuntu — UEFI Secure Boot・mokutil(1)・Kernel module signing・modprobe(8)