Tiny BASIC と ASLR (2012/11/03, 2013/04/25, 2013/12/26)

2013-02-09-wheezy-raspbian 以降のカーネル(linux-3.6.11+)でフレームバッファが動作しない不具合を修正しました。linux-3.10.24+ でも動作確認しました(2013/12/26)

最近 Raspberry Pi で ARM版 rvtl を使うと Segmentation fault が発生する場合があることに気付きました。 最近の x86 や x86_64 のカーネルはバッファオーバーフロー攻撃への対策の一つとして、プログラムが使用するメモリの一部のアドレスをランダムに配置する機能(ALSR)が入っています。 少し前の ARM のカーネルはその機能を使っていなかったのですが、Raspberry Pi の (Debian wheezyの) カーネル(3.2.27+) には ASLR が採用されているようです。

本家でも「TinyBASIC for Raspberry Pi」で盛り上がっているので、同時代に日本で流行したTiny BASIC の一種の GAME III の子孫 である rvtl をちゃんと動作させることにしました。

ASLRの確認

ASLR とは Address Space Layout Randomization の略で、プロセスの実行時のメモリアドレスをランダムに配置することによってバッファオーバーフロー攻撃をやり難くする手段です。 下の図は Linux のプロセスが動作している状態のメモリの内容を示しています。カーネルや動的にリンクされたライブラリは省略しています。下の図の赤いラインがASLRのよってランダムに変化する部分です。

実際にアドレスが変化しているかどうかをアセンブリプログラムを作成して確認してみました。ヒープ領域のアドレスは C で書いたプログラムからも確認できますが、プログラムカウンタやスタックポインタは最低でもインラインアセンブラを使う必要があります。今回はすべてアセンブラで作成しました。

ASLRの効果を確認するために、スタックポインタのアドレス、実行中のプログラムのアドレス、BSSの領域の最終アドレス、ヒープ領域の先頭アドレスを表示するプログラムを作成しました。 プログラムの動作としては、単に r0 レジスタにアドレスを代入して PrintHex8 ルーチンで標準出力に16進数8桁で表示するだけです。最初にスタックポインタ、次にプログラムカウンタ、BSS領域の先頭アドレス、BRKシステムコールで取得したヒープ領域の先頭アドレスの4つを表示します。

@ -------------------------------------
@ 2012/10/27 check randomize_va_space
@ as -o printbrk.o printbrk.s
@ ld -o printbrk printbrk.o
@ -------------------------------------
        .equ sys_exit,  0x000001
        .equ sys_write, 0x000004
        .equ sys_brk,   0x00002D

        .text
        .global _start
        .align   2
_start:
        mov     r0, sp
        bl      PrintHex8
        mov     r0, pc
        bl      PrintHex8
        ldr     r0, dummy0
        bl      PrintHex8
        mov     r0, #0
        mov     r7, #sys_brk
        swi     0
        bl      PrintHex8
Exit:
        mov     r0, #0
        mov     r7, #sys_exit
        swi     0

PrintHex8:
        stmfd   sp!, {r0-r3,lr}         @ push
        mov     r1, #8
        mov     r3, r1                  @ column
1:      and     r2, r0, #0x0F           @
        mov     r0, r0, LSR #4          @
        orr     r2, r2, #0x30
        cmp     r2, #0x39
        addgt   r2, r2, #0x41-0x3A      @ if (r2>'9') r2+='A'-'9'
        stmfd   sp!, {r2}               @ push digit
        subs    r3, r3, #1              @ column--
        bne     1b
        mov     r3, r1                  @ column
2:      ldmfd   sp!, {r0}               @ pop digit
        bl      OutChar
        subs    r3, r3, #1              @ column--
        bne     2b
        mov     r0, #10                 @ LF
        bl      OutChar
        ldmfd   sp!, {r0-r3,pc}         @ restore & return

OutChar:
        stmfd   sp!, {r0-r2, r7, lr}
        mov     r1, sp                  @ r1  address
        mov     r0, #1                  @ r0  stdout
        mov     r2, r0                  @ r2  length
        mov     r7, #sys_write
        swi     0
        ldmfd   sp!, {r0-r2, r7, pc}    @ pop & return

dummy0:         .long   dummy

.bss
                .align   2
dummy:          .long   0
@ -------------------------------------

以上のコードを printbrk.s というファイル名で保存して、次のようにアセンブル、リンクします。printbrk というファイルを実行します。

jun@raspberrypi ~/pi_asm $ as -o printbrk.o printbrk.s
jun@raspberrypi ~/pi_asm $ ld -o printbrk printbrk.o

randomize_va_space の変更方法

スーパーユーザになって (sudo su)、 /proc/sys/kernel/randomize_va_space に対して 0、1、2 を書きこむことで変更できます。

# echo 1 > /proc/sys/kernel/randomize_va_space

randomize_va_space=2 の場合

実行するたびにスタック領域、ヒープ領域のアドレスが変化しています。

