Re: BEWARE! Linux seteuid is broken!

A.N.Kuznetsov (kuznet@ms2.inr.ac.ru)
Sun, 16 Jun 1996 19:27:13 +0400 (MSD)


Hello!

> Huh? seteuid() was never implemented; it's not in 1.2.x.
Cannot believe :-)...(searching through old kernels)... Yes. I'm sorry.

>
> seteuid() is also not a POSIX function; it's not defined in POSIX. So
> if you use it, you must be using it because you're interested in BSD
> compliance.
>
> Keep in mind that the whole notion of "effective" uid is non-POSIX. So
> trying to mix effective uid and saved-uid is invariably going to cause
> problems.
Formally, you are right. Reading Stevens's book, I missed word
"proposed" before POSIX.1 and was sure that it is in POSIX.1.
But really, the most important point is that POSIX.1 does not
specify "super-user", so that in POSIX domain setuid() is exactly UNIX
(BSD and SVR4, but not Linux) seteuid().

> I deliberately had setreuid() set the saved-uid because this
> would seem to avoid certain security problems. See the comments in
> kernel/sys.c for my justification for doing this.

Linux setreuid is broken too!!!

Example (from real life, it is function key_call() called from set-uid
root program):

main() {
int uid = getuid();
printf("%d %d\n", getuid(), geteuid());
seteuid(uid);

/* key_call */
printf("%d %d\n", getuid(), geteuid());
setreuid(uid,uid);
printf("%d %d\n", getuid(), geteuid());
setreuid(uid,uid);
printf("%d %d\n", getuid(), geteuid());
/* key_call returns */

seteuid(0);
printf("%d %d\n", getuid(), geteuid());
}

BSD/SVR4 (2018 is my uid):
2018 0
2018 2018
2018 2018
2018 0

Linux:
2018 0
2018 2018
2018 2018
2018 2018

That's all. Linux faults. Until now I believed that it is one
of numerous bugs in RPC library, but now I checked key_call under
FreeBSD and Solaris. It works there. It is bug in Linux kernel.

Emulation of seteuid(.) by setreuid(-1,.) in libc is another bug.
Moreover, it is impossible to emulate BSD/SVR4 (well, not POSIX :-))
seteuid() without kernel support.

Just compile the following program, make it set-uid root and
run on any UNIX (I checked FreeBSD and Solaris2.3,2.4) and Linux:

main()
{
printf("%d %d\n", getuid(), geteuid());
seteuid(1);
printf("%d %d\n", getuid(), geteuid());
seteuid(0);
printf("%d %d\n", getuid(), geteuid());
}

BSD/SVR4 output:
2018 0
2018 1
2018 0

Linux output:
2018 0
2018 1
2018 1

Do you keep to insist that Linux uid handling is correct? 8)

Alexey Kuznetsov.

Example of correct seteuid/setreuid (FreeBSD).

struct seteuid_args {
uid_t euid;
};
/* ARGSUSED */
int
seteuid(p, uap, retval)
struct proc *p;
struct seteuid_args *uap;
int *retval;
{
register struct pcred *pc = p->p_cred;
register uid_t euid;
int error;

euid = uap->euid;
if (euid != pc->p_ruid && euid != pc->p_svuid &&
(error = suser(pc->pc_ucred, &p->p_acflag)))
return (error);
/*
* Everything's okay, do it. Copy credentials so other references do
* not see our changes.
*/
pc->pc_ucred = crcopy(pc->pc_ucred);
pc->pc_ucred->cr_uid = euid;
p->p_flag |= P_SUGID;
return (0);
}

struct setreuid_args {
int ruid;
int euid;
};
/* ARGSUSED */
int
setreuid(p, uap, retval)
register struct proc *p;
struct setreuid_args *uap;
int *retval;
{
register struct pcred *pc = p->p_cred;
struct seteuid_args args;
int error;

if (uap->ruid != (uid_t)-1 && uap->ruid != pc->p_ruid &&
uap->ruid != pc->p_svuid &&
(error = suser(pc->pc_ucred, &p->p_acflag)))
return (error);
if (uap->euid != (uid_t)-1 && pc->pc_ucred->cr_uid != uap->euid) {
args.euid = uap->euid;
if ((error = seteuid(p, &args, retval)))
return (error);
if (pc->pc_ucred->cr_uid != pc->p_ruid)
pc->p_svuid = pc->pc_ucred->cr_uid;
}
if (uap->ruid != (uid_t)-1 && uap->ruid != pc->p_ruid) {
(void)chgproccnt(pc->p_ruid, -1);
(void)chgproccnt(uap->ruid, 1);
pc->p_ruid = uap->ruid;
pc->p_svuid = pc->pc_ucred->cr_uid;
p->p_flag |= P_SUGID;
}
return (0);
}