6. LinuxのUIDのはなし

setuidとかseteuidとかの話。いまお行儀のよいdaemonを書こうとおもって、そこら辺を調べていた過程でUIDの話が出てきた。

実行中のプログラムにはReal UID, Effective UID, Saved UIDの3つがあって、普通は全部一緒になっている。使われるのはEffective UIDで、それ以外はUID操作に関する特殊用途で持っている。

Real UIDとEffective UIDのはなし

プログラムによっては(Effective) UIDを変更したいことがある。(Effective) UIDを変更したいシチュエーションは2つある

  • 一時的に(Effective) UIDを変更して、権限を落とす。(後で戻したい)
  • 恒久的に(Effective) UIDを変更して、権限を落とす。(後で戻せないようにしたい)

一時的なほうは別ユーザーとしてファイルを作りたいときに使って、恒久的なほうはrootで起動されたdaemonの権限を落としたいというときに使うらしい。

ここで問題なのは、一時的に変更するほうをやりたいときに、単純に一つしかUIDを持っていないと、権限の降格はできても、復帰がセキュリティ上難しくなるということだ。一つしかUIDを持っていないと、次のような2つのUID操作シーケンスが区別できない。

  1. root -> user -> root
  2. user -> root

1はrootから始まってuserに降格した後にrootに復帰している。逆に2はuserがrootに昇格しようとしている。1は許したいが(一時的に権限を落としただけ)、2は勝手にrootになられては困るので、禁止したい。

ここで、Real UIDとEffective UIDが出てくる。この2つのUIDがあると先の2つのシーケンスが区別できる。

1の場合、Effective UIDのみ変えればReal UIDはrootのままなので、もともとがrootであると判定できて、復帰できる。

    | RUID | EUID |
(1) | root | root |
                    <- seteuid(user)
(2) | root | user |
                    <- seteuid(root)
(3) | root | root |

2の場合、Effective UIDをrootにしようとしてもどのUIDもuserなので、昇格は禁止され る。

    | RUID | EUID |
(1) | user | user |
                    <- seteuid(root) EPERM

また、恒久的に落としたい場合はReal UIDも変更すればいい。

    | RUID | EUID |
(1) | root | root |
                    <- setuid(user)
(2) | user | user |
                    <- seteuid(root) EPERM

Saved UIDのはなし

Linuxではファイルにsetuidというパーミッションが付けられる。これは何かというと、通常の実行可能ファイルは実行ユーザーのReal UIDとEffective UIDを持つようになっているけれども、このパーミッションが付いているファイルはEffective UIDがファイルオーナーの権限になる。例えばuserがrootがオーナーの普通のファイルとrootがオーナーのsetuidファイルを起動すると、起動時のUIDは次のようになる。

            | RUID | EUID |
normal file | user | user |
setuid file | user | root |

こういう仕組みがあるとなんで便利なのかというと、例えばsudoみたいなプログラムが書けるようになる。rootじゃないといじれないようなリソースを使って動くプログラムで、それを一般ユーザーにも使えるようにさせたいという場合に便利ということらしい。setuidされたファイルはほとんどroot権限なので、以降、setuidファイルはrootオーナーのファイルという前提で話す。

setuidファイルはEffective UIDがrootになっているが、ここで先のような一時的にUIDを変えたり、恒久的にUIDを変えたりしたいとする。恒久的に変える方はそのまま同じようにsetuidすればいいのだが、一時的に変える方では、root -> user -> rootのようなシーケンスが、Real UIDとEffective UIDだけの世界では実現できない。

    | RUID | EUID |
(1) | user | root |
                    <- (seteuid)
(2) | user | user |
                    <- (seteuid) EPERM

ここで2番めの操作はうまくいって欲しい。そこでSaved UIDが出てくる。

    | RUID | EUID | SUID |
(1) | user | root | root |
                           <- (seteuid)
(2) | user | user | root |
                           <- (seteuid)
(3) | user | root | root |

このようにSaved UIDがあると、setuidファイルじゃないときと同じように権限の降格・復帰ができる。

Real UIDも起動時にファイルオーナーであってもいい気がしてくるが、そうするとsudoが誰のパスワードを聞けばいいのかわからなくなる。やっぱりReal UIDは実行を開始したユーザーである必要がある。