mountのMS_NOSUID
MS_NOSUIDオプションでmountすると、setuid/setgidが無効になります。MS_NOSUIDオプションでmountシステムコールを呼び出すと、マウントフラグとして MNT_NOSUIDでファイルシステムをマウントします。
long do_mount(char *dev_name, char *dir_name, char *type_page, unsigned long flags, void *data_page) { if (flags & MS_NOSUID) mnt_flags |= MNT_NOSUID; if (flags & MS_NODEV) mnt_flags |= MNT_NODEV; if (flags & MS_NOEXEC) mnt_flags |= MNT_NOEXEC; if (flags & MS_NOATIME) mnt_flags |= MNT_NOATIME; if (flags & MS_NODIRATIME) mnt_flags |= MNT_NODIRATIME; if (flags & MS_STRICTATIME) mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME); if (flags & MS_RDONLY) mnt_flags |= MNT_READONLY; flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_BORN | MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT | MS_STRICTATIME); if (flags & MS_REMOUNT) retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags, data_page); else if (flags & MS_BIND) retval = do_loopback(&path, dev_name, flags & MS_REC); else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE)) retval = do_change_type(&path, flags); else if (flags & MS_MOVE) retval = do_move_mount(&path, dev_name); else retval = do_new_mount(&path, type_page, flags, mnt_flags, dev_name, data_page); dput_out: path_put(&path); return retval; }ファイルを実行すると、do_exec()からprepare_binprm()がコールされ、bprm->cred->euid = current_euid()/bprm->cred->egid = current_egid()で親プロセスで初期化し、実行しようとしているファイルのファイルシステムのマウントフラグが、MNT_NOSUIDでないなら、bprm->cred->euid = inode->i_uid/bprm->cred->egid = inode->i_gidで更新して、kernel_read()をコールすることになります。
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 ((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); }proc_fill_super()は、procfsのスーパブロック取得のコールバック関数です。この時s->s_flags |= MS_NODIRATIME | MS_NOSUID | MS_NOEXECとしています。procfsではスーパブロックオプションの位置づけで設定されています。従って再マウントする事で、setuid/setgidを有効にできないようになっています。
int proc_fill_super(struct super_block *s) { struct inode * root_inode; s->s_flags |= MS_NODIRATIME | MS_NOSUID | MS_NOEXEC; s->s_blocksize = 1024; s->s_blocksize_bits = 10; s->s_magic = PROC_SUPER_MAGIC; s->s_op = &proc_sops; s->s_time_gran = 1; pde_get(&proc_root); root_inode = proc_get_inode(s, &proc_root); if (!root_inode) goto out_no_root; root_inode->i_uid = 0; root_inode->i_gid = 0; s->s_root = d_alloc_root(root_inode); if (!s->s_root) goto out_no_root; return 0; out_no_root: printk("proc_read_super: get root inode failed\n"); iput(root_inode); pde_put(&proc_root); return -ENOMEM; }