はじめに
こちらは NEC デジタルテクノロジー開発研究所 Advent Calendar 2023 22日目の記事です。
17日目の記事に引き続き2回目の登場となります。一回ポッキリのつもりでしたが、セキュリティ関連の投稿をすると、なんとCPEが申請できるとのことで、CISSPホルダーとしては2回目の投稿のモチベーションがものすごくわきました。ということで、2日目はARM64のセキュリティ関連のネタになります。なお、手元の実行環境は引き続き、動作環境は前回と同じ、RaspberryPi Model B(8GByte)です。
もう一つの動機
私は、実務では32bitまでしか経験がありません(ARMv7-A)。16進数でEで始まるのが多くないとARMのアセンブラな気がしないです(苦笑)。そんな私が、実は17日目の記事を書くに当たり、atomic処理の一つ目のアセンブラを見て、「何だこれ?見たことがない!」と気になってしまい、記事を書く前にかなり調べて、貴重な週末の時間を潰したのでしたとても勉強になりました。
その見たことがなかったアセンブラが以下の1500番地のモノです。
0000000000001500 <__aarch64_ldadd8_acq_rel>:
1500: d503245f bti c
1504: f00000f0 adrp x16, 20000 <_ZNSo3putEc@GLIBCXX_3.4>
1508: 39436610 ldrb w16, [x16, #217]
150c: 34000070 cbz w16, 1518 <__aarch64_ldadd8_acq_rel+0x18>
1510: f8e00020 ldaddal x0, x0, [x1]
1514: d65f03c0 ret
#ここから先を一日目には出しました。
うん?bti c
、えー、"ブランチ ターゲット イ・・・・?"1
BTIはセキュリティ機能
bti c
は、調べたところARMv8.5(のextension)で追加されたセキュリティ機能の一つ2で、Branch target identification(BTI)が正式名称です。これは、何かをものすごく簡単に言うと、
分岐した先がbtiじゃないとダメ
というものです。この機能により、悪意を持ったプログラムが、分岐先を何がしかの手段で勝手に書き換えても、「任意の場所には飛ばせません」ということになります。ARMの場合、BRやBLRが任意の場所に飛べる命令になりますので、そこが狙われることになりますが、BR/BLRで飛んだ先はBTIである必要があるのです。なお、BTIには、bti c
(関数ジャンプのBLRに適用)、bti j
(switch case文とか、jumpテーブルでのBRに適用)の二種類と、その2種類が同時2設定出来るbti jc
という3種類あります。先程は、関数コールのようなものなので、bti c
でした。
エレガントだな〜と思ったのが、この命令をサポートしていないCPUからはNOP(何もしない命令)にみえるという、後方互換性バッチリなところです。
書き換えて不正な状態にしたら何が起こるかやってみた
1日目のプログラムのatomicを有効にしたバージョンを例に、btiを別の命令、ここではNOPに書き換えて何が起こるか検証してみましょう。地味に、cliを駆使して書き換えていきます。
xxd a.out a.dump
emacs a.dump
あ、emacs派だとバレますね。3
000014e0: 34f0 7dd3 f7ff ff17 0000 0090 0040 1591 4.}..........@..
000014f0: 50fe ff97 1f20 03d5 1f20 03d5 1f20 03d5 P.... ... ... ..
00001500: 5f24 03d5 f000 00f0 1066 4339 7000 0034 _$.......fC9p..4
00001510: 2000 e0f8 c003 5fd6 f003 00aa 20fc 5fc8 ....._..... ._.
00001520: 1100 108b 31fc 0fc8 afff ff35 c003 5fd6 ....1......5.._.
1500番地のところを、NOP命令に書き換えてあげます。objdumpの結果の周囲を見渡せば、NOPはあちこちにありまして、d503201f
です。エンディアンの関係でちょっと順番が違いますが、そこは我慢。(なお、右側の文字表示部分は変更しません)
000014e0: 34f0 7dd3 f7ff ff17 0000 0090 0040 1591 4.}..........@..
000014f0: 50fe ff97 1f20 03d5 1f20 03d5 1f20 03d5 P.... ... ... ..
00001500: 1f20 03d5 f000 00f0 1066 4339 7000 0034 _$.......fC9p..4
00001510: 2000 e0f8 c003 5fd6 f003 00aa 20fc 5fc8 ....._..... ._.
00001520: 1100 108b 31fc 0fc8 afff ff35 c003 5fd6 ....1......5.._.
戻します。
xxd -r a.dump a.out
objdumpでみてみます。
0000000000001500 <__aarch64_ldadd8_acq_rel>:
1500: d503201f nop
1504: f00000f0 adrp x16, 20000 <_ZNSo3putEc@GLIBCXX_3.4>
1508: 39436610 ldrb w16, [x16, #217]
150c: 34000070 cbz w16, 1518 <__aarch64_ldadd8_acq_rel+0x18>
1510: f8e00020 ldaddal x0, x0, [x1]
1514: d65f03c0 ret
ちゃんと、NOPに変わっているではありませんか。
それでは、実行してみましょう。おそらく例外で落ちるのだと推測していました。実行する前までは。
user@raspberrypi:~ $ ./a.out
100000000
あれ?動いていますけど。
そういえば、BTIはextensionでした
(この間ちょっとした時間が経過しております)カーネルをビルドするときのCONFIGは、CONFIG_ARM64_BTI=y
とソフトウエアのビルドの観点ではサポートしているようではありますが、ネットの情報や、カーネルソースを読むと、ハードウエアがサポートしている場合、ログで
CPU features: detected: Branch Target Identification
というのが出るようです。手元の環境を確かめてみますと、
user@raspberrypi:~ $ dmesg | grep Branch
user@raspberrypi:~ $
ログにBranchという言葉さえありません。 なんと未サポートでした! 残念。後方互換性まで用意してあるextensionですから、100%サポートしているわけではありません・・・とはわかっていましたが、最初に調べろってはなしですね。じゃぁと思って、AWS環境で検証しようとも思ったのですが、AWS Graviton4を搭載したR8gでようやくサポート。執筆時点ではまだプレビューです。実行結果は断念することにしました。
さいごに
不正な場合の実行結果をお見せできなかったのは非常に残念ですが、いかがでしょうか。コンパイラが用意したBTIという着地点以外には、ジャンプできないよ・・・という機能は、悪意を持ったアプリケーションが分岐先を都合の良いように書き換える攻撃の手段をだいぶ奪うのではないかと思います。今回はatomicのライブラリ実装のところでみつけたのが私にとっての契機でしたが、このようなライブラリはもちろんのこと、コンパイラ、カーネルなどいたるところに影響がある機能がちゃんと実装されるところにも凄さを感じました。
本記事では紹介しませんでしたが、対で紹介されることも多い、特に戻りアドレスの改ざんを防ぐPAC2も合わせることでかなり強固になると思います。ご興味がありましたら、調べてみてください。