Subject: Re: Addition to force open to open only regular files
To: None <tech-kern@netbsd.org>
From: Greg A. Woods <woods@weird.com>
List: tech-kern
Date: 11/22/2000 16:51:50
[ On Thursday, November 23, 2000 at 03:13:37 (+0900), Noriyuki Soda wrote: ]
> Subject: Re: Addition to force open to open only regular files
>
> I cannot understand the above statement.
> If I understand correctly,
> 
> 	fd = open_as(filename, ...., real_uid);
> 
> is just same with the following code:
> 
> 	if ((euid = geteuid()) == real_uid) {
> 		fd = open(filename, ...);
> 	} else {
> 		seteuid(real_uid);
> 		fd = open(filename, ...);
> 		seteuid(euid);
> 	}
> 
> So, saved-uid/gid feature can do what open_as() can do.

Of course it can.

> And, open_as() cannot do what saved-uid/gid can do.

Exactly -- this is why open_as() is better.  Saved-set-ID swapping has
directly caused several successful exploits in the past in at least two
different implementations that I'm aware of, and given its nature it
makes other kinds of vulnerabilities much more dangerous.

> We already have saved-uid/gid feature, we currently don't have
> open_as().

Which is why I've describe open_as() as a feature swap.

> Saved-uid/gid is almost portable on all POSIX based systems(*),
> open_as() is not portable at all.

Of course.  I presume that's why at least one other *BSD has expressed
interest in it.

> So why you'd say "open_as is the only one that provides sufficient
> functionality"?

It's the only proposal that provides sufficient and *only* sufficient
functionality without using saved-set-ID swapping.

> I seemd to recall that calling open_as() can automatically disable
> setreuid(2)/setregid(2).

Forget setre*id() -- they are deprecated (and I have already made them
no-OPs in my kernel), they do nothing.  I may remove them from my libc
and just forget about changing the major number because I can know
separately that no program will ever use them anyway.

> But that doens't solve any problem.
> The setreuid(2)/setregid(2) problem is that a application can
> call setreuid(2)/setregid(2) before calling a library function
> which calls open(2), so, the library function cannot know
> how to drop it's setuid privilege. Open_as() doesn't solve this
> problem. So, there is nothing that open_as() is better than
> saved-uid/gid feature.

Assuming you mean seteuid(2) and setegid(2), then under at least my
specification for open_as() this is not a problem because the library
can always rely on getuid() returning the "correct" ID for any purpose
that's been discussed to date (i.e. $HOSTALIASES and $TZ).

If a setuid-root program has done setuid(0) before the library is called
then open_as() won't provide any "protection", but on the face of it
that would appear to be the intent anyway, and I think such a program's
author will be clue-by-4'ed before long too.  In any case it can't do
any more damage than could be done any other way, so this is a moot
point.

So in fact open_as(), as I've specified it, does solve the problem,
*and* it solves another similar but coneptually opposite problem at the
same time.

Open_as is also better than allowing arbitrary saved-set-ID swapping
because it can restrict what a privileged process can do, such as
preventing a buffer-overflow exploit from successfully inserting and
executing the following example code in a setuid-root program:

	seteuid(0);
	if (fork() > 0)
		execve("/bin/sh", shargs, newenv);

Without saved-set-ID swapping and only open_as() an exploit would have
to first introduce a trojan somehow into a file (eg. a new entry in
/etc/inetd.conf that invokes a back-door root shell).  However such an
exploit cannot be activated immediately (unless there's a companion bug
in inetd which can be used to crash/restart/reload it from remote).
This gives intrusion detection systems time to react and report the back
door so it can be disabled before it is exploited.

> (*) The reason that our saved-uid/gid feature is not compatible
>   with POSIX_SAVED_ID is:
> 	In POSIX_SAVED_ID, only root-setuid program can drop
> 	it's saved uid by setuid(2), normal-user-setuid program
> 	cannot drop it's saved uid.
> 	In NetBSD, normal-user-setuid program can drop it's
> 	saved uid privilege.

No, that's wrong because even in NetBSD a setuid-non-root program can
drop its saved-set-ID privs.  This must be possible else NetBSD would be
broken by definition.  Here's how the manual describes it:

     The setuid() function sets the real and effective user IDs and the saved
     set-user-ID of the current process to the specified value.  The setuid()
     function is permitted if the specified ID is equal to the real user ID of
     the process, or if the effective user ID is that of the super user.

The difference in POSIX is that seteuid() also sets the saved
set-user-ID value for the current process if the effective user-ID is
that of the superuser.  I.e. in POSIX a setuid-root process can never
regain its root privs after calling seteuid(getuid());

I.e. in POSIX the buffer-overflow exploit example above is impossible
against setuid-root processes, but still possible against
setuid-non-root processes.

In *BSD even a setuid-root program can regain its root privileges,
i.e. *BSD allows all saved-set-ID swapping, thus the vulnerability in
face of a buffer overflow is always possible, even for setuid-root
programs.

So, if even if NetBSD were only to be fully POSIX compliant in this
matter (i.e. not to completely give up saved-set-ID swapping for
non-zero UIDs), some solution would still be needed for the
access()/open() race condition in setuid-root programs, and I believe
open_as() is the best possible solution.

-- 
							Greg A. Woods

+1 416 218-0098      VE3TCP      <gwoods@acm.org>      <robohack!woods>
Planix, Inc. <woods@planix.com>; Secrets of the Weird <woods@weird.com>