setuid
setuidシステムコールは、rootのCAP_SETUIDを有するなら、uid=euid=fsuid=suid=uidとし、有していない通常タスクは、euid=fsuid=uidとします。ただし通常タスクのuidは、タスクのuid/suidでなければなりません。
ログインプロセスのようにroot権限で動作し、その中で他の権限へ移行するような実装を想定したものですが、rootから一般権限へ移行した場合、uid/euid/fsuid/suidの全てのクレデンシャルは同一となり、従って一般権限プロセスから他の権限へ移行するのできません。
これはセキュリティ的に理にかなっていますが、そうなると、!nsown_capable(CAP_SETUID)の処理が必要であるかと言うことです。
root権限の必要なコマンドは、その処理全てでroot権限が必要という事でなく、その部分的な処理で、スタックオーバフローとか、他の外部コマンドを実行するとか、権限が奪われるセキュリティ上、脆弱となります。
で、そのような処理では、脆弱的な箇所では一般権限に移行し、後、root権限へ戻る。と言った実装を可能とするものです。(たぶん)
上記実装を実現するため、uid/euid/fsuid/suidの個別単位で設定できるsetuidシステムコールの設定条件に準じた別のシステムコールが実装されています。
ログインプロセスのようにroot権限で動作し、その中で他の権限へ移行するような実装を想定したものですが、rootから一般権限へ移行した場合、uid/euid/fsuid/suidの全てのクレデンシャルは同一となり、従って一般権限プロセスから他の権限へ移行するのできません。
これはセキュリティ的に理にかなっていますが、そうなると、!nsown_capable(CAP_SETUID)の処理が必要であるかと言うことです。
root権限の必要なコマンドは、その処理全てでroot権限が必要という事でなく、その部分的な処理で、スタックオーバフローとか、他の外部コマンドを実行するとか、権限が奪われるセキュリティ上、脆弱となります。
で、そのような処理では、脆弱的な箇所では一般権限に移行し、後、root権限へ戻る。と言った実装を可能とするものです。(たぶん)
上記実装を実現するため、uid/euid/fsuid/suidの個別単位で設定できるsetuidシステムコールの設定条件に準じた別のシステムコールが実装されています。
SYSCALL_DEFINE1(setuid, uid_t, uid)
{
const struct cred *old;
struct cred *new;
int retval;
new = prepare_creds();
if (!new)
return -ENOMEM;
old = current_cred();
retval = -EPERM;
if (nsown_capable(CAP_SETUID)) {
new->suid = new->uid = uid;
if (uid != old->uid) {
retval = set_user(new);
if (retval < 0)
goto error;
}
} else if (uid != old->uid && uid != new->suid) {
goto error;
}
new->fsuid = new->euid = uid;
retval = security_task_fix_setuid(new, old, LSM_SETID_ID);
if (retval < 0)
goto error;
return commit_creds(new);
error:
abort_creds(new);
return retval;
}
補足
ファイル属性のsuidは、一般権限からroot権限へ移行しますが、システムコールから実現する事は不可能です。本実装は、カーネルマターで、execシステムからコールされるprepare_binprm()で、inodeのi_uidをプロセスのeuidに直接設定し、uid/fsuid/suidは、security_bprm_set_creds()でカーネルのセキュリティに応じて設定されます。未セキュリティカーネルでは、uid/fsuid/suid=euidとします。
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;
bprm->cred->euid = current_euid();
bprm->cred->egid = current_egid();
if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)) {
if (mode & S_ISUID) {
bprm->per_clear |= PER_CLEAR_ON_SETID;
bprm->cred->euid = inode->i_uid;
}
if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
bprm->per_clear |= PER_CLEAR_ON_SETID;
bprm->cred->egid = inode->i_gid;
}
}
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);
}



