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); }