プロセス権限

# 概要
pingの実装を通してプロセスの権限を勉強した備忘録。

# 疑問
pingコマンドはset-user-IDされているので一般ユーザーで実行した場合でも実効IDがrootで実行されるはずだと思っていた。ところがpsコマンドで確認したならば以下の様な結果になった。そう、実効IDが一般ユーザーで実行されていた。なぜだろう・・？

## /bin/ping
~~~
[root@localhost iputils-s20071127]# ll /bin/ping
-rwsr-xr-x. 1 root root 40760  9月 26 14:35 2013 /bin/ping
~~~
set-user-IDがセットされている。これを実行すると実効IDがrootでpingが実行されるはずだと思っていた・・

~~~
[vagrant@localhost ~]$ id
uid=500(vagrant) gid=500(vagrant) 所属グループ=500(vagrant)

[vagrant@localhost ~]$ /bin/ping 192.168.33.20
PING 192.168.33.20 (192.168.33.20) 56(84) bytes of data.
64 bytes from 192.168.33.20: icmp_seq=1 ttl=64 time=0.021 ms
64 bytes from 192.168.33.20: icmp_seq=2 ttl=64 time=0.034 ms
~~~
ping実行したまま、以下を実行

~~~
[vagrant@localhost net]$ ps -eo uid,euid,fuid,suid,command  | grep [p]ing
  500   500   500   500 ping 192.168.33.10
~~~
各IDを確認するとすべてvagrantユーザーの値に。pingはset-user-IDされたプログラムなのでrootの値になっているはず、しかしvagrantユーザーの値。なぜだろう？

## 試した環境
* Vagrant on Mac
* pingのソースコードが入っているsrpmを取得。

~~~sh
[root@localhost iputils-s20071127]# cat /etc/redhat-release
CentOS release 6.5 (Final)
[root@localhost iputils-s20071127]# which ping
/bin/ping
[root@localhost iputils-s20071127]# rpm -qf /bin/ping
iputils-20071127-17.el6_4.2.x86_64
[root@localhost iputils-s20071127]#
~~~

#　処理みてみた
pingのソースコード

```ping.c

int main(int argc, char **argv)
{
  .
  .
  . 
  icmp_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

  socket_errno = errno;

  uid = getuid();
  if (setuid(uid)) {
    perror("ping: setuid");
    exit(-1);
  }
  .
  .
  .
```

ソースコードを見ればわかりました。socketシステムコールを実行した後にgetuidで自プロセスの実ユーザーIDを取得して、setuidで自プロセスの実効ユーザーIDを変更しています。よってpsコマンドではsetuidされた後の値(vagrantユーザー)が見えていたわけです。

## CentOS7のPing
~~~
[root@localhost iputils-s20121221]# cat /etc/redhat-release
CentOS Linux release 7.1.1503 (Core)
[root@localhost iputils-s20121221]# uname -r
3.10.0-229.el7.x86_64
[root@localhost iputils-s20121221]# which ping
/bin/ping
[root@localhost iputils-s20121221]# ll /bin/ping
-rwxr-xr-x. 1 root root 44896  6月 10  2014 /bin/ping
~~~

他にもcentos7の環境で見てみました。するとcentos7のping実行ファイルにはではケーパビリティのチェックをしていました。

~~~ping.c
        limit_capabilities();

#ifdef USE_IDN
        setlocale(LC_ALL, "");
#endif

        enable_capability_raw();

        icmp_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
        socket_errno = errno;

        disable_capability_raw();
~~~

socketシステムコールを実行するまえにenable_capability_raw()でCAP_NET_RAW権限だけを与えて、socketシステムコール後にdisable_capability_raw()で権限をクリアしていました。よってset-user-IDしていない実行ファイルでもsocketシステムコールを実行できているようです。しかしなぜcent6とcent7でpingの作りが違うのでしょうか？？んー、なぜでしょうか。両者を見るとcent７のほうが権限を最小化できているのでセキュリティ的に望ましいといえます。





# 関係ありそうなカーネルの処理をみてみた
カーネル:linux-2.6.32

## set-user-IDを実行ファイルから取得している箇所
sys_execve() → do_execve からの先で呼ばれている。

~~~fs/exec.c
/*
 * Fill the binprm structure from the inode.
 * Check permissions, then read the first 128 (BINPRM_BUF_SIZE) bytes
 *
 * This may be called multiple times for binary chains (scripts for example).
 */
int prepare_binprm(struct linux_binprm *bprm)
{
        umode_t mode;
        struct inode * inode = bprm->file->f_path.dentry->d_inode;
        int retval;

        mode = inode->i_mode;
        if (bprm->file->f_op == NULL)
                return -EACCES;

        /* clear any previous set[ug]id data from a previous binary */
        bprm->cred->euid = current_euid();
        bprm->cred->egid = current_egid();

        if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)) {
                /* Set-uid? */
                if (mode & S_ISUID) {
                        bprm->per_clear |= PER_CLEAR_ON_SETID;
                        bprm->cred->euid = inode->i_uid;
                }

                /* Set-gid? */
                /*
                 * If setgid is set but no group execute bit then this
                 * is a candidate for mandatory locking, not a setgid
                 * executable.
                 */
                if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
                        bprm->per_clear |= PER_CLEAR_ON_SETID;
                        bprm->cred->egid = inode->i_gid;
                }
        }

        /* fill in binprm security blob */
        retval = security_bprm_set_creds(bprm);
        if (retval)
                return retval;
        bprm->cred_prepared = 1;

        memset(bprm->buf, 0, BINPRM_BUF_SIZE);
        return kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE);
}
~~~

linux_binprm