jun@raspberrypi ~/pi_asm $ cat /proc/sys/kernel/randomize_va_space
2
jun@raspberrypi ~/pi_asm $ ./printbrk
BEF37810  スタックポインタ
00008084  プログラムカウンタ
00010114  BSS領域
00CD0000  ヒープ領域先頭
jun@raspberrypi ~/pi_asm $ ./printbrk
BEF03810
00008084
00010114
01B28000
jun@raspberrypi ~/pi_asm $ ./printbrk
BE9F0810
00008084
00010114
00734000

randomize_va_space=1 の場合

実行するたびにスタック領域が変化します。

jun@raspberrypi ~/pi_asm $ cat /proc/sys/kernel/randomize_va_space
1

jun@raspberrypi ~/pi_asm $ ./printbrk
BE814810
00008084
00010114
00011000
jun@raspberrypi ~/pi_asm $ ./printbrk
BEA54810
00008084
00010114
00011000
jun@raspberrypi ~/pi_asm $ ./printbrk
BE9A2810
00008084
00010114
00011000

randomize_va_space=0 の場合

何度実行してもスタック、プログラム領域、ヒープ領域ともに変化していないことが確認できます。

jun@raspberrypi ~/pi_asm $ cat /proc/sys/kernel/randomize_va_space
0

jun@raspberrypi ~/pi_asm $ ./printbrk
BEFFF810
00008084
00010114
00011000
jun@raspberrypi ~/pi_asm $ ./printbrk
BEFFF810
00008084
00010114
00011000
jun@raspberrypi ~/pi_asm $ ./printbrk
BEFFF810
00008084
00010114
00011000

rvtl の修正

rvtl は、BSS領域に最初に確保した256KBをプログラムの保存、配列データ領域に使用します。 プログラムサイズが大きい、またはデータ量が多い場合には、BSS領域の最終アドレスを持つシステム変数「*」の値に増やしたいメモリ量を加えることで好きなだけメモリを確保する事が出来ます。rvtl の実行開始時にヒープ領域の先頭アドレスがランダムに割り当てられる場合、BSS領域の最終位置とヒープ領域の先頭位置の間にギャップができて、その部分がつながっているものとしてアクセスした瞬間に Segmentation fault が発生することになっていました。

プログラムの保存、配列データ領域の初期領域としてBSS領域にメモリを確保するのではなく、最初に sys_brk システムコールを実行して取得したアドレス (ヒープ領域の先頭) をプログラムの保存、配列データ領域とすることで、その後のメモリ拡張領域との間にギャップを作らないように対応する事にします。

rvtl を実行中のプロセスのメモリイメージ

             +--------------------+
             :                    :
             :                    :
             +--------------------+
             | rvtlコマンド       |
             |                    |
             +--------------------+
             | 作業領域           |
             |                    |
             |  変数用領域 A-Za-z |
             |  スタック領域      |
             |  変数スタック領域  |
             +--------------------+
             :                    : ギャップ
        =    +--------------------+
             | (rvtlのプログラム) |
             | 100 A=&            |
             | 110 B=A+100        |
             | 120 A(99)=123      |
             | 130 B[5]=456       |
             | 140 ?=B[5]         |
        &    +--------------------+ A=& プログラム末
             | 配列Aの領域        |
             |                    |
             +--------------------+ B=A+100
             | 配列Bの領域        |
             |                    |
             |- - - - - - - - - - |
             |                    |
             | 空き領域           |
             |                    |
        *    +--------------------+ メモリ領域最終(可変)
             :                    :
             :                    :
             :                    :
             |- - - - - - - - - - |
             | プロセスのスタック |
             |   引数文字列       |
             +--------------------+

