[comp.unix.wizards] setreuid

rayan@ai.toronto.edu (Rayan Zachariassen) (01/20/88)

I'm trying to simulate the effect (within a root process) of the BSD construct:

	setreuid(0, uid);
	...
	setreuid(0, 0);

The important thing being that the '...' is run with effective uid 'uid',
and that both real and effective uid are restored to 0 afterwards.

Now, the SysV manual for setuid() says that

	setuid(uid);
	...
	setuid(0);

is possible iff 0 is the "saved set_user id" and refers to exec(2).
Of course, exec(2) doesn't mention this concept.  What does "saved set_user id"
mean? That the suid bit is set on the executable and this is the owner of
the binary, or that it is the uid that actually did the exec'ing (uid of
parent)?

So, we tried it under SysVr2v4 (no, this is not crypt output...) on a
Honeywell box, the token System V machine here.

Apparently, the documented behaviour refers to neither of the two possibilities
hypothesized.  If anyone knows how to achieve the desired result under
some System V (without using fork()), pray tell.

Incidentally, the '...' is essentially an fopen call.  Can't use access();
for one thing it is junk, for another it tests on the real uid anyway, which
brings us back to the problem at hand.

You can also tell me it can't be done, in which case this program will
allow astute persons to deduce the contents of files they can't read, and
aren't allowed to access. :-( How does one write secure code under System V
without using zillions of forks? (that's rhetorical).

Incidentally, I was reminded that the SVID mumbles something about setuid()
making use of the original uid/gid's of the process.  Might this mean that
(some versions of) System V and the SVID are different in these details?
If so, what's the scoop?  I dimly recall a discussion here on the subject.

rayan

ps: to make this educational, here's a method using one too many fork()'s:

	if ((pid = fork()) == 0) {	/* child */
		(void) setuid(uid);
		if (open(file, 0) < 0)
			_exit(errno)
		_exit(0);
	} else if (pid < 0) {
		sorry, we can't check permissions right now...
		return; /* assume bad guy */
	}
	(void) wait(&status);
	if (status > 0)
		return;	/* permission denied */
	...open file, etc...

but this is really really really silly.

slb@hudson.acc.virginia.edu (Sandy Bryant) (01/21/88)

In article <1988Jan19.192854.3411@jarvis.csri.toronto.edu> rayan@ai.toronto.edu (Rayan Zachariassen) writes:
..I'm trying to simulate the effect (within a root process) of the BSD construct:
..
..	setreuid(0, uid);
..	...
..	setreuid(0, 0);
..
..The important thing being that the '...' is run with effective uid 'uid',
..and that both real and effective uid are restored to 0 afterwards.
..
..Now, the SysV manual for setuid() says that
..
..	setuid(uid);
..	...
..	setuid(0);
..
..is possible iff 0 is the "saved set_user id" and refers to exec(2).
..Of course, exec(2) doesn't mention this concept.  What does "saved set_user id"
..mean? 

There is a field in the user struct that saves the effective uid
for a setuid program at exec.  This usually allows you to switch 
your effective uid back and forth between your real uid and the original 
effective uid at will.  Problem is that whenever *root* calls setuid(),
everything - real uid, effective uid, AND the saved set_user id field in 
the user struct are all set to the new uid, so you can never go back to 
being root again.  To do otherwise creates a security hole, I guess.  
If you can use some other "priviledged" id besides root to own those 
files, you can do what you want.

This was true in SVR2, SVR2.1, and SVR3 on the AT&T 3B's, and I assume
it is standard SV behavior.  The man page does day that when root calls
setuid, both effective and real uid are set.

sandy

sandy

kre@munnari.oz (Robert Elz) (01/23/88)

In article <172@hudson.acc.virginia.edu>, slb@hudson.acc.virginia.edu (Sandy Bryant) writes:
> Problem is that whenever *root* calls setuid(),
> everything - real uid, effective uid, AND the saved set_user id field in 
> the user struct are all set to the new uid, so you can never go back to 
> being root again.  To do otherwise creates a security hole, I guess.  

Not really.  If a root process really wants to do something as another
user, and then come back, it can easily enough in any case ...

	if (fork() == 0) {
		setuid(someome);
		do_whatever();
		exit(0);
	}
	wait(&status);

(plus some error checking) will do it every time.  Allowing root to
just switch uid's and then switch back cannot possibly create any
bigger "security hole".  Once you're root, security is gone anyway.

I suspect that the reason for the limitation is just pragmatics,
combined with a liberal dose of stupidity.

The Sys V developers at AT&T saw the need (correctly) for this facility,
(that is, real and effective uid switching) .. its been added to unix
ever since Waterloo (and probably others) did it in V6.

However, they seem to have a real phobia about adding anything new,
so rather than add a new system call, which could have worked sanely,
without annoying limitations, they just added the internal, hidden,
saved set_user value, and allowed the old setuid() sys call to use
that whenever it wanted.

Now we get to the potential security hole ... From time immemorial
(since Jan 1 00:00:00 1970 GMT ?) setuid processes have always believed
that "setuid(getuid())" is a safe and permanent way to remove any
special privileges they may have had, after which its safe to assume
that no special checking on path names, etc, is needed.

If a process that was setuid root did this, and the definition of
setuid(getuid()) was changed to allow a later setuid(0), then a
clever user might be able to break through all kinds of new security
holes, that didn't used to exist.  This is known as "breaking working
programs", and is, naturally, something to be avoided.

Thus the restriction: if a process that is setuid root does setuid(x),
where x != 0, then all of the uid's are changed, and the security
hole is closed.  And for anyone silly enough to believe that the setuid
bit could ever have been intended to be used with user id's other than
zero, well, tough!

This problem is even worse if the saved set_user id is retained through
exec (which my V.2 man pages very cleverly neglect to say anything
about, my V.3 manuals aren't accessible at the minute, so I can't
check those).

The moral: when you want to add something new, add something new, don't
modify something that exists already, and pretend that everything is
magically going to be compatible.

kre

gamiddleton@watmath.waterloo.edu (Guy Middleton) (01/29/88)

In article <1962@munnari.oz> kre@munnari.oz (Robert Elz) writes:
> The Sys V developers at AT&T saw the need (correctly) for this facility,
> (that is, real and effective uid switching) .. its been added to unix
> ever since Waterloo (and probably others) did it in V6.
> 
> However, they seem to have a real phobia about adding anything new,
> so rather than add a new system call, which could have worked sanely,
> without annoying limitations, they just added the internal, hidden,
> saved set_user value, and allowed the old setuid() sys call to use
> that whenever it wanted.

As a point of information, here is the way it worked at Waterloo:

	Instead of two uids (real and effetive), there were three: real,
	effective, and effective-at-exec-time.  This third uid (called the
	euid) was not directly settable, and was never (well, almost never)
	examined.

	Two new syscalls were provided --

		schizo(), which would set the effective uid to either of
		the other two.

		become(), which would set the effective uid to an arbitrary
		value.  It would only work if the effective-at-exec uid was
		zero [this was the only place the euid was looked at].

	setuid() did the usual thing, except it left the euid unaffected.

We don't use this any more.  The BSD setreuid() makes it unnecessary.

 -Guy Middleton, University of Waterloo Institute for Computer Research
 gamiddleton@math.waterloo.edu, watmath!gamiddleton