root以外のeffective uidの放棄をしてみた

effective uid = root の完全放棄は setuid(newUid) とすれば real uid = effective uid = saved uid = newUid となるので二度と effective uid = root にすることはできなくなる。ところで root ではないが重要なアカウント(仮に 501)があったとして、その権限の放棄はどうすればよいか?と疑問に思ったので試してみた。

#include <sys/types.h>
#include <unistd.h>
#include <iostream>

using namespace std;

void showCurrentUid()
{
    cout << "# real user id = " << getuid() << endl;
    cout << "# effective user id = " << geteuid() << endl;
}

void setUid(uid_t uid)
{
    int rc = setuid(uid);
    cout << "setuid(" << uid << ") = " << rc << endl;
}

int main(int argc, char** argv)
{
    uid_t oldEffectiveUid = geteuid();

    showCurrentUid();
    setUid(getuid()); // real uid に権限を変更
    showCurrentUid();
    setUid(oldEffectiveUid); // 元の effective uid に復帰
    showCurrentUid();

    return 0;
}

こんなプログラムを用意して、chown 0 a.out; chmod u+s a.out; でアカウント 500 で起動すると、

# real user id = 500
# effective user id = 0    ← setuid ビットが立っているため 500 ではなく 0 で起動
setuid(500) = 0            ← effective = 500 に変更 (同時に real = saved = 500 となる)
# real user id = 500
# effective user id = 500
setuid(0) = -1             ← effective = 0 に戻そうとしても失敗(権限が放棄できた)
# real user id = 500
# effective user id = 500

ところが、chown 501 a.out; chmod u+s a.out; でアカウント 500 で起動すると、

# real user id = 500
# effective user id = 501  ← setuid ビットが立っているため 500 ではなく 501 で起動 (見えないが saved = 501)
setuid(500) = 0            ← effective = 501 に変更 (saved は書き換わらない)
# real user id = 500
# effective user id = 500
setuid(501) = 0            ← effective = 501 に戻せてしまう(saved = 501 のため)
# real user id = 500
# effective user id = 501

saved uid も書き換わらないと元の権限に戻ることができてしまうのだな。setuid() では effective uid = root の時のみ saved uid が書き換わるのでどうしようもない。そこで setreuid() の man を読むと、

If the real user ID is set or the effective user ID is set to a value not equal to the previous real user ID, the saved user ID will be set to the new effective user ID. 

こちらは

  1. real uid をセットする
  2. real uid と異なる effective uid をセットする

ことで saved uid も書き換わるみたい。で、実際にプログラムを書き換えて、

void setRealEffectiveUid(uid_t realUid, uid_t effectiveUid)
{
    int rc = setreuid(realUid, effectiveUid);
    cout << "setreuid(" << realUid << ", " << effectiveUid << ") = " << rc << endl;
}

int main(int argc, char** argv)
{
    uid_t oldEffectiveUid = geteuid();

    showCurrentUid();
    setRealEffectiveUid(getuid(), getuid());
    showCurrentUid();
    setUid(oldEffectiveUid);
    showCurrentUid();

    return 0;
}

これで、chown 501 a.out; chmod u+s a.out; でアカウント 500 で起動すると、

# real user id = 500
# effective user id = 501  ← setuid ビットが立っているため 500 ではなく 501 で起動 (見えないが saved = 501)
setreuid(500, 500) = 0     ← effective = 500, real = 500 に変更 (※real を同じ値で上書きしたので saved = 500 になる)
# real user id = 500
# effective user id = 500
setuid(501) = -1           ← effective = 501 に戻そうとしても失敗(権限が放棄できた)
# real user id = 500
# effective user id = 500

※のところがちょっと面白い。effective uid だけ 501→500 に変更するため setreuid(-1, 500) としても、real = 500 のため 2 の条件に満たずに saved uid は書き換わらない。元々 real = 500 だけど、setreuid(500, -1) とすると、1 の条件により saved uid が書き換わった。