June 2013
readdir_r は使ってはいけないという話
Unixの世界には readdir_r()というAPIがある。readdir()のthread safe バージョンとしばしば紹介されている。
それぞれの関数宣言は以下
http://man7.org/linux/man-pages/man3/readdir_r.3.html
ところで、Solarisとかだと d_nameが d_name[1] だったりするので、
などとして、常にmallocで確保しないとポータブルではない。この例はLinuxの manから持ってきたものだが、このコードは問題がある。
"readdir_r considered harmful" [1] http://womble.decadent.org.uk/readdir_r-advisory.html によると以下の脆弱性がある
もし、setuidされた"rd" というプログラムが上記コード片を含んでいたとすると
# ln -sf exploit link && (rd link &; ln -sf /fat link)
pathconf(dirpath) が /fat を相手に行われて FATのrootの最大長は12byteだよーん、などという結果を受け取るので、buffer overflowの脆弱性となる。この場合はinjectionされるのがヒープなので実害がすくないが、allocaを使っていたり、
などと横着をしていると目もあてられない事になる。で、回避方法なんだがく、[1] だと BSD由来のdirfd APIでdir streamのfdとってきて fpathconf()でパス最大長取ってこいということになっている。が、もっといい方法がある
Ulrich Drepperが指摘したのだが、そもそも現代のOSではreaddir()はthread safeなんだから、それ使えばええんや。代替関数なんていらんかったんや。と指摘 [2]
http://udrepper.livejournal.com/18555.html
次世代POSIXでは readdir_rは時代遅れとマークされ、readdir()はDIRをスレッド間で共有しない限りスレッドセーフであることが mustになる。これは現状の追認だから誰も困らんだろう。
さて、実は readdir_r には Linux特有のもう1つ困った問題がある。Linuxは前述のとおり d_nameが256 byteになってるから、結構な数のアプリケーションがmalloc()とかせずに、スタックに直接 direntを置いている。普通はこれは問題ない、なぜなら、NAME_MAX は255 でPOSIXの世界でNAME_MAXがヘッダで defineされていたら、ファイルシステムの種類によらず NAME_MAXを超えてはいけないからである。
問題があるというからにはもちろんルールを破っているお馬鹿さんがいるからである。FUSEである。
linux/fs/fuse/fuse_i.h をみると
などというふざけたコメントとともに、何故か最大長が1024になっている。これによりfuseがONになっているシステムに readdir_r を使う setsidプロセスがインストールされた場合 root 特権が奪取できる穴が空く可能性がある。
何度でもいうが、スタックオーバーフローはヒープオーバーフローよりヤバいのであって、d_name[] が下手に大きいことによって、dirent がスタックに置かれる危険がましちゃってるのが Linux なのだ。
そういうわけで、アプリケーションデベロッパーのみなさまにおきましては、各自自衛のため、readdir_r()はreaddir()に書き換えていただくよう鋭意努力していだたくとよろしいかと思います。
わたしは今FUSEへこの件のパッチ書いてるんだけど、その話は別件なので、このエントリでは書かない。
おわり
あわせて読みたい: 革命の日々! 効率的なdirectry readingコードについて http://mkosaki.blog46.fc2.com/?no=1008
2016/09/28 追記
この件に関するPOSIXの議論はこのURL参照 http://austingroupbugs.net/view.php?id=696
readdir_rとFUSEの話はCVE-2013-4237として、glibc側で、カーネルがNAME_MAXを超えるパスを渡してきたら、readdir_rでENAMETOOLONGに変換してアプリケーションに返すというパッチが入った。NTFS, CIFSだとNAME_MAX超えるのは本来Validなので、Windows上でAttackerがそういうファイルをつくってしまうのを禁止できないからFUSEだけ考えてもしかたがない。
影響範囲はGNU C Library 2.18 およびそれ以前。
この件のリンク各種
Red Hat Bugzilla https://bugzilla.redhat.com/show_bug.cgi?id=995839
glibc Bugzilla https://sourceware.org/bugzilla/show_bug.cgi?id=14699
Red Hat CVE https://access.redhat.com/security/cve/cve-2013-4237
Red Hat RHSA https://rhn.redhat.com/errata/RHSA-2014-1391.html
JVN http://jvndb.jvn.jp/ja/contents/2013/JVNDB-2013-004574.html
それぞれの関数宣言は以下
http://man7.org/linux/man-pages/man3/readdir_r.3.html
struct dirent *readdir(DIR *dirp);
int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);
struct dirent {
ino_t d_ino; /* inode number */
off_t d_off; /* not an offset; see NOTES */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file; not supported
by all file system types */
char d_name[256]; /* filename */
};
ところで、Solarisとかだと d_nameが d_name[1] だったりするので、
name_max = pathconf(dirpath, _PC_NAME_MAX);
if (name_max == -1) /* Limit not defined, or error */
name_max = 255; /* Take a guess */
len = offsetof(struct dirent, d_name) + name_max + 1;
entryp = malloc(len);
などとして、常にmallocで確保しないとポータブルではない。この例はLinuxの manから持ってきたものだが、このコードは問題がある。
"readdir_r considered harmful" [1] http://womble.decadent.org.uk/readdir_r-advisory.html によると以下の脆弱性がある
もし、setuidされた"rd" というプログラムが上記コード片を含んでいたとすると
# ln -sf exploit link && (rd link &; ln -sf /fat link)
pathconf(dirpath) が /fat を相手に行われて FATのrootの最大長は12byteだよーん、などという結果を受け取るので、buffer overflowの脆弱性となる。この場合はinjectionされるのがヒープなので実害がすくないが、allocaを使っていたり、
union {
struct dirent a;
char b[1024];
}
などと横着をしていると目もあてられない事になる。で、回避方法なんだがく、[1] だと BSD由来のdirfd APIでdir streamのfdとってきて fpathconf()でパス最大長取ってこいということになっている。が、もっといい方法がある
Ulrich Drepperが指摘したのだが、そもそも現代のOSではreaddir()はthread safeなんだから、それ使えばええんや。代替関数なんていらんかったんや。と指摘 [2]
http://udrepper.livejournal.com/18555.html
次世代POSIXでは readdir_rは時代遅れとマークされ、readdir()はDIRをスレッド間で共有しない限りスレッドセーフであることが mustになる。これは現状の追認だから誰も困らんだろう。
さて、実は readdir_r には Linux特有のもう1つ困った問題がある。Linuxは前述のとおり d_nameが256 byteになってるから、結構な数のアプリケーションがmalloc()とかせずに、スタックに直接 direntを置いている。普通はこれは問題ない、なぜなら、NAME_MAX は255 でPOSIXの世界でNAME_MAXがヘッダで defineされていたら、ファイルシステムの種類によらず NAME_MAXを超えてはいけないからである。
問題があるというからにはもちろんルールを破っているお馬鹿さんがいるからである。FUSEである。
linux/fs/fuse/fuse_i.h をみると
/** It could be as large as PATH_MAX, but would that have any uses? */
#define FUSE_NAME_MAX 1024
などというふざけたコメントとともに、何故か最大長が1024になっている。これによりfuseがONになっているシステムに readdir_r を使う setsidプロセスがインストールされた場合 root 特権が奪取できる穴が空く可能性がある。
何度でもいうが、スタックオーバーフローはヒープオーバーフローよりヤバいのであって、d_name[] が下手に大きいことによって、dirent がスタックに置かれる危険がましちゃってるのが Linux なのだ。
そういうわけで、アプリケーションデベロッパーのみなさまにおきましては、各自自衛のため、readdir_r()はreaddir()に書き換えていただくよう鋭意努力していだたくとよろしいかと思います。
わたしは今FUSEへこの件のパッチ書いてるんだけど、その話は別件なので、このエントリでは書かない。
おわり
あわせて読みたい: 革命の日々! 効率的なdirectry readingコードについて http://mkosaki.blog46.fc2.com/?no=1008
2016/09/28 追記
この件に関するPOSIXの議論はこのURL参照 http://austingroupbugs.net/view.php?id=696
readdir_rとFUSEの話はCVE-2013-4237として、glibc側で、カーネルがNAME_MAXを超えるパスを渡してきたら、readdir_rでENAMETOOLONGに変換してアプリケーションに返すというパッチが入った。NTFS, CIFSだとNAME_MAX超えるのは本来Validなので、Windows上でAttackerがそういうファイルをつくってしまうのを禁止できないからFUSEだけ考えてもしかたがない。
影響範囲はGNU C Library 2.18 およびそれ以前。
この件のリンク各種
Red Hat Bugzilla https://bugzilla.redhat.com/show_bug.cgi?id=995839
glibc Bugzilla https://sourceware.org/bugzilla/show_bug.cgi?id=14699
Red Hat CVE https://access.redhat.com/security/cve/cve-2013-4237
Red Hat RHSA https://rhn.redhat.com/errata/RHSA-2014-1391.html
JVN http://jvndb.jvn.jp/ja/contents/2013/JVNDB-2013-004574.html