Not only is the Internet dead, it's starting to smell really bad.:2014年09月下旬

2014/09/20(Sat)

[NetBSD] debugging dlsym(3) その2

@あらすじ

NetBSD固有のdlsym(3)のバグだった、まぁ誰も使ってないからね仕方ないね。

@問題の切り分けに、librumphijack_dummy.soを直接リンクしてみる。

もう少し原因を絞るため、サンプルコードのtest.cをlibc_dummyでもlibpthread_dummyでもなく、直接librumphijack_dummyをリンクした場合の結果も確認してみましょう。

$ cc -Wl,-rpath,`pwd` -L`pwd` -lc_dummy -lrumphijack_dummy -o test_rumhijack test.c
$ ldd test_rumhijack
$ ldd test_rumhijack
test_rumhijack:
        -lc_dummy.0 => /tmp/libc_dummy.so.0
        -lc.12 => /usr/lib/libc.so.12
        -lrumphijack_dummy.0 => /tmp/librumphijack_dummy.so.0
        -lpthread_dummy.0 => /tmp/libpthread_dummy.so.0
$ ./test_rumhijack
start hijack
i'm libpthread!
end hijack

ファー↑、LD_PRELOADだとダメだけど直接リンクした場合は問題ないちゅーことです、うーんこの。

@もういっちょ問題の切り分けに __attribute__((constructor)) の影響を調べる

librumphijack_dummy.cの中で、dlsym(2)を呼び出しているrcinit()ですが、gcc拡張の__attribute__((constructor))つまりコンストラクタ属性がついてた事にお気づきでしょうか?

