[JM:01254] Re: [POST: DP] sudo sudo.8

アーカイブの一覧に戻る

長南洋一 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 オプションでパスワード・プロンプトの変更が
できてしまいます。まあ、だれも気にしないことでしょうが。

-- 
長南洋一




linuxjm-discuss メーリングリストの案内
アーカイブの一覧に戻る