sudo はなぜ sudo できるのか
概要・背景
この記事では、 sudo
コマンドが root
等の任意のユーザとしてプロセスを実行できる仕組みを、実践を交えながら説明します。
UNIX 系の OS において、システムに対する重要な変更を行うときには sudo
コマンドを使用します。
システムの重要な部分は root
ユーザ=スーパーユーザしか読み書きができなくなっている場合があります。
sudo
コマンドは、プロセスの所有者をその root
ユーザに変更することで、システムの重要な部分への操作を可能にします。
また、 root
以外の一般ユーザへも切り替えが可能です。
「そういえば、こうして root
ユーザに変化できる仕組み知らないな〜」と思ったので、調べた内容をここにまとめます。
なお、筆者が自由に扱えるホストがあったという理由で、 Linux のみに限って説明します。
macOS 等別の OS では異なると思われますので、ご注意ください。
なお、本記事で登場する各 C プログラムは GitHub で公開しています。
TL;DR
- プロセスは、
setuid
系のシステムコールを使って、自身の権限に紐づく UID を変更できる -
CAP_SETUID
ケイパビリティを持つプロセスだけが、setuid
系のシステムコールを呼び出せる -
root
ユーザが所有するプロセスは、CAP_SETUID
を含めた全てのケイパビリティを持つ -
/usr/bin/sudo
は、 SUID bit により、誰が呼び出してもroot
ユーザで実行される
前提知識
- Linux の権限管理
- ユーザ、グループ、パーミッション
- システムコール
環境
$ uname -a
Linux ops 5.4.0-153-generic #170-Ubuntu SMP Fri Jun 16 13:43:31 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
$ gcc --version
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
setuid
系システムコール
さっそく結論ですが、 Linux には seteuid
というシステムコールが存在し[1]、プロセスはこのシステムコールを呼び出すことで自身の所有者を変更することができます。
このシステムコールは、 uid_t
型で変更先のユーザ ID (UID) を受け取り、プロセスの effective user ID をその UID に変更します。
effective user ID とは、プロセスがさまざざまなリソース (ファイル等) にアクセスする際の権限チェックに使われる UID です[2]。
プロセスに結びついた UID は、 effective user ID 以外にも real user ID や saved user ID などがあります。
seteuid
に加えて、それらの UID を一括で書き換えるシステムコールとして setuid
[3] や setreuid
[4]、 setresuid
[5] が提供されています。
sudo
は、これらのシステムコールを使ってプロセスの UID を変更し、 root
等のユーザとしてプログラムを実行します[6]。
seteuid
の使用例
setuid
システムコールを使って UID を変更し、 sudo
同等の機能を実現する C プログラムを以下に示します。
// Default to root UID
#define UID 0
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
if (seteuid(UID) != 0) {
printf("seteuid failed\n");
return 1;
}
if (argc <= 1) {
printf("command not specified\n");
return 1;
}
execvp(argv[1], &argv[1]);
// execvp never returns unless some error occured
printf("some error occured\n");
return 1;
}
以下のようにコンパイル・実行できます。
gcc setuid-sudo.c
./a.out whoami
期待通りであれば、以下の出力がされます。
root
CAP_SETUID
ケイパビリティ
しかし、残念ながら先ほどのプログラムは期待通り動作しません。
プログラムが正しい権限を持たないからです。
Linux では、ケイパビリティという仕組みで、プロセスがシステムに対して行える操作を制限しています。
先ほどのプログラムが何の工夫もなく root
としてプログラムを実行できてしまうと、どんなユーザであってもシステムに変更を加えられることになってしまいます。
それを防ぐために、 setuid
系システムコールを発行するためには、プロセスは CAP_SETUID
ケイパビリティを持っている必要があります。
このことは、 seteuid
[1:1] のマニュアルに明記されています。
seteuid() sets the effective user ID of the calling process.
Unprivileged processes may only set the effective user ID to the
real user ID, the effective user ID or the saved set-user-ID.
筆者による訳:
setuid() は、呼び出し元プロセスの effective user ID を設定します。権限を持たないプロセスは、現在の real user ID, effective user ID、または saved set-user-ID にのみ設定が可能です。
つまり、起動したプロセスの各 UID は一般ユーザ(筆者の場合は kino-ma
(UID=1000
))であるにもかかわらず、異なる値である root
(UID=0
) に設定しようとしたため、失敗してしまったわけです。
実際、先ほどのプログラムを以下のように編集して確認すると、CAP_SETUID
ケイパビリティが付与されていないことがわかります。
#define UID 0
#include <unistd.h>
#include <sys/capability.h>
#include <sys/types.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
int check_cap() {
pid_t pid = getpid();
cap_t cap = cap_get_pid(pid);
if (cap == NULL) {
printf("failed to get capabilites\n");
return 1;
}
cap_value_t cap_value = CAP_SETUID;
cap_flag_value_t cap_flags_value;
cap_get_flag(cap, cap_value, CAP_EFFECTIVE, &cap_flags_value);
printf("EFFECTIVE %s\n", (cap_flags_value == CAP_SET) ? "YES" : "NO");
cap_get_flag(cap, cap_value, CAP_PERMITTED, &cap_flags_value);
printf("PERMITTED %s\n", (cap_flags_value == CAP_SET) ? "YES" : "NO");
cap_get_flag(cap, cap_value, CAP_INHERITABLE, &cap_flags_value);
printf("INHERITABLE %s\n", (cap_flags_value == CAP_SET) ? "YES" : "NO");
return 0;
}
int main(int argc, char** argv) {
if (check_cap() != 0) {
printf("check_cap failed\n");
return 1;
}
// ... 以下略 ...
}
実行(libcap-dev が必要です):
gcc cap-setuid.c -lcap
./a.out whoami
出力:
EFFECTIVE NO
PERMITTED NO
INHERITABLE NO
seteuid failed
CAP_SETUID
ケイパビリティを追加する
プログラムに CAP_SETUID
ケイパビリティさえあれば、期待通り sudo
のような機能を実現することができます。
やり方は簡単です。
コンパイル後に下のコマンドを実行します。
sudo setcap 'cap_setuid=ep' a.out
再度試してみます。
./a.out whoami
出力:
EFFECTIVE YES
PERMITTED YES
INHERITABLE NO
root
Effective, Permitted のケイパビリティセットで CAP_SETUID
が有効になり、期待通り whoami
コマンドが実行できたことがわかります。
/usr/bin/sudo
のケイパビリティ
では、この CAP_SETUID
が設定されていることが理由で、 sudo
コマンドが機能するのでしょうか。
確認してみます。
getcap $(which sudo)
何も表示されませんでした。
先ほど setuid ケイパビリティを設定した a.out
では以下の通り表示されます。
反対に何も表示されない sudo
では、特にケイパビリティが設定されていないと推測することができます。
$ getcap a.out
a.out = cap_setuid+ep
しかし、 seteuid
のマニュアルにあった通り、 CAP_SETUID
は必須なはずです。
いつ CAP_SETUID
が有効になるのでしょうか。
root
プロセスのケイパビリティ
capability のマニュアルを読むと、冒頭に以下の記載があります。
For the purpose of performing permission checks, traditional UNIX
implementations distinguish two categories of processes:
privileged processes (whose effective user ID is 0, referred to
as superuser or root), and unprivileged processes (whose
effective UID is nonzero). Privileged processes bypass all
kernel permission checks, while unprivileged processes are
subject to full permission checking based on the process's
credentials (usually: effective UID, effective GID, and
supplementary group list).Starting with Linux 2.2, Linux divides the privileges
traditionally associated with superuser into distinct units,
known as capabilities, which can be independently enabled and
disabled. Capabilities are a per-thread attribute.
筆者意訳:
伝統的な UNIX 実装は、プロセスを「特権あり」「なし」に分割します。
特権ありのプロセスとは、 effectvie user ID が 0、つまり root またはスーパユーザであるようなものです。
特権ありプロセスは、カーネルによる全ての権限チェックを通過することができる一方、特権なしプロセスは effective UID/GID、追加グループリストなどをもとに権限チェックが行われます。Linux 2.2 から、特権を個別の単位に分離するようになりました。
それらはケイパビリティと呼ばれ、個別に有効化・無効化できます。
また、ケイパビリティはスレッドごとに設定されます。
したがって、(明記はされていませんが……) root が実行したプロセスはデフォルトで全てのケイパビリティが有効になると推測できます。
root
プロセスのケイパビリティを確認する
以下のプログラムで、全てのケイパビリティを確認しましょう。
参考: lcap.c
(Gist)
(私の環境に合わせて改変しました[7]。)
#include <unistd.h>
#include <sys/capability.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
const char *cap_name[CAP_LAST_CAP+1] = {
"CAP_AUDIT_CONTROL",
"CAP_AUDIT_READ",
"CAP_AUDIT_WRITE",
"CAP_BLOCK_SUSPEND",
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_KILL",
"CAP_LEASE",
"CAP_LINUX_IMMUTABLE",
"CAP_MAC_ADMIN",
"CAP_MAC_OVERRIDE",
"CAP_MKNOD",
"CAP_NET_ADMIN",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_SETUID",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_CHROOT",
"CAP_SYS_MODULE",
"CAP_SYS_NICE",
"CAP_SYS_PACCT",
"CAP_SYS_PTRACE",
"CAP_SYS_RAWIO",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_SYSLOG",
"CAP_WAKE_ALARM",
};
pid_t pid = getpid();
cap_t cap = cap_get_pid(pid);
if (cap == NULL) {
printf("failed to get capabilites\n");
return 1;
}
cap_value_t cap_value;
cap_flag_value_t cap_flags_value;
for (int i = 0; i < CAP_LAST_CAP + 1; i += 1) {
cap_from_name(cap_name[i], &cap_value);
printf("%-20s %d\t\t", cap_name[i], cap_value);
printf("flags: \t\t");
cap_get_flag(cap, cap_value, CAP_EFFECTIVE, &cap_flags_value);
printf(" EFFECTIVE %-4s ", (cap_flags_value == CAP_SET) ? "YES" : "NO");
cap_get_flag(cap, cap_value, CAP_PERMITTED, &cap_flags_value);
printf(" PERMITTED %-4s ", (cap_flags_value == CAP_SET) ? "YES" : "NO");
cap_get_flag(cap, cap_value, CAP_INHERITABLE, &cap_flags_value);
printf(" INHERITABLE %-4s ", (cap_flags_value == CAP_SET) ? "YES" : "NO");
printf("\n");
}
}
まずは一般ユーザとして起動してみます。
gcc all-caps.c -lcap
./a.out
出力:
CAP_AUDIT_CONTROL 30 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_AUDIT_READ 37 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_AUDIT_WRITE 29 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_BLOCK_SUSPEND 36 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_CHOWN 0 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_DAC_OVERRIDE 1 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_DAC_READ_SEARCH 2 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_FOWNER 3 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_FSETID 4 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_IPC_LOCK 14 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_IPC_OWNER 15 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_KILL 5 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_LEASE 28 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_LINUX_IMMUTABLE 9 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_MAC_ADMIN 33 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_MAC_OVERRIDE 32 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_MKNOD 27 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_NET_ADMIN 12 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_NET_BIND_SERVICE 10 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_NET_BROADCAST 11 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_NET_RAW 13 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_SETGID 6 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_SETFCAP 31 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_SETPCAP 8 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_SETUID 7 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_SYS_ADMIN 21 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_SYS_BOOT 22 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_SYS_CHROOT 18 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_SYS_MODULE 16 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_SYS_NICE 23 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_SYS_PACCT 20 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_SYS_PTRACE 19 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_SYS_RAWIO 17 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_SYS_RESOURCE 24 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_SYS_TIME 25 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_SYS_TTY_CONFIG 26 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_SYSLOG 34 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
CAP_WAKE_ALARM 35 flags: EFFECTIVE NO PERMITTED NO INHERITABLE NO
特権ユーザ (root
) ではなく、また setcap
による権限追加も行なっていないため、すべてのケイパビリティが無効になっています。
次に、ルートユーザとして実行してみます。
ここで、 sudo
コマンドを使うと結果に何か影響があるかもしれないので、念の為 root
ユーザとしてログインしたシェルで実行します。
# ./a.out
CAP_AUDIT_CONTROL 30 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_AUDIT_READ 37 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_AUDIT_WRITE 29 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_BLOCK_SUSPEND 36 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_CHOWN 0 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_DAC_OVERRIDE 1 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_DAC_READ_SEARCH 2 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_FOWNER 3 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_FSETID 4 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_IPC_LOCK 14 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_IPC_OWNER 15 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_KILL 5 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_LEASE 28 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_LINUX_IMMUTABLE 9 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_MAC_ADMIN 33 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_MAC_OVERRIDE 32 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_MKNOD 27 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_NET_ADMIN 12 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_NET_BIND_SERVICE 10 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_NET_BROADCAST 11 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_NET_RAW 13 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SETGID 6 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SETFCAP 31 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SETPCAP 8 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SETUID 7 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_ADMIN 21 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_BOOT 22 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_CHROOT 18 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_MODULE 16 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_NICE 23 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_PACCT 20 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_PTRACE 19 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_RAWIO 17 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_RESOURCE 24 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_TIME 25 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_TTY_CONFIG 26 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYSLOG 34 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_WAKE_ALARM 35 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
全てのケイパビリティが有効になっている[8] ことがわかります。
たしかに、 root ユーザとしてプロセスを起動することで、 CAP_SETUID
を含めた全ての capability が有効になると確かめられました。
set-user-ID bit
/usr/bin/sudo
ファイル自身に CAP_SETUID
が設定されていない以上、 sudo
は何らかの手段で root
UID で起動していると考えることができます。
それを可能にするのが、ファイルの S_ISUID
モード[9]です。
S_ISUID
モード (04000
) が設定された実行ファイルは、 execve
で実行されたとき、プロセスの effective user ID を ファイルの所有者に変更します[10]。
すなわち、実行ファイルの所有者を root
にした上で set-user-ID を設定することで、プログラムを root
ユーザとして起動することができます。
set-user-ID bit をつけて seteuid を実行する
それでは、 set-uesr-ID を使って seteuid.c
が期待通り動作できるようにしましょう。
gcc seteuid.c
sudo chown root ./a.out
sudo chmod u+s ./a.out
./a.out whoami
出力
root
また、 all-caps.c
も root
権限で実行してみます。
gcc all-caps.c -lcap
sudo chown root ./a.out
sudo chmod u+s ./a.out
./a.out
CAP_AUDIT_CONTROL 30 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_AUDIT_READ 37 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_AUDIT_WRITE 29 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_BLOCK_SUSPEND 36 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_CHOWN 0 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_DAC_OVERRIDE 1 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_DAC_READ_SEARCH 2 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_FOWNER 3 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_FSETID 4 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_IPC_LOCK 14 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_IPC_OWNER 15 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_KILL 5 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_LEASE 28 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_LINUX_IMMUTABLE 9 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_MAC_ADMIN 33 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_MAC_OVERRIDE 32 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_MKNOD 27 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_NET_ADMIN 12 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_NET_BIND_SERVICE 10 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_NET_BROADCAST 11 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_NET_RAW 13 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SETGID 6 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SETFCAP 31 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SETPCAP 8 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SETUID 7 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_ADMIN 21 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_BOOT 22 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_CHROOT 18 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_MODULE 16 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_NICE 23 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_PACCT 20 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_PTRACE 19 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_RAWIO 17 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_RESOURCE 24 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_TIME 25 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYS_TTY_CONFIG 26 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_SYSLOG 34 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
CAP_WAKE_ALARM 35 flags: EFFECTIVE YES PERMITTED YES INHERITABLE NO
先ほど root ユーザが直接起動した時と同様、全てのケイパビリティが有効になっていることがわかります。
/usr/bin/sudo
のモードを確認する
set-user-ID を使えば root
ユーザとしてプログラムを実行可能なことがわかりました。
実際に sudo
コマンドもこれを利用していることを確認してみます。
ls -l $(which sudo)
-rwsr-xr-x 1 root root 166056 Apr 4 2023 /usr/bin/sudo
^ この s が set-user-ID bit
たしかに、所有者の executable bit に当たる部分が s
[11]、つまり set-user-ID が有効であることが確認できました。
したがって、一般ユーザが sudo
を実行すると、 sudo
プロセス自身が root ユーザとして起動され、その特権を使って他ユーザ (あるいは root
自身) への切り替えを行なっていると言えます。
また、筆者はソースを読んでいませんが、実際に切り替えを行う前に sudoers
の確認やパスワード認証を行うことで、権限のチェックをしているものと思われます。
結論
sudo
は、 UID を root
等別のユーザに切り替えるために setuid
系のシステムコールを使っています。
しかし、 setuid
系のシステムコールを呼び出すためには、プロせスに CAP_SETUID
というケイパビリティ(権限)が必要です。
通常、プログラムのケイパビリティは setcap(8)
等で付与されますが、 sudo
実行ファイルには付与されていません。
その代わりに、 sudo
実行ファイルの所有者を root
にし、加えて set-user-ID ビットを設定することで、 sudo
プロセスは常に root
ユーザとして起動されます。
root
ユーザのプロセスは CAP_SETUID
を含めた全ての特権を持つため、 setuid
系のシステムコールを使ってユーザを切り替えることができます。
まとめ
この記事では、 sudo
コマンドが我々の思う sudo として機能する理由を調べてみました。
調査に当たってはできるだけ Linux マニュアル等の信頼できるソースと実際の動作を根拠にしましたが、文章の読み違い・見落としがあるかもしれません。
もし内容に誤りがあれば、または追加の情報や文脈があれば、コメントに投稿いただけると幸いです。
以上です。
読んで下さったどなたかの参考になれば嬉しいです。
-
https://github.com/sudo-project/sudo/blob/78699a8f7abc6682fa8de9100671e756ed1b5b11/src/exec.c#L201-L220 ↩︎
-
私の環境では
CAP_LAST_CAP
は 37 でした。これが異なる場合、マニュアルからcap_name
を書き換えてください。 ↩︎ -
Inheritable
セットだけは無効になっています。 capabilities マニュアルの Thread capability sets 節によると、Inheritable
セットはケイパビリティをexecve
を超えて継承するか否かを示します。デフォルトでオフである理由は詳しく調べていませんが、 UID を切り替えてからexecve
する場合に権限が継承することを避けるためなどが考えられるのではないでしょうか。 ↩︎ -
https://www.gnu.org/software/libc/manual/html_node/How-Change-Persona.html ↩︎
-
ちなみに、通常の実行可能 bit (
x
) が無効なファイルに set-user-ID が設定されると、大文字のS
になるようです。 ↩︎
Discussion