void __attribute__((constructor))
rcinit(void)
{
...

この属性のついた関数は.ctorsというセクションに配置され(__CTOR_LIST__というリストで管理)、どこからも呼び出さなくても main() より前に実行されます *1

そんでStackOverflowをはじめとして .ctors の実行タイミングは「動的リンクライブラリの場合それがロードされたタイミング」などと書かれてるのを多数目撃するのですが gccの マヌアルには

the function to be called automatically before execution enters main ()

としかないのですが、Yahoo!知恵袋や2chム板くだ質スレと違って世界レベルのプログラマが集まるところだし(震え声)、そういう実装もあるのかもしれません。

  • librumphijack_dummy が dlsym(2) を呼び出す
  • まだ RunTime Link eDitor は libpthread_dummy をロードしていない
  • 先にロードされていた libc_dummy の dfork() がdlsym(2)によって返される
  • ち~ん(笑)

となる可能性?うーんこれにはワイ疑問。

ちゅーことでこのロード(第二章)のタイミングによる影響を排除する為、dlsym(3) を dfork() の中で直接アン毎回呼ぶように変更してみましょう。

$ diff -uBw librumphijack_dummy.c.orig librumphijack_dummy.c
--- librumphijack_dummy.c.orig    2014-09-20 15:07:40.000000000 +0900
+++ librumphijack_dummy.c       2014-09-20 15:07:48.000000000 +0900
@@ -7,6 +7,7 @@
 dfork(void)
 {
 	fprintf(stderr, "start hijack\n");
+	host_dfork = dlsym(RTLD_NEXT, "dfork");
 	(*host_dfork)();
 	fprintf(stderr, "end hijack\n");
 }
@@ -14,5 +15,4 @@
 void __attribute__((constructor))
 rcinit(void)
 {
-	host_dfork = dlsym(RTLD_NEXT, "dfork");
 }

この差分を適用したlibrumphijack_dummy.soを使って再度サンプルコードを実行してみると?

$ LD_PRELOAD=./librumphijack_dummy.so.0.0 ./test_c
start hijack
i'm libc!
end hijack

知ってた。

@もうにっちょ問題の切り分けに、シンボルの解決をいつやるの、今でしょ!してみる

ld.so(1) は関数のシンボルの解決は怠け者モード(LAZY)がデフォルトです、 dlopen(3)のマニュアルをみると判りますがシンボルの解決方法には

  • RTLD_LAZY ... マクドみたいに予めポテトは作り置き
  • RTLD_NOW ... モスみたいに注文があってからポテトを揚げる

のふた通りありまして、dlopenではなく動的リンクされた場合には必ず前者になります。

ですので、未解決の関数シンボルについては ライブラリの前方互換性が壊れるときで説明したように

$ ./test_foo
foo
./test_foo: Undefined PLT symbol "bar" (symnum = 15)

というようにエラーメッセージを出すのですが、こいつが出るのは実際にその関数が呼ばれるタイミングになります。

こういう前方互換性問題ってデバッグしづらいので、その為に RTLD_NOWと同等の処理をするなう!デバッグモードがあります。 それが環境変数 LD_BIND_NOW です。

つっても LD_BIND_NOW=1 で状況がかわるなんて 1145148103349800パーセント 無さそうだけどね。 もしそういうバグが ld.so(1) にあるのなら、LAZY だろうが NOW だろうがウィークシンボルを完全に無視して問題解決!してるアホの子ちゅーことで こんな重箱の隅でない別の重大トラブルになってるでしょう。

リンクがらみのデバッグ環境変数のひとつの紹介として、とりあえず実行するだけしてみましょう。

$ LD_BIND_NOW=1 LD_PRELOAD=./librumphijack_dummy.so.0.0 ./test_c
start hijack
i'm libc!
end hijack

はいダメみたいですね(平然)

@次回

ちゅーことで LD_PRELOAD がらみっぽい、なのでその辺 ld.so(1) はどう動作するか LD_DEBUG の出力から読んでみます。

*1:同様に__attribute__((destructor))は.dtors(__DTOR_LIST__)で管理され、exit()などの後に実行されます。

2014/09/22(Mon)

[NetBSD] debugging dlsym(3) その3

@あらすじ

LD_PRELOAD した場合だけ dlsym(3) がおかしいっぽいね、仕方ないね。

@【教養】 ld.so(1) が呼ばれるまで何が行われるのか? 【時間かせぎ】

今回のバグに無関係なんですが、お嬢様の嗜みとしてね? ld.so(1)が実行されるまでの流れをさらっと説明。

@shell → libc

まず shell は入力された環境変数やコマンドラインそしてオプション及び引数を execve(2) に渡します、以下/bin/shの コード

161 STATIC void
162 tryexec(char *cmd, char **argv, char **envp, int vforked)
163 {
164         int e;
165 #ifndef BSD
166         char *p;
167 #endif
168
169 #ifdef SYSV
170         do {
171                 execve(cmd, argv, envp);
172         } while (errno == EINTR);
173 #else
174         execve(cmd, argv, envp);
175 #endif

@libc → kernel

execve(2) はセクション 2 すなわちシステムコールで libc 側の実装はただの kernel へのプロキシですので、すぐ飛びます。 飛び先は kernel_exec.c の sys_execve()がそれっすね。

518 /*
519  * exec system call
520  */
521 int
522 sys_execve(struct lwp *l, const struct sys_execve_args *uap, register_t *retval)
523 {
...

execve(2)は、コマンドラインで渡されたファイルについていろいろなチェックを行うのですが、今回は実行形式かどうかのチェックについて詳しくみていこうかと。

@NetBSDにおいてサポートされる実行形式は、どのように判別されるのか?

まず NetBSD kernelでは、とある実行形式をサポートするには execsw 構造体というインタフェースを実装したサブクラスを提供する必要があります。

150 struct execsw {
151 	u_int	es_hdrsz;		/* size of header for this format */
152 	exec_makecmds_fcn es_makecmds;	/* function to setup vmcmds */
...
170 };

ネイティブでサポートされる実行形式、そしてサブクラスの実装は

  • a.out形式 ... EXEC_AOUT, exec_aout.c
    9 options 	EXEC_AOUT	# exec a.out binaries
    ...
    
    59 static struct execsw exec_aout_execsw = {
    ...
    
  • ECOFF形式 ... EXEC_COFF, exec_ecoff.c
    12 options 	EXEC_ECOFF	# (native) ECOFF binary support
    ...
    
    60 static struct execsw exec_ecoff_execsw = {
    ...
    
  • ELF(32bit)形式 ... EXEC_ELF32, exec_elf32.c
    10 options 	EXEC_ELF32	# exec ELF binaries
    ...
    
    54 static struct execsw exec_elf32_execsw[] = {
    ...
    
  • ELF(64bit)系形式 ... EXEC_ELF64, exec_elf64.c
    9 options 	EXEC_ELF64	# exec ELF binaries
    ...
    
    54 static struct execsw exec_elf64_execsw[] = {
    ...
    
  • #! スクリプト ...EXEC_SCRIPT, exec_script.c
    11 options 	EXEC_SCRIPT	# exec #! scripts
    ...
    
    59 static struct execsw exec_script_execsw = {
    ...
    

あたりを参照ですやね、EXEC_* だけでなく他にもLinux等の他OSバイナリ実行サポートの COMPAT_* でも いろいろ取り揃えております(動くとはいっていない) *1

こいつらは execsw のサブクラスであるだけでなく、カーネルモジュールでもあるので

  • MODULE() マクロを宣言し、自身をカーネルモジュールとして登録
  • *_modcmd という初期化/終了時/状態取得インタフェースを実装

ちゅーこともしとります。

52 MODULE(MODULE_CLASS_EXEC, exec_elf64, DEP);
...
89 static int
90 exec_elf64_modcmd(modcmd_t cmd, void *arg)
91 {
92 
93 	switch (cmd) {
94 	case MODULE_CMD_INIT:
...

詳しくは module(9) 読んでどうぞ。

@次回

この execsw と module インタフェースがどのように使われていくかを読んでいきます。

*1:ちな昔はDarwin/MACHバイナリを動かす為にMACH-O形式、Windows以下同文PECOFF形式とかのサポートもあったんですが メンテが滞るとの理由でいつものモヒカンが ヒャッハーして削られてます、南無。

2014/09/25(Thu)

[Security] bash bashing

bashが名前の通りに叩かれる、うーん秋ですね。

それ絡みでいろいろコード読んでて N の nologin(8) って未だにシェルスクリプトだから環境変数汚染攻撃に弱いよなーと思いました。

ちなOpenBSD/FreeBSDは何十年も前の時点でCプログラムに置き換えとりますな。

OpenBSD

Revision 1.1, Sun Feb 16 04:15:32 1997 UTC (17 years, 7 months ago) by downsj 
Branch: MAIN

Just for the hell of it..?

じごくだ!じごくだ!

FreeBSD

Author:	das
Date:	Mon Nov 17 06:39:38 2003 UTC (10 years, 10 months ago)
Changed paths:	6
Log Message:	
Reimplement nologin(8) as a C program.  This allows us to statically
link it at low cost and avoid environment poisoning attacks associated
with LD_LIBRARY_PATH.

Suggested by:	rwatson

環境変数汚染攻撃についてちゃんと触れられてますな。

まぁ例えばsshの場合認証があるしsshd_configもAcceptEnvがデフォルト無効だけど、セキュリティはひとつひとつの積み重ねだからね。

ちなログイン禁止アカウントのシェルに nologin(8) でなく false(1) を指定するオールドタイマーな人は、OpenBSD/FreeBSD ともに /bin/false はシェルスクリプトな事に注意。

2014/09/26(Fri)

[チラシの裏] さくらVPS

shellshockで大騒ぎの天界を眺めながら、ワイは今日も血の池地獄でのんびり泳いでおりますが、その裏でさくら VPS は KVM のアップデートでもしたんですかね。 昨日の夜までは NetBSD では NIC は wm0 で認識されてたのが突然 vioif0~2 で認識されるようになったようで、再起動したらこのチラシの裏みえなくなっとりました。

これが

Sep 25 00:30:42 xxx /netbsd: wm0 at pci0 dev 3 function 0: Intel i82540EM 1000BASE-T Ethernet (rev. 0x03)
Sep 25 00:30:42 xxx /netbsd: wm0: interrupting at ioapic0 pin 11
Sep 25 00:30:42 xxx /netbsd: wm0: 32-bit 33MHz PCI bus
Sep 25 00:30:42 xxx /netbsd: wm0: 64 word (6 address bits) MicroWire EEPROM
Sep 25 00:30:42 xxx /netbsd: wm0: Ethernet address XX:XX:XX:XX:XX:XX
Sep 25 00:30:42 xxx /netbsd: makphy0 at wm0 phy 1: Marvell 88E1011 Gigabit PHY, rev. 0

こうなっとる、ハハッ

Sep 26 00:35:49 xxx /netbsd: vioif0 at virtio0: Ethernet address XX:XX:XX:XX:XX:XX
Sep 26 00:35:49 xxx /netbsd: vioif1 at virtio3: Ethernet address YY:YY:YY:YY:YY:YY
Sep 26 00:35:49 xxx /netbsd: vioif2 at virtio4: Ethernet address ZZ:ZZ:ZZ:ZZ:ZZ:ZZ

なもんでVPSコンソールから入って ifconfig.wm0 を ifconfig.vioif0 にコピーしたんですが、ioctl() でエラーになるという。 vioif(4) は ifconfig のパラメーターに media autoselect つけると認識しないのね…

ところで vioif0~2 ってこれ N だと agr(4) で鈴木亜久里ならぬ Link Aggregation でもしたら、冗長化とか帯域増強になるんすかねこれ。

debugging dlsym(3) その4

@あらすじ

ちょっと間が空いてしまいましたが、書きかけた記事がミスで消えたからね、ちかたないね。

@【悲報】 execsw 周りのコードを読む 【どんどん話が本題から逸れていく】

ちかたないね、一応本題のld.soのバグ修正は bitbucket に投棄済なのでぐだぐだ説明はいいからパッチよこせという人はそっち探してくだしあ。

@execsw の実装

前回挙げた execsw インタフェースを実装したサブクラスは 以下のコードのように、長さ nexecs の 配列 execsw で管理されます *1

164 /*
165  * Exec function switch:
166  *
167  * Note that each makecmds function is responsible for loading the
168  * exec package with the necessary functions for any exec-type-specific
169  * handling.
170  *
171  * Functions for specific exec types should be defined in their own
172  * header file.
173  */
174 static const struct execsw	**execsw = NULL;
175 static int			nexecs;

コメントをみると execsw はEXEC function SWitchの略だとわかりますやね。

switch というと switch 文と脳内で短絡評価してしまいますが、実際の処理は itetate ですやね、 ここ

301 /*
302  * check exec:
303  * given an "executable" described in the exec package's namei info,
304  * see what we can do with it.
...
326 int
327 /*ARGSUSED*/
328 check_exec(struct lwp *l, struct exec_package *epp, struct pathbuf *pb)
329 {
...
411 	for (i = 0; i < nexecs; i++) {
412 		int newerror;
413 
414 		epp->ep_esch = execsw[i];
415 		newerror = (*execsw[i]->es_makecmds)(l, epp);
416 
417 		if (!newerror) {

ループしてexecsw配列の各要素のmakecmd()を実行していきます、引数に渡された exec_package 構造体には

189 struct exec_package {
190 	const char *ep_name;		/* file's name */
191 	const char *ep_kname;		/* kernel-side copy of file's name */
192 	char *ep_resolvedname;		/* fully resolved path from namei */
193 	void	*ep_hdr;		/* file's exec header */
194 	u_int	ep_hdrlen;		/* length of ep_hdr */

のように execve(2) に渡された実行ファイル名そしてファイルイメージが含まれています。 各execswサブクラスのmakecmd()はこいつらを元に実行可能なファイルかどうかのチェックを行うわけです。 例えばシェルスクリプトなら シバン(shebang) すなわち #! とその後のインタプリタを探すとかっすな。

exec_script.h

36 #define	EXEC_SCRIPT_MAGIC	"#!"
37 #define	EXEC_SCRIPT_MAGICLEN	2

exec_script.c

101 /*
102  * exec_script_makecmds(): Check if it's an executable shell script.
103  *
104  * Given a proc pointer and an exec package pointer, see if the referent
105  * of the epp is in shell script.  If it is, then set thing up so that
106  * the script can be run.  This involves preparing the address space
107  * and arguments for the shell which will run the script.
108  *
109  * This function is ultimately responsible for creating a set of vmcmds
110  * which can be used to build the process's vm space and inserting them
111  * into the exec package.
112  */
113 int
114 exec_script_makecmds(struct lwp *l, struct exec_package *epp)
115 {
...
117 	char *hdrstr = epp->ep_hdr;
...
131 	/*
132 	 * if the magic isn't that of a shell script, or we've already
133 	 * done shell script processing for this exec, punt on it.
134 	 */
135 	if ((epp->ep_flags & EXEC_INDIR) != 0 ||
136 	    epp->ep_hdrvalid < EXEC_SCRIPT_MAGICLEN ||
137 	    strncmp(hdrstr, EXEC_SCRIPT_MAGIC, EXEC_SCRIPT_MAGICLEN))
138 		return ENOEXEC;

まーデザパタでいうところのStrategy(戦略)パターンすかね。execsw 構造体が Strategy Interface で exec_*_execsw が Concreate Strategy Class です。 そんで Strategy の切替はこのConcretate Storategy Classにとりあえずぶん投げてみるとゆー、Chain of Responsibility(責任たらい回し)パターンですか、デザパタ興味ないので間違ってたら苦情は/dev/nullへどうぞ。

@execsw 配列の再構築

しかしこのexecsw配列って初期状態では NULL/0 なので、これじゃループが花びら大回転ジャンジャンバリバリ開放しません。

ちゅーことでexecsw配列の作成方法。

  • ex_head という一時作業用のリストに対して
  • exec_add() で要素を追加し
  • exec_init() を呼び出すことで execsw 配列を再作成する

という仕組みになってます。

 179 /* list of dynamically loaded execsw entries */
 180 static LIST_HEAD(execlist_head, exec_entry) ex_head =
 181     LIST_HEAD_INITIALIZER(ex_head);
 182 struct exec_entry {
 183 	LIST_ENTRY(exec_entry)	ex_list;
 184 	SLIST_ENTRY(exec_entry)	ex_slist;
 185 	const struct execsw	*ex_sw;
 186 };
...
1658 /*
1659  * Add execsw[] entries.
1660  */
1661 int
1662 exec_add(struct execsw *esp, int count)
1663 {
...
1690 		LIST_INSERT_HEAD(&ex_head, it, ex_list);
1693 	/* update execsw[] */
1694 	exec_init(0);
...
1748 /*
1749  * Initialize exec structures. If init_boot is true, also does necessary
1750  * one-time initialization (it's called from main() that way).
1751  * Once system is multiuser, this should be called with exec_lock held,
1752  * i.e. via exec_{add|remove}().
1753  */
1754 int
1755 exec_init(int init_boot)
1756 {
...
1780 	LIST_FOREACH(ex, &ex_head, ex_list) {
...
1795 		sz++;
1796 	}
1797 
1798 	/*
1799 	 * Create new execsw[].  Ensure we do not try a zero-sized
1800 	 * allocation.
1801 	 */
1802 	sw = kmem_alloc(sz * sizeof(struct execsw *) + 1, KM_SLEEP);
...
1819 	execsw = sw;
1820 	nexecs = sz;

ここまでお分かりいただけたでしょうか、別に判らなくても何の問題もありません、人生にはもっと重要なことがあります。

@次回予告

最後の exec_add() はいつ呼び出されるか MONOTHILIC(物知り) および MODULAR それぞれのkernelのケースで説明します。

*1:つか構造体の名称と配列の変数名一緒だと説明しづらいんじゃ…