seteuid(2) でかわれるのは誰?
最近、会社の読書会で 入門 モダン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
-
特権は、root 及び、CAP_SETUID を持つプロセス ↩︎