Linux: 0, 1, 2 のファイルディスクリプタを閉じて setuid したバイナリ実行の挙動を調べる - その2 ソースを調べる

下記のエントリの続きです

hiboma.hatenadiary.jp

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 調査をやり直した経緯があります

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 で回答を見つけました

stackoverflow.com

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 の回答を指しているみたいですね。多分あってそうな??

sourceware.org

ABORT_INSTRUCTION は?

ABORT_INSTRUCTION を呼ぶコードが混じっています。これは以前 会社のテックブログにまとめたのを思い出しました

tech.pepabo.com

感謝

このエントリを書いてる途中、id:NeoCat さんからコメントいただいておりました! ありがとうございます

コメントは先のエントリにいただいております。

hiboma.hatenadiary.jp


  1. NeoCat さんからのコメントで確かであることを再確認