きっかけ
Check Point V-80でOpenWrtをLinux Kernel 6.6を使用してビルドしブートした際、U-BootからLinux Kernel 6.1同様に正しく blkdevparts
が渡っているにも関わらず、パースに失敗してeMMC内のパーティションテーブルにあるパーティション1つしか認識されなかった。
Linux Kernel 6.6
[ 0.000000] Linux version 6.6.25 (musashino205@TAIHA.NET) (aarch64-openwrt-linux-musl-gcc (OpenWrt GCC 13.2.0 r25822-49e9adfdd0) 13.2.0, GNU ld (GNU Binutils) 2.40.0) #0 SMP Sat Apr 13 03:47:41 2024 ... [ 0.000000] Kernel command line: console=ttyS0,115200 earlycon=uart8250,mmio32,0xf0512000 crashkernel=30M mvpp2x.queue_mode=1 blkdevparts=mmcblk1:48M@10M(kernel-1),1M(dtb-1),720M(rootfs-1),48M(kernel-2),1M(dtb-2),720M(rootfs-2),300M(default_sw),650M(logs),1M(preset_cfg),1M(adsl),-(storage) maxcpus=4 [ 0.000000] Unknown kernel command line parameters "crashkernel=30M", will be passed to user space. ... [ 0.884016] mmc1: new HS200 MMC card at address 0001 [ 0.889951] mmcblk1: mmc1:0001 004GA0 3.69 GiB [ 0.895043] cmdline partition format is invalid. [ 0.895704] mmcblk1: p1 [ 0.903447] mmcblk1boot0: mmc1:0001 004GA0 2.00 MiB [ 0.908667] mmcblk1boot1: mmc1:0001 004GA0 2.00 MiB [ 0.913765] mmcblk1rpmb: mmc1:0001 004GA0 512 KiB, chardev (248:0)
Linux Kernel 6.1
[ 0.000000] Linux version 6.1.82 (musashino205@TAIHA.NET) (aarch64-openwrt-linux-musl-gcc (OpenWrt GCC 13.2.0 r25822-49e9adfdd0) 13.2.0, GNU ld (GNU Binutils) 2.40.0) #0 SMP Sat Apr 13 03:47:41 2024 ... [ 0.000000] Kernel command line: console=ttyS0,115200 earlycon=uart8250,mmio32,0xf0512000 crashkernel=30M mvpp2x.queue_mode=1 blkdevparts=mmcblk1:48M@10M(kernel-1),1M(dtb-1),720M(rootfs-1),48M(kernel-2),1M(dtb-2),720M(rootfs-2),300M(default_sw),650M(logs),1M(preset_cfg),1M(adsl),-(storage) maxcpus=4 ... [ 0.953142] mmc1: new HS200 MMC card at address 0001 [ 0.959114] mmcblk1: mmc1:0001 004GA0 3.69 GiB [ 0.964259] mmcblk1: p1(kernel-1) p2(dtb-1) p3(rootfs-1) p4(kernel-2) p5(dtb-2) 6(rootfs-2) p7(default_sw) p8(logs) p9(preset_cfg) p10(adsl) p11(storage) [ 0.979174] mmcblk1boot0: mmc1:0001 004GA0 2.00 MiB [ 0.984674] mmcblk1boot1: mmc1:0001 004GA0 2.00 MiB [ 0.989926] mmcblk1rpmb: mmc1:0001 004GA0 512 KiB, chardev (248:0
調べた
blkdevparts
をパースしてパーティションを構成しているのは block/partitions/cmdline.c であり、これにバグがあるかもしれないと仮定して調査。
ひとまずコードを辿って文字列を処理している辺り(parse_parts()
周辺)を眺めつつhistoryを漁ってみると、直近で文字列のコピーを strncpy()
から strscpy()
に変更しているコミットを発見。
これが最も怪しいので、コード弄って実機で試すこととした。
検証
とりあえず適当に文字列を吐かせてみる。
[ 0.873842] parse_parts: newparts->name -> "mmcblk" [ 0.878764] parse_parts: "48M@10M(kernel-1),1M(dtb-1),720M(rootfs-1),48M(kernel-2),1M(dtb-2),720M(rootfs-2),300M(default_sw),650M(logs),1M(preset_cfg),1M(adsl),-(storage)" [ 0.894274] parse_parts: length=17, strscpy ret=-7 [ 0.899101] parse_subpart: "48M@10M(kernel-1" [ 0.903506] cmdline partition format is invalid.
どうやら parse_parts()
には正しく blkdevparts
のパラメータが渡っているものの、 parse_subpart()
に渡った文字列が尻切れになり )
が欠落してエラーになっている模様。
strncpy()
と strscpy()
の仕様を確認してみると、前者はNULL終端 '\0'
をハンドルせず、コピー後にコピー先バッファがNULL終端されていようがいまいが指定した count
で単純に終了して返るものの、後者はNULL終端をハンドルしており、指定した count
はNULL終端分1文字を含む必要がある模様。
それ故に、cmdline.c で strncpy()
で渡していたものと同じ count
を渡した場合、渡される文字列が指定された count
内でNULL終端されておらず、コピーがNULL終端されないまま終わってしまうので dest[count - 1]
に '\0'
がセットされ -E2BIG
で返り、尻切れとなる模様。
対処
strscpy()
に渡される count
をNULL終端 '\0'
分 +1 してやる。
length = (!next) ? (sizeof(buf) - 1) : min_t(int, next - bdevdef, sizeof(buf) - 1);
となっているので、
length = (!next) ? (sizeof(buf) - 1) : min_t(int, next - bdevdef + 1, sizeof(buf));
に変更。
追記: min_t()
で取っている2つのうち、後者の -1
は元々 strncpy()
でコピーした後 '\0'
を詰める為確保しているものなので、NULL終端をハンドルする strscpy()
では不要なので削る。
そうすると、尻切れせずパースが正しく通るようになる。(しかしNULL終端せず strscpy()
に渡るので -E2BIG
が返っているのは同じ。でもこれは無視できる)
[ 0.872571] mmc1: new HS200 MMC card at address 0001 [ 0.879495] mmcblk1: mmc1:0001 004GA0 3.69 GiB [ 0.884674] parse_parts: newparts->name -> "mmcblk" [ 0.889592] parse_parts: "48M@10M(kernel-1),1M(dtb-1),720M(rootfs-1),48M(kernel-2),1M(dtb-2),720M(rootfs-2),300M(default_sw),650M(logs),1M(preset_cfg),1M(adsl),-(storage)" [ 0.904995] parse_parts: length=18, strscpy ret=-7 [ 0.909812] parse_subpart: "48M@10M(kernel-1)" [ 0.914287] parse_parts: "1M(dtb-1),720M(rootfs-1),48M(kernel-2),1M(dtb-2),720M(rootfs-2),300M(default_sw),650M(logs),1M(preset_cfg),1M(adsl),-(storage)" [ 0.928095] parse_parts: length=10, strscpy ret=-7 [ 0.932912] parse_subpart: "1M(dtb-1)" [ 0.936686] parse_parts: "720M(rootfs-1),48M(kernel-2),1M(dtb-2),720M(rootfs-2),300M(default_sw),650M(logs),1M(preset_cfg),1M(adsl),-(storage)" [ 0.949616] parse_parts: length=15, strscpy ret=-7 [ 0.954436] parse_subpart: "720M(rootfs-1)" [ 0.958640] parse_parts: "48M(kernel-2),1M(dtb-2),720M(rootfs-2),300M(default_sw),650M(logs),1M(preset_cfg),1M(adsl),-(storage)" [ 0.970263] parse_parts: length=14, strscpy ret=-7 [ 0.975082] parse_subpart: "48M(kernel-2)" [ 0.979199] parse_parts: "1M(dtb-2),720M(rootfs-2),300M(default_sw),650M(logs),1M(preset_cfg),1M(adsl),-(storage)" [ 0.989608] parse_parts: length=10, strscpy ret=-7 [ 0.994428] parse_subpart: "1M(dtb-2)" [ 0.998196] parse_parts: "720M(rootfs-2),300M(default_sw),650M(logs),1M(preset_cfg),1M(adsl),-(storage)" [ 1.007725] parse_parts: length=15, strscpy ret=-7 [ 1.012539] parse_subpart: "720M(rootfs-2)" [ 1.016748] parse_parts: "300M(default_sw),650M(logs),1M(preset_cfg),1M(adsl),-(storage)" [ 1.024969] parse_parts: length=17, strscpy ret=-7 [ 1.029783] parse_subpart: "300M(default_sw)" [ 1.034167] parse_parts: "650M(logs),1M(preset_cfg),1M(adsl),-(storage)" [ 1.040900] parse_parts: length=11, strscpy ret=-7 [ 1.045719] parse_subpart: "650M(logs)" [ 1.049575] parse_parts: "1M(preset_cfg),1M(adsl),-(storage)" [ 1.055353] parse_parts: length=15, strscpy ret=-7 [ 1.060167] parse_subpart: "1M(preset_cfg)" [ 1.064375] parse_parts: "1M(adsl),-(storage)" [ 1.068841] parse_parts: length=9, strscpy ret=-7 [ 1.073572] parse_subpart: "1M(adsl)" [ 1.077254] parse_parts: "-(storage)" [ 1.080934] parse_parts: length=67, strscpy ret=10 [ 1.085753] parse_subpart: "-(storage)" [ 1.090231] mmcblk1: p1 [ 1.093727] mmcblk1boot0: mmc1:0001 004GA0 2.00 MiB [ 1.099531] mmcblk1boot1: mmc1:0001 004GA0 2.00 MiB [ 1.105090] mmcblk1rpmb: mmc1:0001 004GA0 512 KiB, chardev (248:0)
余談
ここまで来たが、パースはされているのに結局 mmcblk1
にパーティションが適用されていないことに気付いた。
よく確認したら newparts->name
のパースも尻切れで正しく取れていないので、ここも要修正。あとたぶん parse_subpart()
でパーティション名取る箇所も要修正だと思う。
その後
とかいい感じに謎が解けたと思ったら、Mastodonで教えて頂いたところによれば既に大体同じ修正が投げられていた模様。ただし指摘が入りその後v2が投げられているものの、メンテナ等からの反応が皆無で止まってしまっている様子。
どうしたものか。