プロフィール

kosaki

Author:kosaki
連絡先はコチラ

ブログ検索
最近の記事
最近のコメント
最近のトラックバック
リンク
カテゴリー
月別アーカイブ
RSSフィード
FC2ブログランキング

スポンサーサイト このエントリーをはてなブックマークに追加

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。


スポンサー広告 | 【--------(--) --:--:--】 | Trackback(-) | Comments(-)

3.1からRLIMIT_NPROCの挙動が変わった件について このエントリーをはてなブックマークに追加

小ネタ

3.1からLinux の RLIMIT_NPROC のあつかいがちょっと変わります。端的にいうとNetBSDちっくな動きになりました。

まずバックグランドを説明すると、NPROCはユーザあたりのプロセス数を制限する機能であると。端的にいうとプロセス数超過するとforkがEAGAIN返して失敗する。プロセスを作る方法は1つしかないから一見自明に見える。

ところが「ユーザあたりのプロセス数」というのがキモで、プロセスの所有ユーザを変えてしまうという手がある。set*uid() 族と setuidされたプログラムに対するexec()族である。

余談。従来Linuxはset*uid()族はNPROCチェックをしてEAGAINを返していたが、execではチェックしていなかった。ついでにいうとforkでのチェックもちゃんとロックされてなかったので、プロセス数の厳密な保証はもとから無かった。NPROCなんてしょせんforkbomb対策のオモチャだから適当でいいんだよ。というのはLinusの弁。まあ、それは置く。

set*uid()でチェックすると何が問題かというと、POSIX的にはset*uid()は正しい引数を与える限りにおいて失敗しないので、かなり著名なソフトでも誰も返値をチェックしてない。ついでにいうとLinuxはmanページからしてEAGAINの記載がないというていらくで(※ごめん、いま見直したらmanにはちゃんと書いて有ったわ)エスパーでもない限り失敗するケースを思いつく人はいないという状況だった。

さて、set*uid()を使う場面というのは(ごく一部の例外を除いて)端的に言えばrootが非rootに変身して権限を落とすということであるので、ここで失敗してかつエラーチェックを怠るとroot権限をもったまま信頼できないプログラムをexecするアホdaemonが出来上がる。

で、権限を落とすシステムコールを失敗させるのはセキュリティ的にたいへん危険なので、権限昇格させるほう、つまりexec族で失敗させるのが正しいんじゃね?という意見が提出された。普通のデーモンは fork - setuid - exec の3つをつづけて呼ぶのでチェックがsetuidからexecに変わっても、実質の動きはかわらんでしょう、と。

で、いつものように互換性がにゃーにゃー、戻り値をチェックしないプログラムがワンワンと騒いでいたのだが、たまたまtwitterでsodaさんがNは何年も前からexecでチェックしてるとつぶやいていたのでLinusに転送してみたら採用されてしまった。

そのあと互換性議論が復活して、setuid使ってないプロセスはexec失敗しないよう仕様が若干変更され、現在の使用は
・set*uid()時にNPROC超過を検出し、かつ
・その後のexecでも再びNPROC超過を検出したとき
のみexecがEAGAINを返す。となっている

というわけで、この件でなにか困った事にぶちあたった人がいたらsodaさんをdisるといいと思うよ。

・・・という免責事項を軽くジャブしたあとでいうのもなんですけど、NetBSDのみなさんはRLIMIT_NPROCのコメントやドキュメントで「Linuxと違ってNetBSDはhogehoge」などと説明してるところをいちいち治して回る刺身たんぽぽの作業をするといいと思うよ。ではでは


以下はLinus treeに入ってるコミットログ

commit 72fa59970f8698023045ab0713d66f3f4f96945c
Author: Vasiliy Kulikov
Date: Mon Aug 8 19:02:04 2011 +0400

move RLIMIT_NPROC check from set_user() to do_execve_common()

The patch http://lkml.org/lkml/2003/7/13/226 introduced an RLIMIT_NPROC
check in set_user() to check for NPROC exceeding via setuid() and
similar functions.

Before the check there was a possibility to greatly exceed the allowed
number of processes by an unprivileged user if the program relied on
rlimit only. But the check created new security threat: many poorly
written programs simply don't check setuid() return code and believe it
cannot fail if executed with root privileges. So, the check is removed
in this patch because of too often privilege escalations related to
buggy programs.

The NPROC can still be enforced in the common code flow of daemons
spawning user processes. Most of daemons do fork()+setuid()+execve().
The check introduced in execve() (1) enforces the same limit as in
setuid() and (2) doesn't create similar security issues.

Neil Brown suggested to track what specific process has exceeded the
limit by setting PF_NPROC_EXCEEDED process flag. With the change only
this process would fail on execve(), and other processes' execve()
behaviour is not changed.

Solar Designer suggested to re-check whether NPROC limit is still
exceeded at the moment of execve(). If the process was sleeping for
days between set*uid() and execve(), and the NPROC counter step down
under the limit, the defered execve() failure because NPROC limit was
exceeded days ago would be unexpected. If the limit is not exceeded
anymore, we clear the flag on successful calls to execve() and fork().

The flag is also cleared on successful calls to set_user() as the limit
was exceeded for the previous user, not the current one.

Similar check was introduced in -ow patches (without the process flag).


関連記事


linux | 【2012-01-31(Tue) 06:13:23】 | Trackback:(0) | Comments:(3)
コメント
> POSIX的にはset*uid()は正しい引数を与える限りにおいて失敗しないので、かなり著名なソフトでも誰も返値をチェックしてない。

2.6.29 で COW Credentials が導入されて以来、可能性は低いですが set*uid()は正しい引数を与えても ENOMEM で失敗する場合が存在しています。

729 SYSCALL_DEFINE1(setuid, uid_t, uid)
730 {
731 const struct cred *old;
732 struct cred *new;
733 int retval;
734
735 new = prepare_creds();
736 if (!new)
737 return -ENOMEM;

また、LSMのチェックにより正しい引数を与えても失敗する可能性は常に存在しています。

なので、今回の件以前に、 set*uid() のエラーチェックをしない時点でアウトだという気がしてなりません。
2012-02-17 金 11:21:42 | URL | 熊猫さくら #- [ 編集]

熊猫さん、それはLKMLでいうべきだったのでは・・・・
2012-02-20 月 14:56:29 | URL | kosaki #- [ 編集]

しばらく考えたすえ、この件は問題ないという結論に

・prepare_creds()はGFP_KERNELだからいまの実装だとタスク死なない限り失敗しない
SIGKILL等でタスク死んだ時はENOMEMがユーザランドに見えるようになるまえに死ぬので無問題
・security_task_fix_setuid() は現在だれも使ってないから無害


逆に、security_task_fix_setuid() を削除するべきなんじゃないですかね。このパッチの主張が正しいなら
2012-02-21 火 14:51:07 | URL | kosaki #- [ 編集]
  1. 無料アクセス解析
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。