~~~include/linux/binfmts.h
/*
 * This structure is used to hold the arguments that are used when loading binaries.
 */
struct linux_binprm{
        char buf[BINPRM_BUF_SIZE];
#ifdef CONFIG_MMU
        struct vm_area_struct *vma;
#else
# define MAX_ARG_PAGES  32
        struct page *page[MAX_ARG_PAGES];
#endif
        struct mm_struct *mm;
        unsigned long p; /* current top of mem */
        unsigned int
                cred_prepared:1,/* true if creds already prepared (multiple
                                 * preps happen for interpreters) */
                cap_effective:1;/* true if has elevated effective capabilities,
                                 * false if not; except for init which inherits
                                 * its parent's caps anyway */
#ifdef __alpha__
        unsigned int taso:1;
#endif
        unsigned int recursion_depth;
        struct file * file;
        struct cred *cred;      /* new credentials */
        int unsafe;             /* how unsafe this exec is (mask of LSM_UNSAFE_*) */
        unsigned int per_clear; /* bits to clear in current->personality */
        int argc, envc;
        char * filename;        /* Name of binary as seen by procps */
        char * interp;          /* Name of the binary really executed. Most
                                   of the time same as filename, but could be
                                   different for binfmt_{misc,script} */
        unsigned interp_flags;
        unsigned interp_data;
        unsigned long loader, exec;
};
~~~
set-user-IDの値を取得してlinux_binprmの構造体に値を入れているようです。linux_binprmはコメントにかかれているように、execveする時の各種情報を入れる構造体みたいです。その後にsecurity_bprm_set_credsで権限のチェックをしているみたいでした(ケーパビリティも含む)。この先で通常ユーザーの実効IDをset-user-IDの値で権現の上書きをしているんでしょうか？？？security_bprm_set_credsの先はいつか読んでみたいと思います。

他にも関係ありそうな箇所を見てみました。

task_struc構造体からひも付く各ID。

~~~include/linux/cred.h
struct cred {
・
・
・
        uid_t           uid;            /* real UID of the task */
        gid_t           gid;            /* real GID of the task */
        uid_t           suid;           /* saved UID of the task */
        gid_t           sgid;           /* saved GID of the task */
        uid_t           euid;           /* effective UID of the task */
        gid_t           egid;           /* effective GID of the task */
        uid_t           fsuid;          /* UID for VFS ops */
        gid_t           fsgid;          /* GID for VFS ops */
        unsigned        securebits;     /* SUID-less security management 
・
・
・
}
~~~

どこかで使われていないかなーと思ってみてみました。do_execve()のさらに先でELF実行ファイルの場合にELFをメモリにロードする処理の途中で何か権限を挿入している処理がありました。

~~~fs/binfmt_elf.c
static int
create_elf_tables(struct linux_binprm *bprm, struct elfhdr *exec,
                unsigned long load_addr, unsigned long interp_load_addr)
{
・
・
・
/* Create the ELF interpreter info */
        elf_info = (elf_addr_t *)current->mm->saved_auxv;
        /* update AT_VECTOR_SIZE_BASE if the number of NEW_AUX_ENT() changes */
#define NEW_AUX_ENT(id, val) \
        do { \
                elf_info[ei_index++] = id; \
                elf_info[ei_index++] = val; \
        } while (0)

#ifdef ARCH_DLINFO
        /*
         * ARCH_DLINFO must come first so PPC can do its special alignment of
         * AUXV.
         * update AT_VECTOR_SIZE_ARCH if the number of NEW_AUX_ENT() in
         * ARCH_DLINFO changes
         */
        ARCH_DLINFO;
#endif
        NEW_AUX_ENT(AT_HWCAP, ELF_HWCAP);
        NEW_AUX_ENT(AT_PAGESZ, ELF_EXEC_PAGESIZE);
        NEW_AUX_ENT(AT_CLKTCK, CLOCKS_PER_SEC);
        NEW_AUX_ENT(AT_PHDR, load_addr + exec->e_phoff);
        NEW_AUX_ENT(AT_PHENT, sizeof(struct elf_phdr));
        NEW_AUX_ENT(AT_PHNUM, exec->e_phnum);
        NEW_AUX_ENT(AT_BASE, interp_load_addr);
        NEW_AUX_ENT(AT_FLAGS, 0);
        NEW_AUX_ENT(AT_ENTRY, exec->e_entry);
        NEW_AUX_ENT(AT_UID, cred->uid);
        NEW_AUX_ENT(AT_EUID, cred->euid);
        NEW_AUX_ENT(AT_GID, cred->gid);
        NEW_AUX_ENT(AT_EGID, cred->egid);
        NEW_AUX_ENT(AT_SECURE, security_bprm_secureexec(bprm));
        NEW_AUX_ENT(AT_RANDOM, (elf_addr_t)(unsigned long)u_rand_bytes);
        NEW_AUX_ENT(AT_EXECFN, bprm->exec);
・
・
・
}
~~~
ELF interpreter infoにNEW_AUX_ENTマクロを使用して各IDを挿入していました。この領域はユーザースタックの環境変数へのポインタが入っている配列の直前に置かれるようです。これは一体なんなのでしょうか？？実はプログラムを実行する時カーネルでexec処理するんですが最終的にカーネルではIPの値としてその実行ファイルの動的リンカのエントリポイントが挿入されます。動的リンカが実行ファイルの共有ライブラリをリンクして、実行ファイルのエントリポイントを設定する流れです。色々調べると動的リンカがこの領域を取得しているようでした。わざわざ各IDもここに挿入されているということはきっと何かに使われているのだと思います。何に使われているのかは調べていません。いつか調べられたらと思います。