読み取り権限がなく実行権限だけのファイルが実行できる仕組みをstraceで追ってみた

"読み取り権限がなく実行権限だけのファイルが実行できるのはなぜ? - カーネルのソースを読む - - 猫型の蓄音機は 1 分間に 45 回にゃあと鳴く"
こちらの記事が大変素晴らしかったので、実際の実行時システムコール呼び出しを追ってみました。
平たく言うと、strace・システムコールの入門です。

バイナリ実行するのに読み取り権限は必要ないことを実験で確かめる

C言語でhello worldを書いてみます。
hello.c
#include <stdio.h>

int main()
{
  printf("hello world\n");
  return 0;
}
コンパイルして、パーミッションを100にします。
$ gcc hello.c -o hello
$ chmod 0100 hello
$ ls -l
合計 12
---x------ 1 vagrant vagrant 6417  3月 30 01:43 2014 hello*
-rw-r--r-- 1 vagrant vagrant   74  3月 30 01:42 2014 hello.c
helloバイナリに読み取り権限がないことを確認しました。
では実行します。
$ ./hello
hello world
おお!
実行できました!!
ちょっと感動。
今まで実行ファイルはなんとなく755にしてたのですが、今後はもうちょっとパーミッションを厳しくしてもいいのかも。

スクリプト実行するのに読み取り権限が必要であることを実験で確かめる

お次はPerlスクリプトでhello worldを作ってみます。
hello.pl
#!/usr/bin/perl

print "hello world\n";
パーミッションを100にして実行してみます。
$ chmod 0100 hello.pl
$ ls -l hello.pl
---x------ 1 vagrant vagrant 40  3月 30 01:50 2014 hello.pl*
$ ./hello.pl
Can't open perl script "./hello.pl": 許可がありません
実行することが、できませんでしたぁぁ!
書き込み権限を付与しても
$ chmod 0300 hello.pl
$ ./hello.pl
Can't open perl script "./hello.pl": 許可がありません
実行することが、できませんでしたぁぁ!

読み取り+実行権限を付与したら
$ chmod 0500 hello.pl
$ ./hello.pl
hello world
実行できました!

straceでバイナリファイル実行のシステムコールを追ってみる

