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;