fileutils.rb のテストをしてたら変なことが起こった。
/tmp % ruby -e 'Dir.mkdir"a"; 20000.times{|i| open("a/#{i}","w"){}; open("a/passwd","w"){} }' /tmp % rm -rf a || ls a | wc -l rm: cannot remove directory `a': Directory not empty 9926 /tmp % rm -rf a || ls a | wc -l rm: cannot remove directory `a': Directory not empty 4817 /tmp % rm -rf a || ls a | wc -l rm: cannot remove directory `a': Directory not empty 2334 /tmp % rm -rf a || ls a | wc -l rm: cannot remove directory `a': Directory not empty 1022 /tmp % rm -rf a || ls a | wc -l rm: cannot remove directory `a': Directory not empty 436 /tmp % rm -rf a || ls a | wc -l rm: cannot remove directory `a': Directory not empty 144 /tmp % rm -rf a || ls a | wc -l
エントリが半分ずつしか減らない。いったいどうなってるんだ。
ちなみに /tmp 以外だと何も問題はない。
~/tmp % ruby -e 'Dir.mkdir"a"; 20000.times{|i| open("a/#{i}","w"){}; open("a/passwd","w"){} }' ~/tmp % rm -rf ~/tmp % ls a ls: a: No such file or directory
これはもしかして tmpfs のバグか?
/tmp % uname -srm Linux 2.4.22 i686 /tmp % mount | grep tmp none on /tmp type tmpfs (rw,mode=1777,size=64m)
報告されてた。これだな。
新しいバージョンでは修正されてんのかな。
/d/src/linux-2.4.30 % grep tmpfs ChangeLog-2.4.* ChangeLog-2.4.23: o tmpfs 1/5 LTP ENAMETOOLONG ChangeLog-2.4.23: o tmpfs 2/5 LTP S_ISGID dir ChangeLog-2.4.23: o tmpfs 3/5 swapoff/truncate race ChangeLog-2.4.23: o tmpfs 4/5 getpage/truncate race ChangeLog-2.4.23: o tmpfs 5/5 writepage/truncate race ChangeLog-2.4.25: o tmpfs readdir does not update dir atime ChangeLog-2.4.25: o Fix tmpfs dcache oops ChangeLog-2.4.27: o tmpfs surplus page miscounted ChangeLog-2.4.28: o tmpfs: stop negative dentries ChangeLog-2.4.28: o tmpfs: fix shmem_file_write return value
いっぱいあった。2.4.28 以降なら大丈夫そう?
(19:27)
次のような攻撃ができる可能性はあるんだろうか。 両プロセスのカレントディレクトリは /tmp で、 /tmp のパーミッションは 1777 とする。
rm -rf プロセス(euid=0) クラックプロセス(euid=1000) --------------------------------------------------------------------- (プログラム cmd を作り chmod(01777, "cmd")) chmod(01777, "cmd") mkdir("a") readdir("/tmp") unlink("a") = EISDIR rmdir("a") rename("cmd", "a") lchown(0, "a") execl("./a") # setuid root で動作? lchmod(0700, "a")
Linux 2.4 では lchown(2) で suid bit がクリアされたけど、 SUSv3 によると実装依存らしい。
If the specified file is a regular file, one or more of the S_IXUSR, S_IXGRP, or S_IXOTH bits of the file mode are set, and the process has appropriate privileges, it is implementation-defined whether the set-user-ID and set-group-ID bits are altered.
……だりい……。 家にあるシステムを片端から実行してみて問題なければ OKってことにしちゃおうかなあ……。
アプリケーションを特定できればなんとかなるはずなんだよなあ。 万能のメソッドを一個だけ作ろうとするから無理なわけで……。
結論は fork, setuid, chdir ってことで。冗談です。
うーん、open したディレクトリに対して chown とか chmod できればいいのかなあ。 それなら普通に stat が使えるし。
え、いや違うか、 open する対象がすりかえられたら回避できない?! そうか、だから lchown が必要なんだっけ。 なんだ、それじゃ lchown が suid bit をクリアしないシステムでは どうやってもセキュアにはならないんだな。 それならクリアされるほうに賭けたほうが得だ。
テンポラリファイルを使わずに調べよう、 シンボリックリンク編
/tmp % cat /d/tmp/have-symlink-p.rb def have_symlink? File.symlink nil, nil rescue NotImplementedError return false rescue return true end p have_symlink? /tmp % test-all-ruby /d/tmp/have-symlink-p.rb ruby 1.4.6 (2000-08-16) [i686-linux] /d/tmp/have-symlink-p.rb:3: parse error rescue NotImplementedError ^ /d/tmp/have-symlink-p.rb:3: warning: useless use of a constant in void context /d/tmp/have-symlink-p.rb:5: parse error ruby 1.6.0 (2000-09-19) [i686-linux] true ruby 1.6.1 (2000-09-27) [i686-linux] true ruby 1.6.2 (2000-12-25) [i686-linux] true ruby 1.6.3 (2001-03-19) [i686-linux] true ruby 1.6.4 (2001-06-04) [i686-linux] true ruby 1.6.5 (2001-09-19) [i686-linux] true ruby 1.6.6 (2001-12-26) [i686-linux] true ruby 1.6.7 (2002-03-01) [i686-linux] true ruby 1.6.8 (2002-12-24) [i686-linux] true ruby 1.8.0 (2003-08-04) [i686-linux] true ruby 1.8.1 (2003-12-25) [i686-linux] true ruby 1.9.0 (2005-05-16) [i686-linux] true
Windows での状況
~/src/ruby % ruby-vc6 -v have-symlink-p.rb ruby 1.9.0 (2005-03-28) [i386-mswin32] false ~/src/ruby % ruby-cygwin -v have-symlink-p.rb ruby 1.9.0 (2005-03-28) [i386-cygwin] true
ハードリンクも同じようにすれば調べられる。
しかし、なんか妙な知識ばっかり蓄積されてくなあ。
(02:20)
Copyright (c) 2002-2007 青木峰郎 / Minero Aoki. All rights reserved.