るくすの日記 ~ Out_Of_Range ~

主にプログラミング関係

SEGVという概念が存在しない退屈な世界

この記事はKobeUniv Advent Calendar 2015 - Adventar12/6の記事です。

本カレンダーではKobeUniversityの現役生または卒業生が自分のやっている事(技術系,理工系に限らず)
について自由に書く物です。すごい身内ネタで終わってしまうと思っていましたが予想以上に
たくさんの方に参加して頂けてとても嬉しいです。(結構geekな方いるんですねー)


私は普段主に低レイヤー(?)を触っている人間で、具体的にはシステムプログラム7割,セキュリティ3割
ぐらいの割合で活動しています。


で、今回は前者のシステムプログラムについて、デモプログラムを作成しゆるく紹介します。
こんな事ができるよーという簡単なデモを通して、システムプログラミングの面白さを感じて頂ければ幸いです。
(後者のセキュリティについては本カレンダーのもうひとつ記事でまあまあ真面目に書く予定です。)


で何をやるかですが、今回はエンジニアお馴染みのエラーである
"Segmentation fault (コアダンプ)"
を、OS(Linux)を改造して発生しないようにしてみます。以下のプログラムを見てください。


(segv.c)

#include <stdio.h>
char a[4];
int main(){
  a[-114514] = 'h';
  printf("%c\n",a[-114514]);
}

めちゃくちゃですね。これを実行すると当然

$ gcc -O0 -o segv segv.c                                           
$ ./segv                                                           
Segmentation fault (コアダンプ)

SIGSEGVが飛んできます。しかし改造済みのlinux上で動かすと

$ ./segv
h

なんと'h'が出力されます。


改造パッチは以下に置いておきます。ubuntu14.04(trusty)のlinuxカーネル3.13を使用していますが
同じバージョンのバニラカーネルにも適用できます。(ただし何が起こっても責任は取れません。VM上で行うことをおすすめします)
gist.github.com


上のパッチを適用したらmake menuconfigを実行し

Joke setup

---> [*] SIGSEGV must not happen

をONにしてください。
f:id:RKX1209:20151206213412p:plain

そしてビルドして再起動し先程のプログラムを動かしてみてください。


どうやったか

SIGSEGVはいわゆるアクセス権的に許可されていなかったりプロセスの管理外の領域にアクセスした場合に発生します。
このようなアクセス違反はCPUが補足し、あらかじめlinuxが登録しておいたハンドラを呼び出し対処する事になっています。


このハンドラはlinuxカーネル内ではarch/x86/mm/fault.c内のdo_page_faultになっています。
でここで新たに違反アドレスに対してセクションを確保し、ページフォルト処理にそのまま渡して物理アドレスページを拾ってくるよう改造
しただけです。

static void __kprobes
__do_page_fault(struct pt_regs *regs, unsigned long error_code)
{
	...
	if (unlikely(!(vma->vm_flags & VM_GROWSDOWN))) {
		printk(KERN_INFO"<NO_SIG> not stack 0x%16lx 0x%16lx\n",vma->vm_start, address);
		#ifdef CONFIG_NO_SIGSEGV
		goto new_vma;
		#else
		bad_area(regs, error_code, address);
		return;
		#endif
	}
	new_vma:;
		unsigned long mm_flags = 0;
		mm_flags = 0x22;
		do_mmap_pgoff(0,address & PAGE_MASK,PAGE_SIZE,0x7,mm_flags,0,&populate);
		vma = find_vma(mm, address);
	...
}


では新たにセクションが増えている事を確認してみましょう。
今度は以下のようなプログラムを実行してみます。

#include <unistd.h>
int main(){
  *(unsigned long*)(0x1145140000) = 893;
  sleep(889464);
}


とんでもないアドレスに値を入れていますね。(これも当然ちゃんと動きます)
ではセクションはと言うと

$ cat /proc/1415/maps
00400000-00401000 r-xp 00000000 fd:01 795520                             /home/rkx/segv
00600000-00601000 r--p 00000000 fd:01 795520                             /home/rkx/segv
00601000-00602000 rw-p 00001000 fd:01 795520                             /home/rkx/segv
1145140000-1145141000 rwxp 00000000 00:00 0 
7f4ed87fc000-7f4ed89b7000 r-xp 00000000 fd:01 786601                     /lib/x86_64-linux-gnu/libc-2.19.so
7f4ed89b7000-7f4ed8bb6000 ---p 001bb000 fd:01 786601                     /lib/x86_64-linux-gnu/libc-2.19.so
7f4ed8bb6000-7f4ed8bba000 r--p 001ba000 fd:01 786601                     /lib/x86_64-linux-gnu/libc-2.19.so
7f4ed8bba000-7f4ed8bbc000 rw-p 001be000 fd:01 786601                     /lib/x86_64-linux-gnu/libc-2.19.so
7f4ed8bbc000-7f4ed8bc1000 rw-p 00000000 00:00 0 
7f4ed8bc1000-7f4ed8be4000 r-xp 00000000 fd:01 786581                     /lib/x86_64-linux-gnu/ld-2.19.so
7f4ed8dd6000-7f4ed8dd9000 rw-p 00000000 00:00 0 
7f4ed8de1000-7f4ed8de3000 rw-p 00000000 00:00 0 
7f4ed8de3000-7f4ed8de4000 r--p 00022000 fd:01 786581                     /lib/x86_64-linux-gnu/ld-2.19.so
7f4ed8de4000-7f4ed8de5000 rw-p 00023000 fd:01 786581                     /lib/x86_64-linux-gnu/ld-2.19.so
7f4ed8de5000-7f4ed8de6000 rw-p 00000000 00:00 0 
7ffc13787000-7ffc137a8000 rw-p 00000000 00:00 0                          [stack]
7ffc137de000-7ffc137e0000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]


アドレス0x1145140000にちゃんと無名のセクションができています。ここに上の値がちゃんと入っている訳です。
(ちなみにサイズが0となっているのはデマンドページングの影響かと思われます)


おわりに

非常にくだらない改造ですがユーザー空間では不可能な事がいとも簡単に実現できました。
もちろんこんなネタ改造だけでなくもっと真面目な機能を自分で実装し、LKMLなどにパッチを投げてみるのも良いと
思います。ともあれ少しでもシステムプログラムに興味を持って頂ければ幸いです。


次はちゃんと真面目にセキュリティの記事を書きます....