長南洋一
cyoic****@maple*****
2016年 8月 27日 (土) 11:46:50 JST
長南です。 わからないところがあるのですが、それをどう説明するかが難しい。 モタモタしているうちに、日にちが経ってしまいました。 混乱した説明になると思いますが、とにかく書いてしまいます。 長すぎる引用ですが、次の二ヶ所は、sudo-1.8.4 の man には 存在しなかった部分です。内容がよくわからないまま訳していますから、 つじつまの合わないところがあるかもしれません。お気づきの点が あれば、ご指摘ください。 Process model When sudo runs a command, it calls fork(2), sets up the execution environment as described above, and calls the execve system call in the child process. The main sudo process waits until the command has completed, then passes the command's exit status to the security policy's close function and exits. If an I/O logging plugin is configured or if the security policy explicitly requests it, a new pseudo-terminal (pty) is created and a second sudo process is used to relay job control signals between the user's existing pty and the new pty the command is being run in. This extra process makes it possible to, for example, suspend and resume the command. Without it, the command would be in what POSIX terms an orphaned process group and it would not receive any job control signals. As a special case, if the policy plugin does not define a close function and no pty is required, sudo will execute the command directly instead of calling fork(2) first. The sudoers policy plugin will only define a close function when I/O logging is enabled, a pty is required, or the pam_session or pam_setcred options are enabled. Note that pam_session and pam_setcred are enabled by default on systems using PAM. プロセス・モデル sudo は、コマンドを実行するとき、まず fork(2) を呼び、 実行環境を上記のように設定してから、子プロセスで execve システムコールを呼び出す。 メインの sudo プロセスは、 コマンドが完了するまで wait し、完了したら、 コマンドの 終了ステータスをセキュリティポリシーの close 関数に渡してから、 終了する。入出力ロギング・プラグインが設定されている場合や、 セキュリティポリシーが明示的にそれを要求している場合は、 擬似端末 ("pty") が新規に作成され、二つ目の sudo プロセスが、 既に存在しているユーザの pty と、コマンドがそこで実行されて いる新しい pty との間で、 ジョブ制御シグナルを中継するために 使用される。この二つ目の sudo プロセスによって、たとえば、 コマンドのサスペンドやレジュームといったことが可能になるので ある。 この仕組みがなければ、コマンドは、POSIX で "orphaned process group" と言われる状態に陥り、どんなジョブ制御シグナル も受け取れないことになってしまうだろう。 なお、特殊ケースとして 次のことがある。ポリシー・プラグインが close 関数を定義していず、 しかも、pty が要求されていない場合は、 sudo は fork(2) を最初に 呼ぶことをせず、直接コマンドを実行する。sudoers ポリシー・ プラグインで close 関数が定義されることになるのは、入出力 ロギングが有効か、pty が要求されているか、pam_session または pam_setcred が有効な場合だけである。PAM を使用している システムでは、 デフォルトで pam_session と pam_setcred が 有効になることに注意していただきたい。 自分で気がついたことですが、「特殊ケースとして (As a special case)」 以下の文は、実際の動作とは違うのではないかと思います。 うちの debian jessie には /etc/pam.d/sudo がありますから、 PAM を使用している (と言うことは、プラグインは close 関数を 定義している) と思いますが、念のため /etc/sudoers に Defaults !use_pty Defaults pam_session Defaults pam_setcred と書いて、sudo-1.8.17 で "sudo sleep 15" を実行してみました。 "ps axf" で見ると、こうなります。 1240 ? S 0:00 xterm -class UXTerm ... 1246 pts/0 Ss 0:00 \_ bash 1817 pts/0 S+ 0:00 \_ sleep 15 つまり、実際の動作は、「close 関数が定義されていず、しかも、 pts が要求されていないと、sudo は fork せず、直接コマンドを 実行する」ではなく、「close 関数が定義されているか、いないかに 関係なく、pts が要求されていないならば、直接コマンドを実行する」 のようです。 ちなみに、"Defaults use_pts" の場合の "ps axf" の結果は次の ようになります (pam_session, pam_setcred を有効にしても、 無効にしても、結果は同じ)。 1240 ? S 0:00 xterm -class UXTerm ... 1246 pts/0 Ss 0:00 \_ bash 1795 pts/0 S+ 0:00 \_ sudo sleep 15 1796 pts/3 Ss 0:00 \_ sudo sleep 15 1797 pts/3 S+ 0:00 \_ sleep 15 まとめて言うと、「sudo-1.8.17 では、pty が要求されていれば、 sudo は fork する」。 原文の説明が古くなっているのでしょうか (sudo-1.8.10 あたりから 記述が変わっていません)。それとも、わたしが何か勘違いをしている のでしょうか。 今朝、ふと思いついて、jessie のデフォルトの sudo-1.8.10p3 で "/usr/bin/sudo sleep 15" を試してみました。 条件を次のようにした場合。 Defaults !use_pty Defaults pam_session Defaults pam_setcred "ps axf" は、 1240 ? S 0:00 xterm -class UXTerm -title ... 1246 pts/0 Ss 0:00 \_ bash 1597 pts/0 S+ 0:00 \_ /usr/bin/sudo sleep 15 1598 pts/0 S+ 0:00 \_ sleep 15 条件を次のようにすると、 Defaults !use_pty Defaults !pam_session Defaults !pam_setcred 1240 ? S 0:00 xterm -class UXTerm -title ... 1246 pts/0 Ss 0:00 \_ bash 1639 pts/0 S+ 0:00 \_ sleep 15 どうやら、動作が変わって、説明が古くなったということのようです。 訳文は、原文のとおりにしておくのが穏当なところでしょうか。 Signal handling When the command is run as a child of the sudo process, sudo will relay signals it receives to the command. The SIGINT and SIGQUIT signals are only relayed when the command is being run in a new pty or when the signal was sent by a user process, not the kernel. This prevents the command from receiving SIGINT twice each time the user enters control-C. Some signals, such as SIGSTOP and SIGKILL, cannot be caught and thus will not be relayed to the command. As a general rule, SIGTSTP should be used instead of SIGSTOP when you wish to suspend a command being run by sudo. シグナルの処理 コマンドが sudo プロセスの子プロセスとして実行されているとき、 sudo は自分が受け取ったシグナルをそのコマンドに中継する。 ただし、SIGINT や SIGQUIT シグナルが中継されるのは、その コマンドが新たに開いた pty で実行されているときか、シグナルが カーネルではなく、ユーザ・プロセスによって送出されたときだけ である。そうなっていることで、ユーザが control-C を入力する たびに、コマンドが SIGINT シグナルを二重に受け取らないように しているのだ。 SIGSTOP や SIGKILL のようないくつかのシグナルは、 捕獲できないので、コマンドに中継されることもない。だから、 sudo によって実行されているコマンドをサスペンドしたかったら、 原則として、SIGSTOP ではなく、SIGTSTP コマンドを使用するべき である。 As a special case, sudo will not relay signals that were sent by the command it is running. This prevents the command from accidentally killing itself. On some systems, the reboot(8) command sends SIGTERM to all non-system processes other than itself before rebooting the system. This prevents sudo from relaying the SIGTERM signal it received back to reboot(8), which might then exit before the system was actually rebooted, leaving it in a half-dead state similar to single user mode. Note, however, that this check only applies to the command run by sudo and not any other processes that the command may create. As a result, running a script that calls reboot(8) or shutdown(8) via sudo may cause the system to end up in this undefined state unless the reboot(8) or shutdown(8) are run using the exec() family of functions instead of system() (which interposes a shell between the command and the calling process). sudo は原則として、自分が受け取ったシグナルを子プロセスに 中継するわけだが、 自分が実行しているコマンドから来たシグナルは、 中継しないという例外がある。コマンドが意図に反して自分自身を 殺してしまわないようにしているのだ。システムによっては、 reboot(8) コマンドが、システムをリブートする前に、 自分自身を 除くすべてのノン・システム・プロセスに SIGTERM を送るものがある。 そうした場合も、中継の抑制があるため、sudo は自分が受け取った SIGTERM シグナルを reboot(8) に送り返さない。もし送り返すように なっていたら、システムが実際にリブートする前に reboot(8) が 終了して、システムがシングルユーザ・モードによく似た半分死んだ 状態 (half-dead state) に陥ってしまうだろう。とは言え、 注意していただきたいが、 この中継の抑制が行われるのは、 sudo によって直接実行されるコマンドに対してのみであり、 そのコマンドが生成するかもしれない他のどんなプロセスに対しても 当てはまらない。 それ故、reboot(8) や shutdown(8) を呼び出す スクリプトを sudo 経由で実行すると、システムがそうしたわけの わからない状態に陥ることがある。 reboot(8) や shutdown(8) の 実行に exec() ファミリーの関数ではなく、 system() 関数を使用して いると、(system() は、呼び出しプロセスとコマンドの間にシェルを 挟むため) そうしたことが起こりかねないのだ。 If no I/O logging plugins are loaded and the policy plugin has not defined a close() function, set a command timeout or required that the command be run in a new pty, sudo may execute the command directly instead of running it as a child process. 入出力ロギング・プラグインがロードされていない場合に、 ポリシー・プラグインが close() 関数を定義してもいず、 コマンドのタイムアウトを設定していることもなく、コマンドを 新たに開いた pty で実行することを要求してもいなかったならば、 sudo は、コマンドを子プロセスとしてではなく、直接実行する かもしれない。 最後の文ですが、"not ... or ..." は "neither ... nor .." でしょうから、 上のように訳しましたが、これは「プロセス・モデル」で言ったのと同じ 問題があると思います。 なお、"set a command timeout" というのがわからなかったのですが、 これは sudo_plugin.man に説明がありました。よくわかりませんけれど。 ついでに、もう一つ。-p オプションの説明の最後の部分です。 The custom prompt will override the system password prompt on systems that support PAM unless the passprompt_override flag is disabled in sudoers. 自家特製のプロンプトが、 PAM をサポートしているシステムで システムのパスワードプロンプトに置き替わるのは、 sudoers で passprompt_override フラグが無効になっていない場合である これも実際の動作とは違うような気がします。/etc/sudoers で "Defaults !passprompt_override" と書いても (man sudoers によれば、 それがデフォルト)、-p オプションでパスワード・プロンプトの変更が できてしまいます。まあ、だれも気にしないことでしょうが。 -- 長南洋一