>Marcus always has good ideas. I'm a little surprised at his apparent
>dislike of testing, [...]
I don't dislike testing!!! I have *NEVER* said that!
I dislike things that are expensive and don't work in
the real world: like formal methods, especially when they are
held up as a paragon of how to do something right, when in
reality they amount to doing nothing. :) You CANNOT prove an
absence of bugs. They teach you in logic 101 that you can't
prove a negative - so let's focus on engineering things to
be robust, fail safely, and have backup safe failure modes.
I'm a big believer in practical testing. That means
you test the HELL out of stuff you know from real life experience
is likely to break. For example, smb pointed out to me that
resource starvation in firewalls might cause a bad situation
where netperm-table is read, and when a proxy tries to map a
username to a uid to do a setuid to a non-prived user, it
might fail due to insufficient file descriptors and not do
the setuid. That's why there are checks for all relevant
system errors in the proxies. [check lib/mapu.c - it hasn't
changed since the first version of the toolkit and it checks
for exactly that. amuse yourself by seeing what happens to
the proxies if calls to mapuid fail]
Testing where you know applications typically have
problems is really really important because that is where
you get your bang for your buck. Some people call these
"boundary conditions" - that's academic pretension: the
fact is that mistakes happen in predictable places and most
security mistakes happen in predictable places. So you
check and test there. In fact, if you can you design your
code (I did) to not rely on *CODE* for those critical places
but rather to rely on the O/S.
By relying on the O/S you can make some pretty
strong assumptions, I think:
1) My process has done a chroot to /lockdown
2) My process has done a setuid() to an unprivved uid
3) There are no executables or devices in /lockdown
4) /lockdown is owned by root and is unwriteable
Therefore, I'm content to assume that an attacker is
going to have a lot of trouble getting privs for that process,
since there's nothing setuid and no devices. Processes that
are unprivved can't chroot and can't make nodes so I am pretty
darned comfortable with the fact that that process is locked
up pretty well. I'm able to sleep at night fairly comfortable
that someone isn't going to be able to do a lot of damage
with that process!! It doesn't mean I don't check for error
conditions elsewhere in the code because I do -- but I've made
mistakes before and this is a REALLY good way to minimize
their impact.
To test that this worked correctly, I did the following:
1) left /lockdown world writeable and had my process
chdir to /.. and create a file called /foo
2) verified that /lockdown/foo appeared and was owned
by my unprivved uid
3) made /lockdown unwriteable and tried again and made
sure it failed
This is what I call "practical testing" and it's based
on intimate understanding of the O/S and a minimalist approach
to relying on its features.
Now, I did not go review the kernel code that implements
setuid() chroot() and so on. That's for the trusted systems guys
and you can see the impact that trusted system style assurance
has on your product schedules.
mjr.
Follow-Ups:
References:
|
|