さて、実際のシステムコール呼び出しを見てみましょう。 straceコマンドを使います。
$ strace ./hello
execve("./hello", ["./hello"], [/* 28 vars */]) = 0
brk(0)                                  = 0x13d7000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f58523c1000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=31577, ...}) = 0
mmap(NULL, 31577, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f58523b9000
close(3)                                = 0
open("/lib64/libc.so.6", O_RDONLY)      = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000\356\1\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1921216, ...}) = 0
mmap(NULL, 3750152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f5851e0f000
mprotect(0x7f5851f9a000, 2093056, PROT_NONE) = 0
mmap(0x7f5852199000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x18a000) = 0x7f5852199000
mmap(0x7f585219e000, 18696, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f585219e000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f58523b8000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f58523b7000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f58523b6000
arch_prctl(ARCH_SET_FS, 0x7f58523b7700) = 0
mprotect(0x7f5852199000, 16384, PROT_READ) = 0
mprotect(0x7f58523c2000, 4096, PROT_READ) = 0
munmap(0x7f58523b9000, 31577)           = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f58523c0000
write(1, "hello world\n", 12hello world
)           = 12
exit_group(0)                           = ?
冒頭でexexveシステムコールが./helloファイルを引数にして呼ばれていることがわかります。
一方、./helloバイナリファイルに対してopenやreadなどの呼び出しはされていません。
バイナリファイルを実行するのに読み取り権限が必要ないことがこれで納得がいきます。

straceでスクリプト実行のシステムコールを追ってみる

次はstraceでhello.plを実行します。
わざと失敗させるために、パーミッションを100にしてからstraceします。
$ chmod 0100 hello.pl
$ strace ./hello.pl
execve("./hello.pl", ["./hello.pl"], [/* 28 vars */]) = 0
brk(0)                                  = 0x17e3000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe174cff000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/usr/lib64/perl5/CORE/tls/x86_64/libperl.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat("/usr/lib64/perl5/CORE/tls/x86_64", 0x7fff8744e2c0) = -1 ENOENT (No such file or directory)
open("/usr/lib64/perl5/CORE/tls/libperl.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat("/usr/lib64/perl5/CORE/tls", 0x7fff8744e2c0) = -1 ENOENT (No such file or directory)
open("/usr/lib64/perl5/CORE/x86_64/libperl.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat("/usr/lib64/perl5/CORE/x86_64", 0x7fff8744e2c0) = -1 ENOENT (No such file or directory)
open("/usr/lib64/perl5/CORE/libperl.so", O_RDONLY) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20M\3\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1485896, ...}) = 0
mmap(NULL, 3581608, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe174776000
mprotect(0x7fe1748d8000, 2097152, PROT_NONE) = 0
mmap(0x7fe174ad8000, 36864, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x162000) = 0x7fe174ad8000
close(3)                                = 0
open("/usr/lib64/perl5/CORE/libresolv.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=31577, ...}) = 0
mmap(NULL, 31577, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe174cf7000
close(3)                                = 0
open("/lib64/libresolv.so.2", O_RDONLY) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\00009\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=110960, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe174cf6000
mmap(NULL, 2202248, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe17455c000
mprotect(0x7fe174572000, 2097152, PROT_NONE) = 0
mmap(0x7fe174772000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16000) = 0x7fe174772000
mmap(0x7fe174774000, 6792, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fe174774000
close(3)                                = 0
open("/usr/lib64/perl5/CORE/libnsl.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib64/libnsl.so.1", O_RDONLY)    = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p@\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=113432, ...}) = 0
mmap(NULL, 2198192, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe174343000
mprotect(0x7fe174359000, 2093056, PROT_NONE) = 0
mmap(0x7fe174558000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15000) = 0x7fe174558000
mmap(0x7fe17455a000, 6832, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fe17455a000
close(3)                                = 0
open("/usr/lib64/perl5/CORE/libdl.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib64/libdl.so.2", O_RDONLY)     = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340\r\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=19536, ...}) = 0
mmap(NULL, 2109696, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe17413f000
mprotect(0x7fe174141000, 2097152, PROT_NONE) = 0
mmap(0x7fe174341000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7fe174341000
close(3)                                = 0
open("/usr/lib64/perl5/CORE/libm.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib64/libm.so.6", O_RDONLY)      = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p>\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=596264, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe174cf5000
mmap(NULL, 2633912, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe173ebb000
mprotect(0x7fe173f3e000, 2093056, PROT_NONE) = 0
mmap(0x7fe17413d000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x82000) = 0x7fe17413d000
close(3)                                = 0
open("/usr/lib64/perl5/CORE/libcrypt.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib64/libcrypt.so.1", O_RDONLY)  = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\f\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=40400, ...}) = 0
mmap(NULL, 2318816, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe173c84000
mprotect(0x7fe173c8b000, 2097152, PROT_NONE) = 0
mmap(0x7fe173e8b000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x7000) = 0x7fe173e8b000
mmap(0x7fe173e8d000, 184800, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fe173e8d000
close(3)                                = 0
open("/usr/lib64/perl5/CORE/libutil.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib64/libutil.so.1", O_RDONLY)   = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\16\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=14584, ...}) = 0
mmap(NULL, 2105600, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe173a81000
mprotect(0x7fe173a83000, 2093056, PROT_NONE) = 0
mmap(0x7fe173c82000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1000) = 0x7fe173c82000
close(3)                                = 0
open("/usr/lib64/perl5/CORE/libpthread.so.0", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib64/libpthread.so.0", O_RDONLY) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340]\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=142640, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe174cf4000
mmap(NULL, 2212848, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe173864000
mprotect(0x7fe17387b000, 2097152, PROT_NONE) = 0
mmap(0x7fe173a7b000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x17000) = 0x7fe173a7b000
mmap(0x7fe173a7d000, 13296, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fe173a7d000
close(3)                                = 0
open("/usr/lib64/perl5/CORE/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib64/libc.so.6", O_RDONLY)      = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000\356\1\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1921216, ...}) = 0
mmap(NULL, 3750152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe1734d0000
mprotect(0x7fe17365b000, 2093056, PROT_NONE) = 0
mmap(0x7fe17385a000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x18a000) = 0x7fe17385a000
mmap(0x7fe17385f000, 18696, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fe17385f000
close(3)                                = 0
open("/usr/lib64/perl5/CORE/libfreebl3.so", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib64/libfreebl3.so", O_RDONLY)  = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0@<\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=469528, ...}) = 0
mmap(NULL, 2582368, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe173259000
mprotect(0x7fe1732ca000, 2093056, PROT_NONE) = 0
mmap(0x7fe1734c9000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x70000) = 0x7fe1734c9000
mmap(0x7fe1734cc000, 14176, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fe1734cc000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe174cf3000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe174cf2000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe174cf1000
arch_prctl(ARCH_SET_FS, 0x7fe174cf2700) = 0
mprotect(0x7fe1734c9000, 8192, PROT_READ) = 0
mprotect(0x7fe17385a000, 16384, PROT_READ) = 0
mprotect(0x7fe173a7b000, 4096, PROT_READ) = 0
mprotect(0x7fe173c82000, 4096, PROT_READ) = 0
mprotect(0x7fe173e8b000, 4096, PROT_READ) = 0
mprotect(0x7fe17413d000, 4096, PROT_READ) = 0
mprotect(0x7fe174341000, 4096, PROT_READ) = 0
mprotect(0x7fe174558000, 4096, PROT_READ) = 0
munmap(0x7fe174cf7000, 31577)           = 0
set_tid_address(0x7fe174cf29d0)         = 19648
set_robust_list(0x7fe174cf29e0, 0x18)   = 0
futex(0x7fff8744ebbc, FUTEX_WAKE_PRIVATE, 1) = 0
futex(0x7fff8744ebbc, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 1, NULL, 7fe174cf2700) = -1 EAGAIN (Resource temporarily unavailable)
rt_sigaction(SIGRTMIN, {0x7fe173869c60, [], SA_RESTORER|SA_SIGINFO, 0x7fe173873710}, NULL, 8) = 0
rt_sigaction(SIGRT_1, {0x7fe173869cf0, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x7fe173873710}, NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
getrlimit(RLIMIT_STACK, {rlim_cur=10240*1024, rlim_max=RLIM_INFINITY}) = 0
rt_sigaction(SIGFPE, {SIG_IGN, [FPE], SA_RESTORER|SA_RESTART, 0x7fe1735029a0}, {SIG_DFL, [], 0}, 8) = 0
brk(0)                                  = 0x17e3000
brk(0x1804000)                          = 0x1804000
getuid()                                = 501
geteuid()                               = 501
getgid()                                = 501
getegid()                               = 501
open("/usr/lib/locale/locale-archive", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=99154480, ...}) = 0
mmap(NULL, 99154480, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe16d3c9000
close(3)                                = 0
open("/dev/urandom", O_RDONLY)          = 3
read(3, "\355\216m\263", 4)             = 4
close(3)                                = 0
readlink("/proc/self/exe", "/usr/bin/perl", 4095) = 13
ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
lseek(0, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
lseek(1, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
lseek(2, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
open("./hello.pl", O_RDONLY)            = -1 EACCES (Permission denied)
write(2, "Can't open perl script \"./hello."..., 55Can't open perl script "./hello.pl": Permission denied
) = 55
rt_sigaction(SIG_0, NULL, {0x9f0ed807, [], SA_RESTORER|SA_RESTART|SA_INTERRUPT|SA_NODEFER|SA_NOCLDWAIT|0xaeae58, (nil)}, 8) = -1 EINVAL (Invalid argument)
rt_sigaction(SIGHUP, NULL, {SIG_DFL, [], 0}, 8) = 0
冒頭でexecveが呼ばれているのはさっきのバイナリ実行と同じですが、最後の方でhello.plに対してopenシステムコールがO_RDONLYモードで呼ばれており、これが "Permission denied" で失敗しています。
なので、hello.plを実行するには読み取り権限が必要なのだとわかります。
考えてみれば、Perlインタープリタがhello.plを解釈するためにファイルオープンするのだがら、当然といえば当然ですね。

まとめ

straceたのしい!
カテゴリ: