教育用RISC-V Davis In-Order CPUの論文を読む
Davis In-Order CPUというカリフォルニア大学デービス校のコンピューターアーキテクチャの授業で使われている、RISC-V ISAを実装したCPUがある。少し前に論文として上がっていたので読んでみた。
論文のPDFはこちら。
カリフォルニア大学デービス校では長い間、コンピューターアーキテクチャの授業ではLogisimが使われていたが、学生から非常に不評だったとのこと。論文には、学生からのアンケートのコメントと思わしき文章が掲載されていた。
- “there isn’t much documentation on Logisim online”
- “I hate logisim with a passion.”
そこで、Chiselを使って設計したDINO CPUを使い、Chiselを教える講義に変更したとのこと。 そして実際に 2クォーター分講義を実施したところ、評価が高かったそうだ。
- “Very challenging but rewarding course. Please keep using Chisel in the future!”
また、そこでChiselの知識を身に着け、企業のインターンシップに参加した学生もいたらしい。
Additionally, after teaching this course for the first time, one student reported that they received an internship offer because they had Chisel experience
講義では座学に加え、4つの課題が出され評価がくだされる。
- ALU Control Unitの実装 (Chiselとハードウェア記述言語になれさせる目的のため)
- アプリケーションを動かせるようにするために、CPUの全体を設計
- パイプラインの実装
- パイプラインに分岐予測を実装
この課題の内容はすべてGitHubに掲載されており、リポジトリをクローンすればカリフォルニア大学デービス校の学生でなくとも取り組むことができるみたいだ。
DINO CPUについては、以下の特徴を備えたCPUであるとのこと。
- シンプルなデザインで、教育用途に作られており、わかりやすいコードを心がけて実装されている。
- RISC-V ISA(RV32I)が実装されており、Cのプログラムをコンパイルして実行することができる
- CSR命令 ecall ebreakは実装していない
- 将来的にはこれらの命令をサポートすることにフォーカスするとのこと
基本的にはPatterson & HennessyのRISC-V Editionに掲載されているCPUをベースに作られているが、Patterson & Hennessyの本ではRV32Iのうち命令のエンコード形式がR形式の一部しか実装されていないのに対し、DINOはそれ以外の特権命令をのぞくほぼすべての命令を実装しているのが大きな違いとなっている。
デジタル回路について予備知識がない人にもChiselが理解できるようにマテリアルを追加したということも書いてあった。Chisel Bootcampなどの資料はVerilogなどの他のHDLの知識を必要としているところがあったのは確かにそのとおり。
However, most of the Chisel documentation targets graduate students and professionals with architecture and digital design background. Therefore, we supplemented this documentation with our own slimmed down documentation which only contained details required for completing the DINO CPU assignments.
Chiselからハードウェア設計を学ぶ上で、DINO CPUは最適かもしれない。
講義ノートもすべて公開されているみたいだ。良い時代になったものだ。
Zephyr OSを使ってみた
Zephyr OSというリアルタイムOSを使うことになったので、その環境構築時の手順のメモ。
$ wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.10.1/zephyr-sdk-0.10.1-setup.run $ ./zephyr-sdk-0.10.1-setup.run -- -d ~/zephyr-sdk-0.10.1
環境変数に設定しておくために、.bashrcに以下の設定を記入しておく。
export ZEPHYR_TOOLCHAIN_VARIANT=zephyr export ZEPHYR_SDK_INSTALL_DIR=${HOME}/zephyr-sdk-0.10.1
$ pip3 install --user west $ west init zephyrproject $ cd zephyrproject $ west update $ sudo pip3 install --user -r zephyr/scripts/requirements.txt
ビルドを行う。
$ cd zephyr $ source zephyr-env.sh $ west build -b litex_vexriscv samples/subsys/shell/shell_module -- west build: build configuration: source directory: /mnt/src/zephyrproject/zephyr/samples/subsys/shell/shell_module build directory: /mnt/src/zephyrproject/zephyr/build (created) BOARD: litex_vexriscv (origin: command line) -- west build: generating a build system Zephyr version: 1.14.99 -- Found PythonInterp: /usr/bin/python3 (found suitable version "3.6.8", minimum required is "3.4") -- Selected BOARD litex_vexriscv -- Found west: /home/tsunekoh/.local/bin/west (found suitable version "0.5.7", minimum required is "0.5.6") -- Loading /mnt/src/zephyrproject/zephyr/boards/riscv32/litex_vexriscv/litex_vexriscv.dts as base -- Overlaying /mnt/src/zephyrproject/zephyr/dts/common/common.dts Parsing Kconfig tree in /mnt/src/zephyrproject/zephyr/samples/subsys/shell/shell_module/Kconfig Loaded configuration '/mnt/src/zephyrproject/zephyr/boards/riscv32/litex_vexriscv/litex_vexriscv_defconfig' Merged configuration '/mnt/src/zephyrproject/zephyr/samples/subsys/shell/shell_module/prj.conf' Configuration saved to '/mnt/src/zephyrproject/zephyr/build/zephyr/.config' -- Cache files will be written to: /home/tsunekoh/.cache/zephyr -- The C compiler identification is GNU 8.3.0 -- The CXX compiler identification is GNU 8.3.0 -- The ASM compiler identification is GNU -- Found assembler: /mnt/src/zephyr-sdk/riscv32-zephyr-elf/bin/riscv32-zephyr-elf-gcc -- Performing Test toolchain_is_ok -- Performing Test toolchain_is_ok - Success Including module: esp-idf in path: /mnt/src/zephyrproject/modules/hal/esp-idf/zephyr Including module: fatfs in path: /mnt/src/zephyrproject/modules/fs/fatfs Including module: qmsi in path: /mnt/src/zephyrproject/modules/hal/qmsi Including module: cypress in path: /mnt/src/zephyrproject/modules/hal/cypress Including module: silabs in path: /mnt/src/zephyrproject/modules/hal/silabs Including module: st in path: /mnt/src/zephyrproject/modules/hal/st Including module: stm32 in path: /mnt/src/zephyrproject/modules/hal/stm32 Including module: libmetal in path: /mnt/src/zephyrproject/modules/hal/libmetal Including module: lvgl in path: /mnt/src/zephyrproject/modules/lib/gui/lvgl Including module: mbedtls in path: /mnt/src/zephyrproject/modules/crypto/mbedtls Including module: mcumgr in path: /mnt/src/zephyrproject/modules/lib/mcumgr Including module: nffs in path: /mnt/src/zephyrproject/modules/fs/nffs Including module: open-amp in path: /mnt/src/zephyrproject/modules/lib/open-amp Including module: segger in path: /mnt/src/zephyrproject/modules/debug/segger Including module: tinycbor in path: /mnt/src/zephyrproject/modules/lib/tinycbor -- Configuring done -- Generating done -- Build files have been written to: /mnt/src/zephyrproject/zephyr/build -- west build: building application [1/114] Preparing syscall dependency handling [109/114] Linking C executable zephyr/zephyr_prebuilt.elf Memory region Used Size Region Size %age Used RAM: 69936 B 256 MB 0.03% IDT_LIST: 41 B 2 KB 2.00% [114/114] Linking C executable zephyr/zephyr.elf
ここで、dtcのコンパイラのバージョンが古く、cmakeに失敗する場合がある。その場合には、新規にdtcを入れ直す。ソースコードを落としてきて、dtcをビルドし /usr/local/bin
あたりに配置すれば良い。 dtcのソースコードはここからダウンロードできる。
新しいバージョンのdtcを入れたにもかかわらずビルドに失敗する場合には、zephyr sdkのdtcを参照しにいっている可能性がある。その場合には、zephyr sdkのdtcの名前を適当に変更すれば良い。
Renodeでシミュレーション
実際に起動するのかを確かめたいのだが、残念ながら実機環境を持っていない。仕方ないので、Renodeでシミュレーションできるかどうかを確かめてみる。
Renodeからリリースビルドを落としてきて、プラットフォームごとに用意されたインストーラーを起動すればインストール自体は問題なくできるはず。
ターミナルを開き、renodeを起動する。
$ cd /mnt/src/zephyrproject/zephyr/build # <-- 先程のzephyrのビルドされたものがディレクトリに移動 $ renode
ターミナルが開くので、先程のビルド時に指定したボード名を指定し、ELFをロードしシミュレーションをスタートする。
(monitor) mach create "litex-vexriscv" (litex-vexriscv) machine LoadPlatformDescription @platforms/cpus/litex_vexriscv.rep l (litex-vexriscv) sysbus LoadELF @zephyr.elf (litex-vexriscv) showAnalyzer sysbus.uart (litex-vexriscv) start Starting emulation... (litex-vexriscv)
terminalが開き、kernel version
とか叩くとzephyrのバージョンを表示することができる。
今回、RISC-V Getting Started Guildeを大いに参考にした。
spike-dasmの逆アセンブル結果がおかしい件について
risv-isa-simに付随しているspike-dasmの逆アセンブル結果におかしい点を見つけた。
$ echo "DASM(0x00000000)" | spike-dasm c.addi4spn s0, sp, 0
と表示されるんだが、これ unimp
が表示されるのが適切。 c.addi4spn
に関しては即値が0以外であることが、RISC-V ISA の仕様書にも書いてある。
C.ADDI16SP adds the non-zero sign-extended 6-bit immediate to the value in the stack pointer
ちなみに、objdump や radare2 は正しく出してくれた。
$ rasm2 -a riscv -d "0x0000" unimp
すでに、GitHubでもイシューとして上がっているけども、長い間修正されていないみたいだ。
spike-dasmを使う場合には注意を。
Practical Binary Analysisを読み始めた 1
hope-RIPEを使ってみる その2
昨日、hope-RIPEをSpikeシミュレーターで動かしてみたんだが、Stack Buffer Overflowを利用したROPが成功しないという不可解な現象に遭遇した。SSPが有効になっているからかと思ったけども、Makefileを見る限りではそうではないみたいだし、これは何だろうということで詳しく調べてみた。
失敗したケースのうち、以下のケースを取り上げる。
ripe_attack_generator -t direct -i rop -c ret -l stack -f memcpy
デバッガーで原因を探る
デバッガーで追ってみたところ、ropでjumpする先のアドレスを間違えていることが原因でセグフォしていることがわかった。
逆アセンブルの内容
0000000000010e00 <perform_attack>: ... 11b84: 78813083 ld ra,1928(sp) <-- ここで return address を書き換える 11b88: 78013403 ld s0,1920(sp) <-- ここに breakpoint を貼って、p $raしてみる 11b8c: 79010113 addi sp,sp,1936 11b90: 8082 ret 0000000000011ec8 <rop_target>: // <-- jump先ターゲット 11ec8: 1141 addi sp,sp,-16 11eca: e406 sd ra,8(sp) 11ecc: e022 sd s0,0(sp) 11ece: 0800 addi s0,sp,16 11ed0: 67cd lui a5,0x13 11ed2: 7d878513 addi a0,a5,2008 # 137d8 <.annobin_elf_init.c_end+0x73e> 11ed6: c3bfe0ef jal ra,10b10 <puts@plt> 11eda: 4501 li a0,0 11edc: c55fe0ef jal ra,10b30 <exit@plt>
デバッガーの出力
(gdb) b *0x11b88 Breakpoint 1 at 0x11b88 (gdb) r Starting program: /root/hope-RIPE/build/ripe_attack_generator -t direct -i rop -c ret -l stack -f memcpy Missing separate debuginfos, use: dnf debuginfo-install glibc-2.28.9000-24.fc30.riscv64 tech: 100 attack: 202 code ptr: 300 location: 400 function: 500 Breakpoint 1, 0x0000000000011b88 in perform_attack () (gdb) p $ra $1 = (void (*)()) 0x11ed8 <rop_target+16>
<rop_target+16>
がちゃんと命令の先頭アドレスとなっていれば良いが、そうなっていない。0x11ed8なので、jal ra,10b10
という命令の"ど真ん中に"ジャンプしてしまう。で、decodeができずIllegal Instructionで落ちていることがわかった。
以下の箇所がjump先を決めているところだが、コンパイラのバージョンが変わったことにより、この16というオフセット値が変わってしまったのだと予想される。
まとめると、昨日hope-RIPEを実行してFAILと表示されたケースは、SSPなどが働いて脆弱性攻撃に失敗したというわけではなく、単純に攻撃コードのバグによって失敗しているということだった。
ということで、修正してプルリクを出しておくのが良さそうである。
hope-RIPEを試してみる その1
Runtime Intrusion Prevention Evaluator (RIPE) という、脆弱性攻撃に対する防御のカバレッジを測定するためのツールがある。これを用いると種々のパラメータを変化させながら種々の脆弱性攻撃用コードを生成、そのコードを実行し脆弱性攻撃の成否をレポートしてくれる。 RIPEのもとの論文はここからダウンロードできる。
このRIPEだが、RISC-V向けにポートしたものが昨年リリースされていたので、試してみた。hope-RIPEと呼ぶらしい。
以下では、RISC-V toolsがインストールされており、パスが通してあることを前提として話をすすめる。
build & run
基本的に普通にmakeで問題なくビルドできた。
$ git clone https://github.com/draperlaboratory/hope-RIPE.git $ cd hope-RIPE $ make
すべてのテストケースを実行するためにpythonスクリプトが用意されている。pythonスクリプトの中身を見ると
for attack in attacks: for tech in techniques: for loc in locations: for ptr in code_ptr: for func in funcs: os.system('rm -f out/out.text') cmdargs = 'build/ripe_attack_generator ' + '-t ' + tech + ' -i ' + attack + ' -c ' + ptr + ' -l ' + loc + ' -f ' + func cmdline= run_cmd + ' ' + cmdargs + ' 1> out/out.text 2>/dev/null' if is_attack_possible (attack, tech, loc, ptr, func) == 0: total_np += 1
となっており、要はすべてのパラメータの組み合わせについてRIPEを実行するだけとなっている。
テストを実行した結果は以下の通り。
... ('build/ripe_attack_generator -t direct -i rop -c longjmpdata -l data -f memcpy', 0) ('build/ripe_attack_generator -t direct -i dataonly -c bof -l stack -f memcpy', 1) ('build/ripe_attack_generator -t direct -i dataonly -c iof -l stack -f memcpy', 1) ('build/ripe_attack_generator -t direct -i dataonly -c leak -l stack -f memcpy', 0) ('build/ripe_attack_generator -t direct -i dataonly -c bof -l heap -f memcpy', 1) ('build/ripe_attack_generator -t direct -i dataonly -c iof -l heap -f memcpy', 1) ('build/ripe_attack_generator -t direct -i dataonly -c leak -l heap -f memcpy', 0) ('build/ripe_attack_generator -t direct -i dataonly -c bof -l bss -f memcpy', 1) ('build/ripe_attack_generator -t direct -i dataonly -c iof -l bss -f memcpy', 1) ('build/ripe_attack_generator -t direct -i dataonly -c leak -l bss -f memcpy', 0) ('build/ripe_attack_generator -t direct -i dataonly -c bof -l data -f memcpy', 1) ('build/ripe_attack_generator -t direct -i dataonly -c iof -l data -f memcpy', 1) ('build/ripe_attack_generator -t direct -i dataonly -c leak -l data -f memcpy', 0) ('build/ripe_attack_generator -t indirect -i dataonly -c bof -l stack -f memcpy', 1) ('build/ripe_attack_generator -t indirect -i dataonly -c bof -l bss -f memcpy', 1) ('build/ripe_attack_generator -t indirect -i dataonly -c bof -l data -f memcpy', 1) SUMMARY ---------------------------------------------------------------- Total OK: 123 Total FAIL: 57 Total Attacks Executed: 180
OKは攻撃に成功したことを示し、FAILは攻撃に失敗したことを示す。攻撃に成功した場合には、標準出力に success. **** function reached.
というメッセージが表示される(****
には攻撃の手法の名前が入る)。
57件FAILしているわけだが、どういうことなのか。。。SSPによって守られているとかかな?
気になってMakefileを見てみると、
# Makefile for RIPE # @author John Wilander & Nick Nikiforakis # Modified for RISC-V by John Merrill #Depending on how you test your system you may want to comment, or uncomment #the following CFLAGS= -fno-stack-protector -z execstack CC=riscv64-unknown-elf-gcc all: ripe_attack_generator clean: rm -rf build/ out/ ripe_attack_generator: ./source/ripe_attack_generator.c mkdir -p build/ out/ $(CC) \ ./source/ripe_attack_generator.c -o ./build/ripe_attack_generator
となっており、バッチリ-fno-stack-protector
が入っている。
FAILしている原因調査は後日行うこととするが、ひとまずFAILしていたテストケースとして何があったかだけ調べたので、以下に掲載しておく。
パット見、direct rop stack ret memcpyとかで失敗しているが、このケースでの失敗はちょっと考えにくい。RISC-Vのポーティングを間違えている可能性が高そう。
direct shellcode funcptrbss bss memcpy direct shellcode structfuncptrbss bss memcpy direct shellcode longjmpbss bss memcpy direct shellcode funcptrdata data memcpy direct shellcode structfuncptrdata data memcpy direct shellcode longjmpdata data memcpy indirect shellcode ret bss memcpy indirect shellcode funcptrstackvar bss memcpy indirect shellcode funcptrstackparam bss memcpy indirect shellcode funcptrheap bss memcpy indirect shellcode funcptrbss bss memcpy indirect shellcode funcptrdata bss memcpy indirect shellcode structfuncptrstack bss memcpy indirect shellcode structfuncptrheap bss memcpy indirect shellcode structfuncptrdata bss memcpy indirect shellcode structfuncptrbss bss memcpy indirect shellcode longjmpstackvar bss memcpy indirect shellcode longjmpstackparam bss memcpy indirect shellcode longjmpheap bss memcpy indirect shellcode longjmpdata bss memcpy indirect shellcode longjmpbss bss memcpy indirect shellcode ret data memcpy indirect shellcode funcptrstackvar data memcpy indirect shellcode funcptrstackparam data memcpy indirect shellcode funcptrheap data memcpy indirect shellcode funcptrbss data memcpy indirect shellcode funcptrdata data memcpy indirect shellcode structfuncptrstack data memcpy indirect shellcode structfuncptrheap data memcpy indirect shellcode structfuncptrdata data memcpy indirect shellcode structfuncptrbss data memcpy indirect shellcode longjmpstackvar data memcpy indirect shellcode longjmpstackparam data memcpy indirect shellcode longjmpheap data memcpy indirect shellcode longjmpdata data memcpy indirect shellcode longjmpbss data memcpy indirect returnintolibc structfuncptrbss bss memcpy indirect returnintolibc structfuncptrdata data memcpy direct rop ret stack memcpy direct rop funcptrstackvar stack memcpy direct rop funcptrstackparam stack memcpy direct rop structfuncptrstack stack memcpy direct rop longjmpstackvar stack memcpy direct rop longjmpstackparam stack memcpy direct rop funcptrheap heap memcpy direct rop structfuncptrheap heap memcpy direct rop longjmpheap heap memcpy direct rop funcptrbss bss memcpy direct rop structfuncptrbss bss memcpy direct rop longjmpbss bss memcpy direct rop funcptrdata data memcpy direct rop structfuncptrdata data memcpy direct rop longjmpdata data memcpy direct dataonly leak stack memcpy direct dataonly leak heap memcpy direct dataonly leak bss memcpy direct dataonly leak data memcpy
『うつを甘く見ていました』を読んだ。
鬱病のこともそうだが、それよりも結婚するとか家族になるってどういうことだろうということの方を考えさせる内容だった。
漫画の内容に関して、旦那さんの非難が集まっていたらしいが、正直そこまで責めるべきでもないような気がする。(筆者もあとがきで夫を非難する内容にしたわけではないと書いてはいるのだけども。)
楽しい時をシェアできるよりも辛い場合にしっかり支え合えるのかとか、そういうことが結婚する相手に求められるべきなんだなと思った。