Ubuntu 18.04のKernelをアップデートした(HWE Kernel)
環境の整理を兼ねて、UbuntuのKernelのアップデートをしたので、そのときのメモ。
メイン Ubuntu 18.04のKernelをアップデートした(HWE Kernel)
カーネルバージョンについて
/lib/modules
を見る限りインストール時のバージョンは4.10.0-28
で、
/usr/src
を見る限り4.15.0-115
をしばらく使ったあと、
4.16.18
に更新していた。
バージョン4.10.x
はおそらくUbuntu 16.04をクリーンインストールしたときのもので、
バージョン4.15.x
はdist-upgrade
でUbuntu 18.04にアップデートしたときに変更されたと思われる。
その後ソフトウェア導入のためのバージョン合わせかなにかで4.16.x
にして、そのまま使っていた。
4.15.xから4.16.xにアップデートするときにはUKUU(Ubuntu Kernel Update Utility)を使っていた。 このとき参考にしたサイト: Upgrade Kernel on Ubuntu 18.04 – Linux Hint
ところで、カーネルバージョンの後ろに付いているハイフン以降の数字はUbuntu Release ABIというらしいのだが、 UKUUを使ってカーネルをインストールするとこの部分がバージョン番号(ハイフンの前)を6ケタの数字に直したようなものになるので、 これはABIとは違いそうだ(ABIは0から255までの範囲のように思われる)。 ABIのドキュメントらしきものがあったので、機会があれば読みたい:KernelTeam/BuildSystem/ABI - Ubuntu Wiki
UKUUとセキュリティアップデートについて
- Ubuntu 20.04 その164 - Linux kernelにDoSや任意コード実行の脆弱性・ア ップデートを - kledgeb
- USN-4489-1: Linux kernel vulnerability | Ubuntu security notices | Ubuntu
通常の方法でインストールされるカーネルを使っている場合、 Linux Kernelに脆弱性が発見されてもこのようにUbuntu側からセキュリティアップデートが提供される。
しかしUKUUを使って(起動時にデフォルトで使用する)カーネルのバージョンを変更した場合、 これは(デバッグ目的などで)カーネルのバージョンを固定しているのに近いと思われるので、 セキュリティアップデートを手動で行う必要があるのではないかという懸念があった。 実際カーネルバージョンは4.16.xインストール時から固定されていたので、 e1000eの自動ビルドはおそらく無駄で(4.10から4.15では意味があったが)、 4.16.xカーネルのセキュリティアップデートも行われていなかったのではないかと思っている。
HWEカーネルのインストール
はじめはUKUUを使って5.4.xに アップデートしたものの、 前項の懸念からUbuntuが公式に提供しているHWEカーネルというものを使うことにした。 これならばaptでカーネルが管理され、自動でパッチが適用されるものと思われる。
- Ubuntuのベースバージョンを変えずにLinuxカーネルをアップグレードする方法 - iberianpigsty
- 第278回 Ubuntuカーネルとの付き合い方:Ubuntu Weekly Recipe|gihyo.jp … 技術評論社
Ubuntu 18.04の場合、linux-generic-hwe-18.04
が安定版、linux-generic-hwe-18.04-edge
が開発版ということだろうか。
今回は安定性を重視するので安定版を選んでアップデートする。
sudo apt install linux-generic-hwe-18.04
このあとから以下のようになったので、このシステムでaptが管理していたカーネルバージョンは4.15.0-115
だったことがわかる。
The following packages were automatically installed and are no longer required:linux-headers-4.15.0-115 linux-headers-4.15.0-115-genericlinux-image-4.15.0-115-generic linux-modules-4.15.0-115-genericlinux-modules-extra-4.15.0-115-generic
余談:UKUUを使ったカーネル導入とデフォルトカーネルとgrubの設定
ここで、grubのデフォルトエントリがUKUUで入れた5.4.xカーネルのままで、 新たにインストールしたUbuntu HWEカーネルではなかった。 この項ではこの理由を検討するが、実際にはgrub(またはUbuntuに同梱されているgrub)のデフォルトの挙動であったので、 余談である。
(カーネルインストール時に自動で呼ばれるが)update-grub
実行時には以下 のように表示される。
$ sudo update-grubSourcing file `/etc/default/grub'Generating grub configuration file ...Found linux image: /boot/vmlinuz-5.4.42-050442-generic <-- UKUUで入れたカーネル(デフォルト)Found initrd image: /boot/initrd.img-5.4.42-050442-genericFound linux image: /boot/vmlinuz-5.4.0-47-generic <-- 新しいカーネル(HWE)Found initrd image: /boot/initrd.img-5.4.0-47-genericFound linux image: /boot/vmlinuz-4.16.18-041618-generic <-- 現在のカーネル(UKUU)Found initrd image: /boot/initrd.img-4.16.18-041618-genericFound linux image: /boot/vmlinuz-4.15.0-117-generic <-- 未使用?Found initrd image: /boot/initrd.img-4.15.0-117-genericFound linux image: /boot/vmlinuz-4.15.0-115-generic <-- aptが管理しているカーネルFound initrd image: /boot/initrd.img-4.15.0-115-genericFound Windows Boot Manager on /dev/sdc1@/EFI/Microsoft/Boot/bootmgfw.efiAdding boot menu entry for EFI firmware configurationdone
/boot/grub/grub.cfg
をみると、
menuentry 'Ubuntu' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-ed8cbed5-714e-4201-b606-c41d570f834d' {recordfailload_videogfxmode $linux_gfx_modeinsmod gzioif [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fiinsmod part_gptinsmod ext2set root='hd0,gpt1'if [ x$feature_platform_search_hint = xy ]; thensearch --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt1 --hint-efi=hd0,gpt1 --hint-baremetal=ahci0,gpt1 ed8cbed5-714e-4201-b606-c41d570f834delsesearch --no-floppy --fs-uuid --set=root ed8cbed5-714e-4201-b606-c41d570f834dfilinux /boot/vmlinuz-5.4.42-050442-generic root=UUID=ed8cbed5-714e-4201-b606-c41d570f834d ro quiet splash $vt_handoffinitrd /boot/initrd.img-5.4.42-050442-generic}
このような記述があったので、grubメニューの0番目に表示されるUbuntu
という項目は5.4.42-050442
のカーネルを起動するようになっていることがわかる。
この設定はapt install linux-generic-hwe-18.04
のあとupdate-grub2
しても変わらなかった。
おそらくUKUUが自動で設定したと思われる(以下で調べるが、実際には違った)が、これをaptの管理するカーネルになるようにしたい。
### BEGIN /etc/grub.d/10_linux ###
とあるので、この部分は/etc/grub.d/10_linux
からインクルードされている。
grub.d以下は標準出力をgrub.cfgに書き出し、エラー出力をupdate-grub
したときに 表示するような
シェルスクリプトになっているようで、自動的にカーネルイメージを見つける作りになっているようだ。
grubのメニューにはUbuntu
(menuentry)、Advanced options for Ubuntu
(submenu./men)のように並ぶ。
【 grub2-set-default/grub-set-default 】コマンド――GRUB 2のデフォルト起動メニューを設定する:Linux基本コマンドTips(277) - @IT
一番上のmenuentryを生成している/etc/grub.d/10_linux
の一部:
linux_entry "${OS}" "${version}" simple \"${GRUB_CMDLINE_LINUX} ${GRUB_CMDLINE_LINUX_DEFAULT}"
関数linux_entryでは、第3引数にsimple
が指定されている場合、以下のようなコード(一部)でmenuentryを生成する。
linux_entry (){os="$1"version="$2"type="$3"args="$4"(略)echo "menuentry '$(echo "$os" | grub_quote)' ${CLASS} \$menuentry_id_option 'gnulinux-simple-$boot_device_id' {" | sed "s/^/$submenu_indentation/"(略)sed "s/^/$submenu_indentation/" << EOFlinux ${rel_dirname}/${basename} root=${linux_root_device_thisversion} ro ${args}EOF
${rel_dirname}
や${basename}
は関数version_find_latest $list
から生成しているようだ。
menuentryのループを回している部分では、以下のようにversion_find_latest $list
を呼び出していて、
is_top_level
は初回のループでtrue
になり、このとき一番上のメニューを生成する。
関数linux_entry
を呼び出しているところは上に書いたものと同じ部分である。
is_top_level=truewhile [ "x$list" != "x" ] ; dolinux=`version_find_latest $list`(略)if [ "x$is_top_level" = xtrue ] && [ "x${GRUB_DISABLE_SUBMENU}" != xy ]; thenlinux_entry "${OS}" "${version}" simple \"${GRUB_CMDLINE_LINUX} ${GRUB_CMDLINE_LINUX_DEFAULT}"submenu_indentation="$grub_tab"if [ -z "$boot_device_id" ]; thenboot_device_id="$(grub_get_device_id "${GRUB_DEVICE}")"fi# TRANSLATORS: %s is replaced with an OS nameecho "submenu '$(gettext_printf "Advanced options for %s" "${OS}" | grub_quote)' \$menuentry_id_option 'gnulinux-advanced-$boot_device_id' {"is_top_level=falsefi
$list
は以下のように生成される。
list=for i in /boot/vmlinuz-* /vmlinuz-* /boot/kernel-* ; doif grub_file_is_not_garbage "$i" ; then list="$list $i" ; fidone ;;
これで問題は関数version_find_latest
のアルゴリズムということがわかった。
関数version_find_latest
は/etc/grub.d/10_linux
にはないので、
おそらくファイル先頭近くにある. "$pkgdatadir/grub-mkconfig_lib"
の部分で読み出されていると思われる。
$pkgdatadir
というのがどこかわからなかったので、find
で雑に検索を掛けたところ、/usr/lib/grub/grub-mkconfig_lib
を読み出していそうなことがわかった。
以下は/usr/lib/grub/grub-mkconfig_lib
の一部である。確かにversion_find_latest
があった。
version_test_gt (){version_test_gt_sedexp="s/[^-]*-//;s/[._-]\(pre\|rc\|test\|git\|old\|trunk\)/~\1/g"version_test_gt_a="`echo "$1" | sed -e "$version_test_gt_sedexp"`"version_test_gt_b="`echo "$2" | sed -e "$version_test_gt_sedexp"`"version_test_gt_cmp=gtif [ "x$version_test_gt_b" = "x" ] ; thenreturn 0fi# GRUB_FLAVOUR_ORDER is an ordered list of kernels, in decreasing# priority. Any items in the list take precedence over other kernels,# and earlier flavours are preferred over later ones.for flavour in ${GRUB_FLAVOUR_ORDER:-}; doversion_test_gt_a_preferred=$(echo "$version_test_gt_a" | grep -- "-[0-9]*-$flavour\$")version_test_gt_b_preferred=$(echo "$version_test_gt_b" | grep -- "-[0-9]*-$flavour\$")if [ -n "$version_test_gt_a_preferred" -a -z "$version_test_gt_b_preferred" ] ; thenreturn 0elif [ -z "$version_test_gt_a_preferred" -a -n "$version_test_gt_b_preferred" ] ; thenreturn 1fidonecase "$version_test_gt_a:$version_test_gt_b" in*.old:*.old) ;;*.old:*) version_test_gt_a="`echo "$version_test_gt_a" | sed -e 's/\.old$//'`" ; version_test_gt_cmp=gt ;;*:*.old) version_test_gt_b="`echo "$version_test_gt_b" | sed -e 's/\.old$//'`" ; version_test_gt_cmp=ge ;;esacdpkg --compare-versions "$version_test_gt_a" "$version_test_gt_cmp" "$version_test_gt_b"return "$?"}version_find_latest (){version_find_latest_a=""for i in "$@" ; doif version_test_gt "$i" "$version_find_latest_a" ; thenversion_find_latest_a="$i"fidoneecho "$version_find_latest_a"}
GRUB_FLAVOUR_ORDER
がおそらく/etc/default/grub
で指定されていなければ、
dpkg --compare-versions PKG_A COMPARATOR PKG_B
によってソートされそうなことがわかった。
ここでUKUUについて調べてみると、 UKUUは2019年1月に有料化しているようだが、 ppa:teejee2008/ppaとコードベースは残 っていた。
gothicVI January 22, 2019 at 1:18 am
So ukuu now completely turned into a closed source project?
Tony George January 22, 2019 at 12:09 pm
Yes. Older versions are still open-source. Somebody can develop that version further if they have the time and interest. I may open the source again if I stop working on it (it won’t happen anytime soon).
grubの設定をいじっているソースコードを探したところ、update-grub
を呼び出すくらいで特に優先度を設定するようなことはしていなさそうだったので、
dpkg --compare-versions
を使ったソートの結果、単純に最初に来たカーネルがデフォルト(一番上のmenuentry)に使われていそうとわかった(なにも特殊なことはない普通の動作だ..)。
ukuu/LinuxKernel.vala#L1298 at master · teejee2008/ukuu
問題はdpkgがUbuntu HWEカーネルよりUKUUで入れた5.4.xカーネルの方が新しいと判断していることが原因で、
UKUUは特殊なことをしていないとわかったので、
単純にUKUUから入れたカーネルを削除してupdate-grub
すればデフォルトが(もっとも新しい)HWEカーネルになりそうだと わかった。
一度別のカーネル(HWEでOK)で起動して、UKUUのGUIを使ってUKUU側の5.4.xを削除(ふつうに選択してRemove)すればデフォルトでもっとも新しいHWEカーネルが起動するようになる。
(準備中)e1000eのDKMS設定
Intel NICのドライバe1000eについて
$ find /lib/modules/5.4.0-47-generic -name e1000e*/lib/modules/5.4.0-47-generic/kernel/drivers/net/ethernet/intel/e1000e/lib/modules/5.4.0-47-generic/kernel/drivers/net/ethernet/intel/e1000e/e1000e.ko
このようにデフォルトでe1000eのドライバがカーネルに付属しているようなのだが、
(デバイスによっては?)チェックサム検証に失敗(The NVM Checksum Is Not Valid
by netdev.c
)する問題がある。
# lspci -vvv00:1f.6 Ethernet controller: Intel Corporation Ethernet Connection (2) I219-VSubsystem: Intel Corporation Ethernet Connection (2) I219-VControl: I/O- Mem+ BusMaster- SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx-Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-Interrupt: pin A routed to IRQ 16Region 0: Memory at df100000 (32-bit, non-prefetchable) [size=128K]Capabilities: [c8] Power Management version 3Flags: PMEClk- DSI+ D1- D2- AuxCurrent=0mA PME(D0+,D1-,D2-,D3hot+,D3cold+)Status: D0 NoSoftRst+ PME-Enable- DSel=0 DScale=1 PME-Capabilities: [d0] MSI: Enable- Count=1/1 Maskable- 64bit+Address: 00000000fee00338 Data: 0000Capabilities: [e0] PCI Advanced FeaturesAFCap: TP+ FLR+AFCtrl: FLR-AFStatus: TP-Kernel modules: e1000e <-- これが動かない
エラーログ
$ zegrep e1000e /var/log/kern.log*kernel: [ 1.296005] e1000e: Intel(R) PRO/1000 Network Driver - 3.2.6-kkernel: [ 1.296006] e1000e: Copyright(c) 1999 - 2015 Intel Corporation.kernel: [ 1.296023] e1000e 0000:00:1f.6: enabling device (0000 -> 0002)kernel: [ 1.296199] e1000e 0000:00:1f.6: Interrupt Throttling Rate (ints/sec) set to dynamic conservative modekernel: [ 1.546779] e1000e 0000:00:1f.6: The NVM Checksum Is Not Validkernel: [ 1.588850] e1000e: probe of 0000:00:1f.6 failed with error -5
そのため結局は無効にして自分でビルドする必要がある。
ダウンロード Linux * での PCIe * Intel®ギガビット・イーサネット・ネットワーク接続向けインテル®ネットワーク・アダプター・ドライバー
解凍したあと、src/nvm.c
のe1000e_validate_nvm_checksum_generic
が0を返すように編集する。
# チェックサム検証のスキップsed -i "/s32 e1000e_validate_nvm_checksum_generic(struct e1000_hw \*hw)/N;s/\n{/\n{return 0;/" nvm.c
UKUUとe1000eのビルドについて
今回はUKUU を使わないためこれは余談なのだが、UKUUで導入したカーネルでe1000eをビルドするときには、チェックサム検証の問題に加えてABIに関連した問題が起こる。
ここにe1000eのソースコード(kcompat.h
)の一部を引用するが、以下のようにe1000eのプログラム内でABIのチェックが行われていて、
4.16.xのカーネルをUKUUで導入した際はこのバージョンチェックをコメントアウトする必要があった。
/* Ubuntu Release ABI is the 4th digit of their kernel version. You can find* it in /usr/src/linux/$(uname -r)/include/generated/utsrelease.h for new* enough versions of Ubuntu. Otherwise you can simply see it in the output of* uname as the 4th digit of the kernel. The UTS_UBUNTU_RELEASE_ABI is not in* the linux-source package, but in the linux-headers package. It begins to* appear in later releases of 14.04 and 14.10.** Ex:* <Ubuntu 14.04.1>* $uname -r* 3.13.0-45-generic* ABI is 45** <Ubuntu 14.10>* $uname -r* 3.16.0-23-generic* ABI is 23*/(略)#if UTS_UBUNTU_RELEASE_ABI > 255#error UTS_UBUNTU_RELEASE_ABI is too large...#endif /* UTS_UBUNTU_RELEASE_ABI > 255 */
# ABIチェックのスキップsed -i "s/#error UTS_UBUNTU_RELEASE_ABI is too large.../\/\/#error UTS_UBUNTU_RELEASE_ABI is too large.../" kcompat.h
他にe1000e以外で注意が必要かもしれない点として、 お そらくメジャーバージョンが1ケタであるせいで、この数字の始まりが0からになっているため、 これを直接Cコードに埋め込んだりすると8進数扱いされて(さらに数字に8以上が含まれていて)ビルドが通らないということがあった(どのソフトウェアか覚えていないが)。
これまでのe1000e自動ビルドについて
Linuxにはカーネルバージョンをアップデートしたときにドライバなどのモジュールを再ビルドするための DKMS(Dynamic Kernel Module Support)というソフトウェアがあるのだが、 導入当時はこれを使うキャパシティがなかったので、当時でもなんとなく使い方のわかっていたsystemdを使って 起動時に毎回e1000eを自動ビルド・再インストールするという荒い方法で継続的に動作させていた。
/etc/systemd/system/uscript-e1000e.service
[Unit]Description=Make Install e1000e[Service]Type=oneshotExecStart=/etc/uscript/e1000eTimeoutSec=0StandardOutput=ttyRemainAfterExit=yesSysVStartPriority=99[Install]WantedBy=multi-user.target
/etc/uscript/e1000e
#!/bin/bashmodprobe -r e1000emake clean -C /etc/uscript/e1000e-latest/srcmake install -C /etc/uscript/e1000e-latest/srcmodprobe e1000e
カーネルバージョンを更新するにあたって、DKMSに移行することとし、これは不要になったので削除した。
$ sudo systemctl stop uscript-e1000e.service$ sudo systemctl disable uscript-e1000e.serviceRemoved /etc/systemd/system/multi-user.target.wants/uscript-e1000e.service.$ sudo rm /etc/systemd/system/uscript-e1000e.service$ sudo rm /etc/uscript/e1000e$ sudo modprobe -r e1000e$ sudo make uninstall -C /etc/uscript/e1000e-latest/src$ sudo rm -r /etc/uscript/e1000e-latest
DKMSを使ったe1000e自動ビルドについて
Ubuntu 16.04でRTL8189FTV (RTL8188FU)ドライバのDKMS化 (r271-635)
これを参考にカーネルアップデート時に自動でリビルドするDKMSに対応させる作業をした。
まず、あらかじめdkms
をインストールしておく。
もし先にカーネルを更新してしまってdkms
を取得できないときは、一度手動でe1000e
をビルドすればOK(make uninstall
を忘れずに)。
sudo apt install dkms
まずは/usr/src
以下にソースディレクトリをコピーする。
今回の場合、e1000e-3.8.4.tar.gz
を解凍したe1000e-3.8.4
ディレクトリを/usr/src/e1000e-3.8.4
としてコピーする。
そして/usr/src/e1000e-3.8.4/dkms.conf
を作成する。
dkms.confの細かい説明:Ubuntu Manpage: dkms - Dynamic Kernel Module Support
ディレクトリ構造
| /usr/src/e1000e-3.8.4/|-- README|-- dkms.conf <-- New!|-- ...|-- src/|----- Makefile|----- e1000.h|----- ...
dkms.conf
PACKAGE_NAME="e1000e"PACKAGE_VERSION="3.8.4"CLEAN="cd src; make clean"BUILT_MODULE_NAME[0]="e1000e"BUILT_MODULE_LOCATION[0]="src/"DEST_MODULE_NAME[0]="e1000e-dkms"MAKE[0]="cd src; make -j$(nproc)"DEST_MODULE_LOCATION[0]="/updates/dkms"AUTOINSTALL="yes"REMAKE_INITRD="yes"
これだけでDKMSに登録する準備が完了した。次はDKMSにこのソースディレクトリを登録する。
DKMSはデフォルトで/usr/src
以下のディレクトリを見に行くように思われる。
$ sudo dkms add e1000e/3.8.4Creating symlink /var/lib/dkms/e1000e/3.8.4/source ->/usr/src/e1000e-3.8.4DKMS: add completed.
/var/lib/dkms/e1000e/3.8.4/source
からのシンボリックリンクが張られ、DKMSに登録された。DKMSから削除するには:
$ sudo dkms remove e1000e/3.8.4 --all------------------------------Deleting module version: 3.8.4completely from the DKMS tree.------------------------------Done.
次はビルドしてみる。
$ sudo dkms build e1000e/3.8.4Kernel preparation unnecessary for this kernel. Skipping...Building module:cleaning build area...cd src; make -j8....Signing module:- /var/lib/dkms/e1000e/3.8.4/5.4.0-47-generic/x86_64/module/e1000e-dkms.koSecure Boot not enabled on this system.cleaning build area...DKMS: build completed.
そしてインストール。
$ sudo dkms install e1000e/3.8.4e1000e-dkms:Running module version sanity check.- Original module- No original module exists within this kernel- Installation- Installing to /lib/modules/5.4.0-47-generic/updates/dkms/depmod...DKMS: install completed.
デフォルトのe1000e
を無効化する。
sudo modprobe -r e1000e# 再起動時にロードされないようにnouveauにならって設定しようとしたが、うまくいかなかった# printf "# disable default e1000e driver; use self-built version instead.\nblacklist e1000e\n" | sudo tee /etc/modprobe.d/blacklist-e1000e.conf
自動でモジュールが読み込まれないと思われるので、dkms
の方のe1000e
をmodprobe
を使って手動で読み込む。
sudo modprobe e1000e-dkmsmodinfo e1000e-dkms