Subject: Re: BSD auth for NetBSD
To: Jason Thorpe <thorpej@wasabisystems.com>
From: Greg A. Woods <woods@weird.com>
List: tech-security
Date: 09/12/2003 17:46:47
[ On Friday, September 12, 2003 at 12:17:00 (-0700), Jason Thorpe wrote: ]
> Subject: Re: BSD auth for NetBSD
>
> What sort of evidence do you need other than "some authenticators need 
> to modify the context of the process".  You can't currently do that 
> with the proxy scheme used by BSD Auth.

not directly, no, but you don't really have to do it directly....

> If you have a solution for this problem, hey, we're all ears.

Well, the most obvious (to me) solution is to write a little BSD Auth
authenticator module that would act as a _full_ proxy for a given PAM
thingy.  ("PAM module" seems too redundant to me -- is there another
term for the loadable modules?  Why didn't they call it "Pluggable
Authentication Framework"?  ;-)

What do I mean by "_full_ proxy"?  Well, assuming we know (by spec. or
by examination of the PAM code) what exactly the authenticator does to
the current process context then it's simply a matter of making it
possble for a specially written BSD Auth proxy authenticator module to
invoke PAM from its own separate process context and then if the
authentication succeeds to retrieve those changes PAM made to its own
context and then pass that information back to its parent process
through either existing BSD Auth hooks or alternately through new system
calls explicitly designed to facilitate this transfer.

In general I can't imagine anything that any PAM authenticator might do
to a process, either directly in process address space, or in the kernel
context for that process, which one would wish to allow under normal
circumstances, and which could not be encoded and passed back to the
parent process of the BSD Auth proxy authenticator in such a way that it
could be replicated in a controlled manner to the original caller
process as well.  I can imagine things that would be very ugly to deal
with, but so far none of the examples that have been proposed pose any
great problem that I can see.

The difference of course is that with BSD Auth all these things that
might be done by a proxied PAM thingy have to be known in advance so
that the very narrow BSD Auth protocol, or special new system calls, can
be used to transfer their effect as necessary.  This is a very good
thing since it forces full awareness of the interactions of any given
PAM code and it isolates that potentially foreign code to directly
messing with just one relatively short-lived authenticator proxy
process, not the whole application requesting authentication.

For example with the AFS credentials the BSD Auth to PAM proxy need only
be provided with a system call, or pair of system calls, that copy the
credential index entries from its own struct ucred back into its
parent's struct ucred.  Voila!  Now the parent process is blessed with
the authorization tokens it needs to successfully access the AFS
resources for which it has been authenticated and approved to access.

Of course with AFS it would make a lot more sense to just write a new
BSD Auth authenticator which would directly use a new setpag_parent()
system call to authorize the parent process with the new credentials in
much the same way I hinted way back when I cited the paper describing
the similar system call used for OSF DFS.  I.e. with AFS there is no
real need to have the authenticator directly modify the calling process'
context -- those same index values for the AFS credentials can be safely
stored in any process context by a sufficiently privileged process and
that can be done of course without revealing the actual credentials to
the authentiator process.  After all the current hack is simply
modifying the groups list to stick the credential index(es) as integer
values at the front of that list and that requires root privileges and
thus root already handles exactly the same information it would need
handle to assign those credentials to any other process as I propose.
Ideally of course the credential pointers or indexes would be stored in
proper new struct ucred fields, not by overloading the groups list with
their presence.  (One could pass the credential index(es) back to the
parent via the environment variable trick already supported by BSD Auth
and then the caller could authorize itself before calling
setusercontext(), but that would require special AFS-specific code in
the client side of libauth or libc, which I gather we're trying to
avoid.)

In the case of these so-called "template users" for something like a
RAIDUS or TACACS authenticator it also makes the most sense to simply
write a new BSD Auth authenticator since such a thing is really quite
simple on its own (free example code abounds).  Note there's no need
even to invent a new system call like setuid_parent() (more about this
below).

Before BSD Auth or PAM the use of RADIUS to authenticate to template
accounts (where the RADIUS server doesn't have enough information to
construct a full username & password databse) is done simply by having
appropriate code to deal with the concept in whatever application
requests authentication.  Unix has always had the ability to give
multiple username and password authentication tokens to a single system
identity (User-ID).  Several people have already added direct hooks to
*BSD systems to do this via RADIUS directly in login, ftpd, etc.  All
that's needed is to know when to pass the username (and possibly the
authentication as well) to the RADIUS server without first looking it up
in the username database (or when to ignore the fact it isn't in the
username database and go on to ask RADIUS).  One way of choosing which
authentication mechanism to use for unknown usernames is to control it
by way of configuration attributes, perhaps including username glob
patterns, in /etc/login.conf.  The actual template accounts must already
exist in the username database of course and the new authenticator code
simply uses some attribute returned by the RADIUS server to choose the
correct user-ID to use from those available "template" accounts
(assuming there's more than one).  All the rest from there on is normal
unix authorization.  The authenticated username can still be used with
setlogin(), perhaps based on whether or not some other login.conf
attribute is set, but of course "ls -l" will show files accessible by
the authenticated user as being owned by the template account.  As I say
most of all of this has already been done in various ways by multiple
people for various kinds of systems and at least one implementation I
know of is publicly available.  Some existing commercial unix-based
routers may already work this way.  :-)

