[Tomoyo-dev 561] シェルコードによる攻撃を受け流す機能について

Back to archive index

from-****@i-lov***** from-****@i-lov*****
2007年 9月 10日 (月) 12:55:27 JST


 熊猫です。

 新機能についてのコメント募集です。

 強制アクセス制御を使えば、バッファオーバーフローに
シェルコード(と呼ばれる悪意あるプログラム)を流し込んで
/bin/sh を起動されてしまうことを防止することができます。

 しかし、シェルコードが無限ループの中で /bin/sh の実行を要求した場合、
( /bin/sh の実行に成功すれば無限ループは終了するのですが)
/bin/sh の実行を拒否したことで無限ループが終了しなくなってしまうため、
CPU使用率100%の状態がずっと続いてしまうという副作用が生じてしまいます。

 そこで、 /bin/sh の実行を拒否する代わりに何か別のプログラムを実行することにより
攻撃を受け流すことができないかと(先週の金曜日にあったBoFに参加して)思いました。
試作してみたところ、技術的には実現できることが判りました。
やっていることは、ファイルの先頭が #! で始まるプログラムを実行する場合に
fs/binfmt_script.c で定義されている load_script() が行う処理とほとんど同じです。

----- パッチ始まり -----
Index: fs/tomoyo_domain.c
===================================================================
--- fs/tomoyo_domain.c	(revision 454)
+++ fs/tomoyo_domain.c	(working copy)
@@ -674,7 +674,7 @@
 	return NULL;
 }
 
