下記のエントリの続きです
setuid したバイナリが /dev/full, /dev/null を open する glibc 実装が分からなかったのが、先のエントリの宿題でした
あれこれ調べてみて csu/check_fds.c だと分かりました
libdw をリンクした strace だと正しいシンボルがとれた。
先のエントリでは Ubuntu Jammy で入る strace を用いて調べていました。Ubuntu Jammy の strace は libunwind をリンクしていますが、関数を正しく解決できてなかったようです。
openat(AT_FDCWD, "/dev/full", O_WRONLY|O_NOFOLLOW) = 0 > /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2(_dl_catch_error+0x9748) [0x26b38] > /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2(_dl_catch_error+0x903e) [0x2642e] > /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2(_dl_catch_error+0x24a5) [0x1f895] > /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2(_dl_catch_error+0x41c8) [0x215b8] > /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2(_dl_catch_error+0x2ec8) [0x202b8]
試しに libdw をリンクした strace をビルドして調べたら解決できました
openat(AT_FDCWD, "/dev/full", O_WRONLY|O_NOFOLLOW) = 0 > /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2(__GI___open_nocancel+0x38) [0x26b38] > /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2(__libc_check_standard_fds+0x7d) [0x2642d] > /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2(_dl_sysdep_start+0x434) [0x1f894] > /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2(_dl_start+0x567) [0x215b7] > /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2(_start+0x7) [0x202b7]
ちょうど「libunwind と libdw は何が違うのか?」というやりとりを TenForward id:defiant さんとしていて、先のエントリの strace 調査をやり直した経緯があります
もやもやっとしますねw https://t.co/fDmZWBluaI で書いていた /dev/full を open するコードを、libunwind と libdw とで それぞれでリンクした strace 比較してみたのですが 、libunwind は間違いで libdw が正しいソースを出してくれました。 pic.twitter.com/8Wqq2kdQ9N
— hiroya ito (@hiboma) 2023年6月8日
csu/check_fds.c __libc_check_standard_fds
strace の結果、 csu/check_fds.c の __libc_check_standard_fds を追って行けばいいことが分かりました 1
それほど長くないコードなのでファイル全部張ります。 コメントもしっかりついていて わかりやすいですね
/* If this is a SUID program we make sure that FDs 0, 1, and 2 are allocated. If necessary we are doing it ourself. If it is not possible we stop the program. */ if (__builtin_expect (__libc_enable_secure, 0)) __libc_check_standard_fds (); /* Copyright (C) 2000-2022 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, see <https://www.gnu.org/licenses/>. */ #include <errno.h> #include <fcntl.h> #include <paths.h> #include <unistd.h> #include <sys/stat.h> #include <sys/sysmacros.h> /* Try to get a machine dependent instruction which will make the program crash. This is used in case everything else fails. */ #include <abort-instr.h> #ifndef ABORT_INSTRUCTION /* No such instruction is available. */ # define ABORT_INSTRUCTION #endif #include <device-nrs.h> #include <not-cancel.h> /* Should other OSes (e.g., Hurd) have different versions which can be written in a better way? */ static void check_one_fd (int fd, int mode) { if (__builtin_expect (__fcntl64_nocancel (fd, F_GETFD), 0) == -1 && errno == EBADF) { const char *name; dev_t dev; /* For writable descriptors we use /dev/full. */ if ((mode & O_ACCMODE) == O_WRONLY) { name = _PATH_DEV "full"; dev = __gnu_dev_makedev (DEV_FULL_MAJOR, DEV_FULL_MINOR); } else { name = _PATH_DEVNULL; dev = __gnu_dev_makedev (DEV_NULL_MAJOR, DEV_NULL_MINOR); } /* Something is wrong with this descriptor, it's probably not opened. Open /dev/null so that the SUID program we are about to start does not accidentally use this descriptor. */ int nullfd = __open_nocancel (name, mode, 0); /* We are very paranoid here. With all means we try to ensure that we are actually opening the /dev/null device and nothing else. Note that the following code assumes that STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO are the three lowest file decsriptor numbers, in this order. */ struct __stat64_t64 st; if (__glibc_unlikely (nullfd != fd) || __glibc_likely (__fstat64_time64 (fd, &st) != 0) || __glibc_unlikely (S_ISCHR (st.st_mode) == 0) || st.st_rdev != dev) /* We cannot even give an error message here since it would run into the same problems. */ while (1) /* Try for ever and ever. */ ABORT_INSTRUCTION; } } void __libc_check_standard_fds (void) { /* Check all three standard file descriptors. The O_NOFOLLOW flag is really paranoid but some people actually are. If /dev/null should happen to be a symlink to somewhere else and not the device commonly known as "/dev/null" we bail out. */ check_one_fd (STDIN_FILENO, O_WRONLY | O_NOFOLLOW); check_one_fd (STDOUT_FILENO, O_RDONLY | O_NOFOLLOW); check_one_fd (STDERR_FILENO, O_RDONLY | O_NOFOLLOW); }
試しに gdb で __libc_check_standard_fds に break point 張って実行すると ... 止まった!
(gdb) bt #0 __libc_check_standard_fds () at ./csu/check_fds.c:88 #1 0x00007f0f4436c895 in _dl_sysdep_start (start_argptr=start_argptr@entry=0x7fff0a9b1830, dl_main=dl_main@entry=0x7f0f4436e900 <dl_main>) at ../elf/dl-sysdep.c:254 #2 0x00007f0f4436e5b8 in _dl_start_final (arg=0x7fff0a9b1830) at ./elf/rtld.c:507 #3 _dl_start (arg=0x7fff0a9b1830) at ./elf/rtld.c:596 #4 0x00007f0f4436d2b8 in _start () from /lib64/ld-linux-x86-64.so.2 #5 0x0000000000000001 in ?? () #6 0x00007fff0a9b276b in ?? () #7 0x0000000000000000 in ?? ()
ディレクトリ名の csu とは?
ディレクトリ名の csu って何だろうと? 調べたら stack overflow で回答を見つけました
I found a reference to "C start up" in the libc mailing list. It's far from definitive, but I imagine it's probably correct given what the function does.
ここの ML の回答を指しているみたいですね。多分あってそうな??
ABORT_INSTRUCTION は?
ABORT_INSTRUCTION を呼ぶコードが混じっています。これは以前 会社のテックブログにまとめたのを思い出しました
感謝
このエントリを書いてる途中、id:NeoCat さんからコメントいただいておりました! ありがとうございます
コメントは先のエントリにいただいております。
- NeoCat さんからのコメントで確かであることを再確認↩