U-BootでUBIFS内のLinuxを起動する|セキュリティごった煮ブログ

ネットエージェント
セキュリティごった煮ブログ

 コース:こってり

U-BootでUBIFS内のLinuxを起動する

scott

こんにちは、scottです。
今日の3分ハッキングでは組み込み系機器でよく使われているブートローダであるDas U-Boot(以下U-Bootと記述)で、LinuxでNANDフラッシュのいちファイルシステムであるUBIFS[1]内に保存されているLinux (Debian 9)を起動してみたいと思います。

......といっても、今時の機器ですと、記憶装置としてNANDフラッシュを直接実装するのではなく、SDカードのようにソフトウェアから手軽に扱えるeMMCを実装することも多く、(話題のRaspberry Piに至っては(micro)SDカードそのものですね!)そのような環境ではUBIFSを使うこともない(というか使えない)と思いますが、ググってみてもあまり情報が日本語で出てこなかったのでメモついでに書いておきたいと思います。

U-Bootの用意

まずはUBIFS対応のU-Bootを用意します。
ほとんどの環境ではUBIFS対応されていないU-Bootが使われていると思いますので、その場合(ubifsload等のコマンドが無い場合)はUBIFS対応のU-Bootのバイナリをつくりましょう!
もちろん、対応しているU-Bootが既に手元にある場合はこの章を飛ばしてかまいません。(思ったよりこの章が長くなってしまった......。)

私が最近試したU-Bootは2017.09で、Marvell社が開発したSheevaplugというコンピュータ上で動かしていましたので、以降これを前提に書きます。一部機種依存なところがありますが、コマンドの使い方等はお役立てできると思います。

まず一通りお好みでconfigを設定した後、make nconfigCommand line interface 内にある以下の2つにチェックを入れましょう。

  • Enable UBI - Unsorted block images commands
  • Enable UBIFS - Unsorted block images filesystem commandsEnable UBI...にチェックを入れると出てくる)

そろそろ文字だらけで記事が殺風景になってきていますね......無駄に図にすると以下のような感じです。

これだけでUBIのサポートやMTD partition supportなども自動的にチェックが入るので楽です。
また、Command line interfaceBoot commands から bootz コマンドを有効にしておきます。(Debianのvmlinuzを読む時に使います。)

次にビルド......といきたい所ですが、いくつか注意ポイントがあったので手当て(patch)しておきます。

  • gccのバージョンによってはU-Bootが使うCの規格とズレが生じてコンパイルに支障が出ることがあったので明示的に指定しておく(ついでに CROSS_COMPILE も書いちゃう)
diff a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -2,6 +2,9 @@
 # SPDX-License-Identifier:     GPL-2.0+
 #

+CROSS_COMPILE=arm-none-eabi-
+KCFLAGS=-std=gnu90
+
 VERSION = 2017
 PATCHLEVEL = 09
 SUBLEVEL =
  • Sheevaplug固有?: なぜか最適化オプションを-Osや-O2にするとまったくU-Bootを起動できなくなる[要調査]ので、-O1にする(以下のpatchでは General setupOptimize for size が無効の前提)
diff a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -590,7 +590,7 @@ ifeq ($(CONFIG_XTENSA),)
 ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE
 KBUILD_CFLAGS  += -Os
 else
-KBUILD_CFLAGS  += -O2
+KBUILD_CFLAGS  += -O1
 endif

 KBUILD_CFLAGS += $(call cc-option,-fno-stack-protector)
  • Sheevaplug固有: U-Boot形式でくるまれていないinitrdに対応していると言いはっておく(さもなくばDebian上で生成したinitrd.imgを無加工で読み込めない)
diff a/include/configs/mv-common.h b/include/configs/mv-common.h
--- a/include/configs/mv-common.h
+++ b/include/configs/mv-common.h
@@ -66,6 +66,7 @@
 #define CONFIG_CMDLINE_TAG     1       /* enable passing of ATAGs  */
 #define CONFIG_INITRD_TAG      1       /* enable INITRD tag */
 #define CONFIG_SETUP_MEMORY_TAGS 1     /* enable memory tag */
+#define CONFIG_SUPPORT_RAW_INITRD 1

 #define        CONFIG_SYS_CBSIZE       1024    /* Console I/O Buff Size */

  • 私固有: NANDフラッシュをカツカツに使っていたので、むりやり調整
diff a/include/configs/sheevaplug.h b/include/configs/sheevaplug.h
--- a/include/configs/sheevaplug.h
+++ b/include/configs/sheevaplug.h
@@ -23,7 +23,10 @@
 /*
  * Standard filesystems
  */
-#define CONFIG_SYS_MVFS
+/*#define CONFIG_SYS_MVFS*/ /* depends on bzip2 */
+/* following two configs are done by CONFIG_SYS_MVFS in mv-common.h */
+#define CONFIG_MTD_DEVICE               /* required for mtdparts commands */
+#define CONFIG_MTD_PARTITIONS

 /*
  * mv-plug-common.h should be defined after CMD configs since it used them

@@ -42,8 +42,8 @@
  * it has to be rounded to sector size
  */
 #define CONFIG_ENV_SIZE                        0x20000 /* 128k */
-#define CONFIG_ENV_ADDR                        0x80000
-#define CONFIG_ENV_OFFSET              0x80000 /* env starts here */
+#define CONFIG_ENV_ADDR                        0x60000
+#define CONFIG_ENV_OFFSET              0x60000 /* env starts here */

 /*
  * Default environment variables

さて、手当てが終わったら、ビルドしましょう。

host$ make
  CHK     include/config/uboot.release
  CHK     include/generated/version_autogenerated.h
  CHK     include/generated/timestamp_autogenerated.h
...
(中略)
...
  LD      u-boot
  OBJCOPY u-boot.srec
  OBJCOPY u-boot-nodtb.bin
  COPY    u-boot.bin
  SYM     u-boot.sym
  MKIMAGE u-boot.kwb
  CHK     include/config.h
  CFG     u-boot.cfg
  CFGCHK  u-boot.cfg
host$

できましたね。Sheevaplug向けの設定の場合はu-boot.kwbというファイルをNANDフラッシュの先頭に書くことで、次回起動時から今ビルドしたU-Bootを使ってくれるようになります。

ちなみに私は、いきなりNANDフラッシュに書いて文鎮になるのは怖かったので、その前にOpenOCD 0.2.0(とても古い)をVirtualBox 5系(新しい)上で動くVineLinux 4.2(とても古い)上で動かしてJTAG経由でu-boot.kwbをSDRAM上にロードして確認していました[2]。新しめのOpenOCD(0.10.0など)では動かなかったので、このようなとても古い環境を使いました。

ファイルシステムの用意

がんばってUBIFS上に用意しましょう。その説明は一本の記事にできるくらい長くなると思うので、今回は割愛します。(めんどくさいわけではない)
ただし今回はUBIFS内のカーネルファイルを起動しようとしているので、 /boot を別パーティションにする必要はありません。

また、次に説明するコマンドを簡単・変更不要にするために以下のsymlinkを / に用意しておきます。

  • /vmlinuzboot/vmlinuz-4.9.0-5-marvell
  • /initrd.imgboot/initrd.img-4.9.0-5-marvell
  • /devicetree.dtbusr/lib/linux-image-4.9.0-5-marvell/kirkwood-sheevaplug.dtb

リンク先パスは導入しようとしているLinuxのバージョンや環境によって適宜読み替えてください。

なお最初2つ(vmlinuz, initrd.img)は、Debianのlinux-image系のパッケージをインストールすると自動で作成されるはずなので、実質最後の devicetree.dtb のみ手動で作成することになるはずです。

【本題】U-BootからUBIFSを操作

というわけで御膳立てが無駄に長くなってしまいましたが、U-BootでUBIFS内にあるLinuxを起動するコマンドをゆったりと紹介したいと思います。

今回扱うNANDフラッシュの中身は以下のようになっているとします。

まずはパーティションを定義しましょう。

setenv mtdparts mtdparts=orion_nand:384k(uboot)ro,128k(config),4608k(dummy),-(fs)

上記パラメータ(パーティション構成、サイズ)は私の場合なので、適宜アドレス・サイズは変更してください。ここでは"fs"(384k+128k+4608k=5120k ~ NANDフラッシュ最後まで)内にUBIFSが鎮座しているとします。

mtdparts という変数に mtdparts= から始まる内容を設定するのがすごくモヤっとしますが、そういう仕様なのでしかたありません。

続いて、UBIFSなUBI volumeがどのパーティションに入っているかをU-Bootに教えます。

ubi part nand0,3

ここで nand0,33 の部分には、UBIFSがあるパーティションを0オリジンで指定します。今回は mtdparts で0オリジンで3番目にUBIFSを置いているので 3 としています。(fs のように名前で指定できなさそうなのが悲しい。)

これを実行し正しく終了すると以下のようなログが出てくると思います。

ubi0: attaching mtd1
ubi0: scanning is finished
ubi0: attached mtd1 (name "mtd=3", size 507 MiB)
ubi0: PEB size: 131072 bytes (128 KiB), LEB size: 129024 bytes
ubi0: min./max. I/O unit sizes: 2048/2048, sub-page size 512
ubi0: VID header offset: 512 (aligned 512), data offset: 2048
ubi0: good PEBs: 4056, bad PEBs: 0, corrupted PEBs: 0
ubi0: user volume: 1, internal volumes: 1, max. volumes count: 128
ubi0: max/mean erase counter: 264/141, WL threshold: 4096, image sequence number: 298739845
ubi0: available PEBs: 0, total reserved PEBs: 4056, PEBs reserved for bad PEB handling: 40

そうしたら、どのUBI volumeをUBIFSとして扱っていいかをU-Bootに教えます。

ubifsmount ubi0:rootfs

ここで rootfs の部分にはUBI volume名を指定します。あなたの環境に合うように適宜読み替えてください。

ここまで来ると、UBIFS内を覗けるようになっているはずです。 ubifsls /bin などとしてみると確認できると思います。

それでは本題のLinuxのカーネル、initramfs、FDTのメモリ上への読み込みをしましょう。

ubifsload 800000 /vmlinuz
ubifsload c00000 /initrd.img
ubifsload f00000 /devicetree.dtb

800000 などの部分にはロード先アドレス、 /vmlinuz などの部分にはロードしたいファイルを指定します。それぞれ別ファイルやU-Boot自体に被らないように余裕を持たせたアドレスを指定してあげましょう。
釈迦に説法かもしれませんが、環境によっては 0 番地以外にSDRAMがマップされているかもしれないので注意しましょう。

カーネル等がロードできたら、カーネルコマンドラインを設定してあげましょう。

set bootargs ${mtdparts} console=ttyS0,115200 root=ubi0:rootfs ro rootfstype=ubifs ubi.mtd=fs 

ここで rootfs の部分はUBI volume名、fs の部分はUBIなパーティションの名前(mtdpartsでつけた名前)を指定します。
console のパラメータ変更や別のパラメータの追加はお好みでどうぞ。

さてここまで来たらカーネルを起動するほかないので、やっちゃいましょう!

bootz 800000 c00000:300000 f00000

パラメータの数値はそれぞれ以下の通りです。

  • 800000: vmlinuzのアドレス
  • c00000: initrd.imgのアドレス
  • 300000: initrd.imgのサイズ(本来より大きくても大丈夫のようです)
  • f00000: dtbのアドレス

これを実行したら、おそらくLinuxが起動されてくると思います 。できなかったら......ごめんなさい。

上記のコマンド群をまとめると、以下のようになります。

setenv mtdparts mtdparts=orion_nand:384k(uboot)ro,128k(config),4608k(dummy),-(fs)
ubi part nand0,3
ubifsmount ubi0:rootfs
ubifsload 800000 /vmlinuz
ubifsload c00000 /initrd.img
ubifsload f00000 /devicetree.dtb
set bootargs ${mtdparts} console=ttyS0,115200 root=ubi0:rootfs ro rootfstype=ubifs ubi.mtd=fs 
bootz 800000 c00000:300000 f00000

※パラメータはこれまでに紹介したままなので適宜読み替えてください。

うまく起動できたら、mtdparts 変数を設定した後、bootcmd 変数に ubi part コマンド以降の内容をセミコロン区切りで ubi part nand0,3; ubifsmount ...; ...; bootz ... のように設定し、saveenv コマンドで保存しておけば、次回から自動で起動するようになります。(ここでsymlinkが活きてきます。)
なお bootcmd 変数を設定する際、たとえば set bootargs ...を含めようとしている場合、 $ をエスケープしてあげる必要があると思うので、そこだけ注意してください。

カーネル等のロード先アドレスに余裕を持たせて設定しておけば、カーネル、initrd.imgやdtbファイルの更新時にU-Bootの変数を書き換える必要がないのでおすすめです。

それでは。


  1. UBIFS: NANDフラッシュの特性を考慮したファイルシステムのひとつ。NANDフラッシュではHDDなどと違い、同じ場所に何度もデータを書き込むと、その場所にはデータが正しく書けなくなるという特性がある。UBIFSのようなファイルシステムには他にjffs2、yaffsなどがある。UBIFSはjffs2に比べて高速マウントできるなどの特長がある。詳しくはWeb Google検索で。 ↩︎

  2. OpenOCD使ってJTAG経由で確認: これは本当にメモ中のメモなんですが、resetinitsheevaplug_initload_image u-boot.kwb 0x5FFE00verify_image u-boot.kwb 0x5FFE00resume 0x600000 といった感じのコマンドをOpenOCDに送っていました。0x5FFE00というのは、U-Bootのロードアドレスおよびエントリポイントが0x600000で、u-boot.kwb のヘッダが(U-Boot 2017.09時点で)0x200バイトであるためそのぶん引いた値です。なお reset 後は毎回 u-boot.kwbload_image する必要があります。なぜなら resetsheevaplug_init 間はSDRAMのリフレッシュが止まっており、1~2秒であってもメモリ上のデータは化けているからです! 私はこれに気付かず数十分を無駄にしました。 ↩︎

メルマガ読者募集 セキュリティを学ぶ

月別