最近、会社の読書会で 入門 モダンLinux を読んでいる。 「保存 set-user-ID」の説明で、以下のような記載があり「切り替えられる」ってことは、 プロセス実行時だけじゃなく、実行中にも変更できるってこと?って、質問が出たので、確認してみた。

4.3.2 プロセスの権限

保存 set-user-ID は setuid ビットが設定されている場合に使う。プロセスが実 UID と保存 set-user-ID の 間で実効 UID を切り替えられる

出典: 入門 モダンLinux Michael Hausenblas 著、武内 覚、大岩 尚宏 訳

setuidビット

passwd コマンドとかについてるアレ。 rootオーナのコマンドが setuid されていると、一般ユーザ(ubuntu とか)で実行しても、 root 権限で実行される。

-rwsr-xr-x 1 root root 55544 Nov 24  2022 /usr/bin/passwd

?疑問点

passwdコマンドのような動きはよくあるやつなのでいいが、疑問に思ったのは、 プロセスを「root 権限で起動し、そこから一般ユーザに切り替えて、さらに root権限に戻る」みたいなことができるのか?ってところ。

「root」→「一般ユーザ」→「root」。一度、一般ユーザになったら、root に戻れない?

準備

macos 上の multipass(Ubuntu 22.04) で確認。

権限の切り替えは seteuid(2) で行うので、 「seteuid() した後、現在の各uid を表示するプログラム 」を書いて、動作確認してみる。 (ソースは、下の方に貼ってます)

コンパイルした「seteuidcheck」を、rootオーナに変更し、setuid ビットを立てる。

ubuntu@primary:~$ sudo chown root ./seteuidcheck ; sudo chmod u+s $_ ; ll $_
-rwsrwxr-x 1 root ubuntu 9152 Jun  8 21:13 ./seteuidcheck*

seteuidcheck は、起動されると現在の uid の表示を行う。

ubuntu@primary:~$ ./seteuidcheck
ruid : 1000
euid : 0
suid : 0
  • ruid(実UID): ubuntuユーザ(UID:1000)で実行したので、1000
  • euid(実行UID): root オーナ(UID:0)で setuid されたプログラムを実行したので、0
  • suid(保存set-user-ID): setuid されたプログラムのオーナーが記録されるので、0

確認1:「root」→「一般ユーザ」→「root」

では、さっそく、疑問点を確認してみる。

この seteuidcheck は「引数に uid が渡されると、その uid に seteuid() した後、 各uid を表示する」ようにしている。

# euid を 0 → 1000 → 0 に変更して見る
ubuntu@primary:~$ ./seteuidcheck 1000 0
ruid : 1000
euid : 0
suid : 0

==== seteuid( 1000 ) ====
ruid : 1000
euid : 1000
suid : 0

==== seteuid( 0 ) ====
ruid : 1000
euid : 0
suid : 0

root権限(euid:0)で起動された後、一般ユーザ(UID:1000)に seteuid すると、 euid は 0→1000 に変わる。ruid・suid の変化はない。

さらに rootユーザに seteuid() すると、euid が 1000→0 に変わっているので 「一般ユーザ権限から root 権限に、問題なく戻せる」ことが確認できた。

確認2:「root」→「一般ユーザA」→「一般ユーザB」

ただ、上の結果だと、seteuid() が「suidの権限(root)」で実行されているようにも 見えるので、一般ユーザ間の切り替え(UID:1000 → UID:1001)も試す。

ubuntu@primary:~$ ./seteuidcheck 1000 1001
ruid : 1000
euid : 0
suid : 0

==== seteuid( 1000 ) ====
ruid : 1000
euid : 1000
suid : 0

==== seteuid( 1001 ) ====
seteuid() error: Operation not permitted
ruid : 1000
euid : 1000
suid : 0

結果、切り替え失敗。ということは、suid の権限で setuid() が実行されているわけではない (なお、root に seteuid() した後なら、1001 にも切り替えできた)。

まとめ

  • ruid(実UID)は、プログラムを実行したユーザの uid
  • suid(保存set-user-ID)は、プログラムが setuid されていれば、そのオーナの uid になる (されていなければ、ruid と同じ)
  • euid(実行UID)は、seteuid() で変更可能。初期値は suid と同じ
  • setuid() できるuidは、ruid か suid。ただし、特権 1 がある時は、それ以外もOK

seteuidcheck.c

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <alloca.h>

// 各UIDを表示する
int print_resuid() {

  uid_t *ruid = alloca(sizeof(uid_t)); //実UID
  uid_t *euid = alloca(sizeof(uid_t)); //実効UID
  uid_t *suid = alloca(sizeof(uid_t)); //保存set-user-ID

  getresuid(ruid, euid, suid);

  printf("ruid : %d\n", *ruid);
  printf("euid : %d\n", *euid);
  printf("suid : %d\n\n", *suid);

  return 0;
}

int main(int argc, char *argv[]) {

  print_resuid();

  int i;
  uid_t uid;

  for (i = 0; i < argc; i++) {

    // 引数の文字列を uid_t型に変換
    char *endptr;
    uid = strtol(argv[i], &endptr, 10);

    // エラーがなければ、seteuid() を実行
    if (*endptr == '\0') {
      printf("==== seteuid( %d ) ====\n", uid);
      if (seteuid(uid) != 0)
        perror("seteuid() error");
      print_resuid();
    }
  }

  return 0;
}

コンパイル

ubuntu@primary:~$ make seteuidcheck
cc     seteuidcheck.c   -o seteuidcheck
ubuntu@primary:~$
ubuntu@primary:~$ ll seteuidcheck
-rwxrwxr-x 1 ubuntu ubuntu 9152 Jun  8 21:13 seteuidcheck*
ubuntu@primary:~$
ubuntu@primary:~$ ./seteuidcheck
ruid : 1000
euid : 1000
suid : 1000

CAP_SETUID

ケーパビリティの場合も確認。setuid していない seteuidcheck を、 ubuntu ユーザで実行しても、root で実行した時と同様の動きになる。

ubuntu@primary:~$ sudo setcap cap_setuid=ep ./seteuidcheck
ubuntu@primary:~$
ubuntu@primary:~$ getcap ./seteuidcheck
./seteuidcheck cap_setuid=ep
ubuntu@primary:~$
ubuntu@primary:~$ ll ./seteuidcheck
-rwxrwxr-x 1 ubuntu ubuntu 9200 Jun  9 10:10 ./seteuidcheck*
ubuntu@primary:~$
ubuntu@primary:~$ ./seteuidcheck 1001 0
ruid : 1000
euid : 1000
suid : 1000

==== seteuid( 1001 ) ====
ruid : 1000
euid : 1001
suid : 1000

==== seteuid( 0 ) ====
ruid : 1000
euid : 0
suid : 1000

  1. 特権は、root 及び、CAP_SETUID を持つプロセス ↩︎