rvtlの初期化部分のコード

        @ システム変数の初期値を設定

        mov     r0, #0              @ 0 を渡して現在値を得る
        mov     r7, #sys_brk        @ brk取得
        swi     0
        mov     r2, r0
        mov     r1, #',             @ プログラム先頭 (,)
        str     r0, [fp, r1,LSL #2]
        mov     r1, #'=             @ プログラム先頭 (=)
        str     r0, [fp, r1,LSL #2]
        add     r3, r0, #4          @ ヒープ先頭 (&)
        mov     r1, #'&
        str     r3, [fp, r1,LSL #2]
        ldr     r1, mem_init        @ MEMINIT=256*1024
        add     r0, r0, r1          @ 初期ヒープ最終
        mov     r1, #'*             @ RAM末設定 (*)
        str     r0, [fp, r1,LSL #2]
        swi     0                   @ brk設定
        mvn     r3, #0              @ -1
        str     r3, [r2]            @ コード末マーク

Raspberry Pi 用 rvtl のダウンロード (2013/04/25)

上記の他、フレームバッファのオープン時の不具合と編集機能をUTF-8に対応する変更を行ったバージョン rvtl-3.04.1eabi.tar.gz (130KB) を作成 (2013/04/25 linux-3.6に対応) しました。

使用するには次のように展開して、「./rvtl」で実行できます。対話モードから終了するには「~ (チルダ)」を押して [Enter] で終了します。詳細は Readme.txt を参照してください。

jun@raspberrypi ~ $ tar zxf rvtl-3.04.1eabi.tar.gz 
jun@raspberrypi ~ $ cd rvtl-3.04.1eabi
jun@raspberrypi ~/rvtl-3.04.1eabi $ ls -l
total 156
-rw-r--r-- 1 jun jun 17996 May 18  1999 COPYING
-rw-r--r-- 1 jun jun 28324 Oct 21  2012 gpl.text
-rw-r--r-- 1 jun jun 64819 Apr 25 19:01 Readme.txt
-rwxr-xr-x 1 jun jun 22572 Apr 25 19:06 rvtl
-rw-r--r-- 1 jun jun 10683 Apr 25 19:00 rvtl_ref.txt
drwxr-xr-x 2 jun jun  4096 Apr 25 19:06 source
drwxr-xr-x 2 jun jun  4096 Apr 25 19:04 vtl

rvtl のプログラム

以下のコードを実行するとシステム変数を表示します。フレームバッファは sys_mmap2 システムコールでメモリにマッピングしているので、その先頭アドレスも表示して、mmapで取得したアドレスもランダムな値を返すかどうかを確認します。

jun@raspberrypi ~/rvtl-3.04eabi/vtl $ cat sysinf.vtl
100 : -------------------------------------------
110 : Information
120 :   2012/10/25
130 : -------------------------------------------
140   |fbo     : open frame buffer
150   [=0      : Range check off
160   "# Start from Line Number :  " ?=#  /  : # 実行中の行番号を保持
170   "= Program starts from    : $" ??== /  : = プログラム先頭アドレス
180   "& Program ends at        : $" ??=& /  : $ コードの最終使用アドレス+1
190   "  Program size           :  " ?=&-= " bytes" /
200   ", Memory Top             : $" ??=, /  : , アクセス可能先頭アドレス
210   "* Memory End             : $" ??=* /  : * メモリ最終位置を保持
220   "  Merory size            :  "  ?=*-, " bytes" /
230   T=_                                    : _ 現在の秒
240   "_ Second                 : " ?=T /
250   U=%                                    : % マイクロ秒
260   "  Micro second           : " ?[6]=U /
270   w=.                                    : . ウィンドウサイズ
280   ". Screen Width           : " ?=w>>16 /   : 上位16ビットに幅
290   "         Height          : " ?=w&$ffff / : 下位16ビットに高さ
300   "  Expand Memory *=*+$1000  $" *=*+$1000 ??=* / / : brkを設定
310   "  Frame buffer mem start : $" ??=g[44] /
320   "  Frame buffer mem length: " ?=g[45]  /
330 :
340   |fbc     : close frame buffer
350   "END" #=-1

実行例

sysinf.vtl を3回実行してみました。「Program starts from」が毎回変化していることが確認できます。フレームバッファの先頭アドレスは毎回同じ値を返しています。

jun@raspberrypi ~/rvtl-3.04.1eabi/vtl $ ../rvtl sysinf.vtl

<0947> #=1
# Start from Line Number :  160
= Program starts from    : $01618000
& Program ends at        : $0161861C
  Program size           :  1564 bytes
, Memory Top             : $01618000
* Memory End             : $01658000
  Merory size            :  262144 bytes
_ Second                 : 1366885126
  Micro second           : 870647
. Screen Width           : 101
         Height          : 53
  Expand Memory *=*+$1000  $01659000

  Frame buffer mem start : $5C006000
  Frame buffer mem length: 2621440
END
<0947> ~

jun@raspberrypi ~/rvtl-3.04.1eabi/vtl $ ../rvtl sysinf.vtl

<0948> #=1
# Start from Line Number :  160
= Program starts from    : $01E98000
& Program ends at        : $01E9861C
  Program size           :  1564 bytes
, Memory Top             : $01E98000
* Memory End             : $01ED8000
  Merory size            :  262144 bytes
_ Second                 : 1366885136
  Micro second           : 109695
. Screen Width           : 101
         Height          : 53
  Expand Memory *=*+$1000  $01ED9000

  Frame buffer mem start : $5C006000
  Frame buffer mem length: 2621440
END
<0948> ~

jun@raspberrypi ~/rvtl-3.04.1eabi/vtl $ ../rvtl sysinf.vtl

<0949> #=1
# Start from Line Number :  160
= Program starts from    : $010CA000
& Program ends at        : $010CA61C
  Program size           :  1564 bytes
, Memory Top             : $010CA000
* Memory End             : $0110A000
  Merory size            :  262144 bytes
_ Second                 : 1366885137
  Micro second           : 485063
. Screen Width           : 101
         Height          : 53
  Expand Memory *=*+$1000  $0110B000

  Frame buffer mem start : $5C006000
  Frame buffer mem length: 2621440
END
<0949> ~

rvtl を起動すると対話モードになるため、rvtlを終了するには 「~ (チルダ)」を入力して [Enter]キー を押してください。

rvtl-3.04.1eabi は Raspberry Pi のフレームバッファにラインやパターンを表示する機能も持っています。詳細はこちらを参照してください。


続く...