COMMAND

    kernel (sys_set*id(uid_t...)

SYSTEMS AFFECTED

    Linux

PROBLEM

    Michal Zalewski found following.   'Physically', UID is stored  by
    kernel  in  single  word.   Due  to  this limitation, UID/GID must
    satisfy condition 0<=UID<=65535.   But there are serious  problems
    with kernel sys_setuid(uid_t) and uid_t type itself:

        - uid_t (UID/GID handling type), unsigned integer, is able  to
          handle large integers (>65535), eg. 131072.
        - sys_setuid(uid_t)  and  other  kernel  UID/GID  manipulation
          routines  (and  their  libc  aliases, like setuid), silently
          strips higher uid_t bits, then returns 'success' value (0).

    So, attacker may change /etc/passwd  UID of any account to  131072
    (binary:   10  00000000  00000000)  -  it  won't  be traced by any
    intrusion-detection programs looking  for '0' UIDs,  because uid_t
    is able to store this value,  and 131072 for sure is NOT  equal to
    0.  But when he will login using this account - he should get root
    shell  (setuid,  as  oticed  above,  silently discards two highest
    bits, so 131072 becomes 0).

    Nice  trick,  but  that's  not  all.  If  you  have  eg. securetty
    installed  to  prevent  root  logins  from  outside  (or any other
    mechanism  to  prevent  unprivledged  root  access, eg. restricted
    'su') - attacker  will be able  to fool it,  because UID retreived
    from  /etc/passwd  (uid_t)  using  standard  libc routines, is NOT
    equal to 0, so this account looks like unprivledged...

SOLUTION

    First solution - rewrite kernel UID/GID code to extend UID address
    space  using  eg.  4  bytes  instead  of  2 (whoow!) - but it will
    probably harm many programs.

    Second solution - patch your kernel to return EINVAL when uid_t is
    too big.  Here's the patch:

    --- linux/kernel/sys.c.orig     Tue Apr  8 17:47:47 1997
    +++ linux/kernel/sys.c          Fri Jun 19 16:00:28 1998
    @@ -237,6 +237,8 @@
     {
            int old_rgid = current->gid;
            int old_egid = current->egid;
    +
    +       if (rgid>0xffff || egid>0xffff) return -EINVAL;

            if (rgid != (gid_t) -1) {
                if ((old_rgid == rgid) ||
    @@ -272,6 +274,8 @@
     asmlinkage int sys_setgid(gid_t gid)
     {
            int old_egid = current->egid;
    +
    +       if (gid>0xffff) return -EINVAL;

            if (suser())
                    current->gid = current->egid = current->sgid = current->fsgid = gid;
    @@ -489,6 +493,8 @@
     asmlinkage int sys_setuid(uid_t uid)
     {
            int old_euid = current->euid;
    +
    +       if (uid>0xffff) return -EINVAL;

            if (suser())
                    current->uid = current->euid = current->suid = current->fsuid = uid;
    @@ -510,6 +516,8 @@
     asmlinkage int sys_setfsuid(uid_t uid)
     {
            int old_fsuid = current->fsuid;
    +
    +       if (uid>0xffff) return -EINVAL;

            if (uid == current->uid || uid == current->euid ||
                uid == current->suid || uid == current->fsuid || suser())
    @@ -525,6 +533,8 @@
     asmlinkage int sys_setfsgid(gid_t gid)
     {
            int old_fsgid = current->fsgid;
    +
    +       if (gid>0xffff) return -EINVAL;

            if (gid == current->gid || gid == current->egid ||
                gid == current->sgid || gid == current->fsgid || suser())
    @@ -563,6 +573,8 @@
     asmlinkage int sys_setpgid(pid_t pid, pid_t pgid)
     {
            struct task_struct * p;
    +
    +       if (pid>0xffff || pgid>0xffff) return -EINVAL;

            if (!pid)
                    pid = current->pid;