-static int FindNextDomain(struct linux_binprm *bprm, struct domain_info **next_domain)
+static int FindNextDomain(struct linux_binprm *bprm, struct domain_info **next_domain, const int do_perm_check)
 {
 	/* This function assumes that the size of buffer returned by realpath() = CCS_MAX_PATHNAME_LEN. */
 	struct domain_info *old_domain = current->domain_info, *domain = NULL;
@@ -713,6 +713,8 @@
 	else l.name = old_domain_name;
 	fill_path_info(&l);
 
+	if (!do_perm_check) goto ok;
+
 	/* Check 'alias' directive. */
 	if (pathcmp(&r, &s)) {
 		struct alias_entry *ptr;
@@ -762,6 +764,7 @@
 	/* Check execute permission. */
 	if ((retval = CheckExecPerm(&r, filp)) < 0) goto out;
 
+ ok: ;
 	/* Allocate memory for calcurating domain name. */
 	retval = -ENOMEM;
 	if ((new_domain_name = ccs_alloc(CCS_MAX_PATHNAME_LEN + 16)) == NULL) goto out;
@@ -806,16 +809,65 @@
 
 #endif
 
+static int try_alt_exec(struct linux_binprm *bprm, char *alt_exec)
+{
+	struct file *filp;
+	int retval;
+	char *domainname = (char *) current->domain_info->domainname->name; /* Will not modified. */
+	allow_write_access(bprm->file);
+	fput(bprm->file);
+	bprm->file = NULL;
+	retval = remove_arg_zero(bprm);
+	if (retval) {
+		printk("remove_arg_zero(bprm) = %d\n", retval);
+		return retval;
+	}
+	retval = copy_strings_kernel(1, &bprm->interp, bprm);
+	if (retval < 0) {
+		printk("copy_strings_kernel(1, &bprm->interp, bprm) = %d\n", retval);
+		return retval;
+	}
+	bprm->argc++;
+	retval = copy_strings_kernel(1, &domainname, bprm);
+	if (retval < 0) {
+		printk("copy_strings_kernel(1, &domainname, bprm) = %d\n", retval);
+		return retval;
+	}
+	bprm->argc++;
+	retval = copy_strings_kernel(1, &alt_exec, bprm);
+	if (retval < 0) {
+		printk("copy_strings_kernel(1, &alt_exec, bprm) = %d\n", retval);
+		return retval;
+	}
+	bprm->argc++;
+	bprm->interp = alt_exec;
+	filp = open_exec(alt_exec);
+	if (IS_ERR(filp)) {
+		printk("open_exec(alt_exec) = %ld\n", PTR_ERR(filp));
+		return PTR_ERR(filp);
+	}
+	bprm->file= filp;
+	bprm->filename = alt_exec;
+	retval = prepare_binprm(bprm);
+	if (retval < 0) printk("prepare_binprm(bprm) = %d\n", retval);
+	return retval;
+}
+
 int search_binary_handler_with_transition(struct linux_binprm *bprm, struct pt_regs *regs)
 {
 	struct domain_info *next_domain = NULL, *prev_domain = current->domain_info;
  	int retval;
+	char alt_exec[128];
+	strcpy(alt_exec, "/bin/logit");
 #if defined(CONFIG_SAKURA) || defined(CONFIG_TOMOYO)
 	extern void CCS_LoadPolicy(const char *filename);
 	CCS_LoadPolicy(bprm->filename);
 #endif
 #if defined(CONFIG_TOMOYO)
-	retval = FindNextDomain(bprm, &next_domain);
+	retval = FindNextDomain(bprm, &next_domain, 1);
+	if (retval == -EPERM) {
+		if (try_alt_exec(bprm, alt_exec) >= 0) retval = FindNextDomain(bprm, &next_domain, 0);
+	}
 #else
 	retval = 0; next_domain = prev_domain;
 #endif
----- パッチ終わり -----

 上記のパッチでは全てのドメインに対してプログラムの実行が拒否された場合に /bin/logit というプログラムを
実行するようにハードコーディングされていますが、実際にはプロファイル単位で /bin/logit 実行するかどうかを
指定できるようにして、実行するプログラムもポリシーファイルで指定するようにします。
/bin/logit の内容は任意で、例えば以下のような内容でも構いません。

----- /bin/logit の例始まり -----
#! /bin/sh
echo "DOMAIN=" $1
shift
echo "You don't have permission to call ""$@"
env
exit 1
----- /bin/logit の例終わり -----

 上記のようなプログラムを利用して、「 touch /etc/f* 」という操作をしようとして
/usr/bin/touch コマンドの実行が拒否された場合、以下のような情報を取得することができるようになります。

----- /bin/logit から得られた情報 -----
DOMAIN= <kernel> /sbin/getty /bin/login /bin/bash
You don't have permission to call /usr/bin/touch /etc/fdmount.conf /etc/fonts /etc/fstab /etc/fstab~
HZ=100
TERM=linux
SHELL=/bin/bash
HUSHLOGIN=FALSE
USER=root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/bin/X11:/root/ccstools
MAIL=/var/mail/root
PWD=/root
LANG=C
HOME=/root
SHLVL=2
LANGUAGE=ja_JP:ja:en_GB:en
LOGNAME=root
_=/usr/bin/env
----- /bin/logit から得られた情報 -----

 このように、 /proc/ccs/reject_log から得られる「 /bin/touch というコマンドの実行が拒否された」という
情報だけではなく、「 /bin/touch というコマンドに指定したコマンドラインパラメータや環境変数など」の情報も
取得できるという利点があります。
 また、 /bin/logit の中からメールを送るようにしたり、クライアントのIPアドレスなどを取得して
ファイアウォールのルールを変更したり攻撃元の情報を他のホストと共有したりする用途にも使えます。
普段はサービスを提供するサーバとして動作していながら、
シェルコードを注ぎ込まれた時だけ攻撃内容を記録するセンサーとして動作します。
シェルコード内の無限ループから /bin/sh の実行が要求された場合にも、
/bin/true 等の無害なプログラムを実行するなり情報収集するなりしてプロセスが終了するので、
CPU使用率100%の状態がずっと続いてしまうという副作用を回避できます。

 ユーザが要求したプログラムとは異なるプログラムが実行されるというのを気持ち悪く思う人が多いようですが、
管理者が「〜〜の場合には・・・を代わりに実行する」ようにポリシーで指示した上で上記のように動作するのであれば、
何かの役に立つのではないかと思います。

http://marc.info/?l=linux-security-module&m=118916470210794&w=2

 皆さんはどう思われますか?




tomoyo-dev メーリングリストの案内
Back to archive index