All that's needed to implement template accounts with BSD Auth is to do
the above and then push the authentication part of the procedure out to
BSD Auth (which is the whole point of BSD Auth in the first place) and
lastly of course to find some way to get the chosen template UID back
from the authenticator module.  Peter has shown how that works with
environment variable passing and with a minor adaptation to the library
code which handles this protocol it's simply a matter of reserving one
variable name in which to pass the UID value.  So from there it is
simply a matter of putting the template UID choosing code in the
authenticator module, along with the RADIUS or whatever interface of
course, and then pass back the chosen UID in the reserved environment
variable to the library code that would know to use it when calling
setusercontext().

Note that all of this seemed very obvious to me right from the get go
when I first learned about BSD Auth, so I appologize if I've assumed it
would be just about as obvious to everyone else.  The AFS wiggles took
me more time to grasp, but that was more due to the fact I found all the
AFS terminology extremely foreign and I had to dig quite a bit even to
find out what a PAG _really_ was, etc.  Learning _exactly_ what was
being done to the process context was like pulling teeth from a very
angry and very hungry carnivore twice my size!

I find it quite bizzare that anyone consider it a requirement of PAM
that it be able to modify the calling process or its context directly.
PAM might be allowed to do that in its own back yard, but there's no
reason its modules can't be put in a limited playpen with razor-wire
fencing and a very wise and careful proxy at the gate who only lets it
get away with things it's explicitly required and permitted to do.

BSD Auth doesn't change the privileges necessary to authorize a process
as a newly authenticated UID -- it just retains the control over what
the authenticator module can do to the calling process and makes it
possible to separate the privileges needed for authentication from those
necessary to authorize a process thus making it possible to delegate
authentication privileges to a non-privileged UID and to then know that
no matter how buggy or compromised the authenticator is the best its
attacker can do, besides run other arbitrary foreign code as the
non-privileged UID, is validate the credentials of a specified username
without actually checking them.  Even with the template account feature
the attacker can only _suggest_ a UID to authorise the caller as, but of
course the caller need not honour that suggestion if it seems out of
line.  This makes it possible to greatly reduce the risk of running
third party authenticator code which might make network connections to
look up the data it needs such as from the likes of a RADIUS server on
an untrusted network.  Spoofed malicious RADIUS replies no longer get
instant root privileges if they manage to exploit a vulnerability in the
authenticator code, and even if the attacker is really smart there's no
reason the system must be configured in such a way as to allow a RADIUS
user to be authenticated as UID==0.

(When you think about it beyond the first obvious levels it's clear that
within the framework of the unix security model the delegation of
authenticator privileges to a non-superuser requires that the
authenticator run in a separate process context and that it only be
allowed to communicate very limited things back to the caller (which
will bt running as root as it is privileged to authorize its process as
any UID).  I.e. BSD Auth is a very obvious solution to this problem, and
as it turns out it's a very clean, simple, and elegant implementation as
well.  You wouldn't want to try to use a less trusted authenticator with
something like AFS which requires much more trust, but at least with BSD
Auth you can do both separately as required.)

-- 
						Greg A. Woods

+1 416 218-0098                  VE3TCP            RoboHack <woods@robohack.ca>
Planix, Inc. <woods@planix.com>          Secrets of the Weird <woods@weird.com>