[sf-lug] insecure temporary file vunerability, race conditions, good vs. perfect, etc.

Rick Moen rick at linuxmafia.com
Tue Nov 17 12:46:32 PST 2009


Passing along.  Appreciate the concern.

----- Forwarded message from Michael Paoli <Michael.Paoli at cal.berkeley.edu> -----

Date: Tue, 17 Nov 2009 03:00:20 -0800
From: Michael Paoli <Michael.Paoli at cal.berkeley.edu>
To: Rick Moen <rick at linuxmafia.com>
Subject: insecure temporary file vunerability, race conditions, good vs.
	perfect, etc.

Wouldn't want someone to exploit your system with this (specific
insecure temporary file) information.  Let me know when it's okay to
post to the sf-lug list ... or feel free to forward it on - or reply to
it on-list, feeling of course free to comment upon it as you wish
(though if you reply to it on-list before I post it or you forward it,
I'd prefer you include at least all my text below ... otherwise of
course feel free to trim mercilessly).

references/excerpts:
> Date: Mon, 16 Nov 2009 10:32:31 -0800
> From: Rick Moen <rick at linuxmafia.com>
> Subject: Re: [sf-lug] SF-LUG (& BALUG) DNS, etc.
> To: sf-lug at linuxmafia.com

> This is one of the key lessons of system administration:  The
> good-enough if half-assed solution that you actually do is better than
> the really good one that you never get to.

Uhm, good point, and I'd approximately agree.  I'd be inclined to state
more along the lines of it needn't be perfect, or as I believe a
coworker once (re)stated it:
The perfect is the enemy of the good. (apparently from Voltaire)
But in areas such as systems administration, one should take sufficient
care that it's not going to cause problems or allow something to cause
problems - the results can be significant to catastrophic.
see also:
http://www.rawbw.com/~mp/unix/sh/#Good_Programming_Practices
E.g. jr. sysadmin adds a
crontab job that runs a script that looks basically like this:
#!/bin/sh
cd /var/local/log/fooapplication/log
find . -type f -mtime +3 -exec rm -f \{\} \;
... and was run by root.  Guess what happened the very first time that
cd failed?  Instant nasty death of production system.  Just a teensy bit
more care would have averted disaster, e.g.:
#!/bin/sh
cd /var/local/log/fooapplication/log &&
find . -type f -mtime +3 -exec rm -f \{\} \;
or:
#!/bin/sh
set -e
cd /var/local/log/fooapplication/log
find . -type f -mtime +3 -exec rm -f \{\} \;

(and you did include at least some set -e or equivalent - I've omitted
at least some of that below for brevity)

> #!/bin/sh
> dig -t soa linuxmafia.com @NS.PRIMATE.NET +short | awk {'print $3'} > 
> /tmp/domains
> dig -t soa linuxmafia.com @NS.TX.PRIMATE.NET +short | awk {'print $3'} 
> >> /tmp/domains
> dig -t soa linuxmafia.com @NS2.LINUXMAFIA.COM +short | awk {'print $3'} 
> >> /tmp/domains
> dig -t soa linuxmafia.com @NS1.THECOOP.NET +short | awk {'print $3'} >> 
> /tmp/domains
> dig -t soa linuxmafia.com @NS1.LINUXMAFIA.COM +short | awk {'print $3'} 
> >> /tmp/domains
> test -e /tmp/domains || exit 0
> /usr/bin/mail -s "Domain linuxmafia.com SOA check" rick at linuxmafia.com < 
> /tmp/domains

When first glancing over the above, the most immediate thing I think of
is insecure temporary file vulnerabilities.  Now, this might be one's
own home box, and there might not "ever" be any other users - but even
if/when that might be the case, I still tend to think in terms of
multi-user multi-tasking operating system, and security.  Besides, who
knows, maybe things change later, or the script appears to work so
wonderfully that it gets copied and implemented elsewhere.  Most, if
not all, major popular Linux distribution offer excellent tools for
securely creating temporary files - so if I were to create a temporary
file in a shell script, I'd likely use those utilities in such an
environment.  If I needed to be more portable (e.g. POSIX), and might
not have such utilities available, the temporary file vulnerability
situation can be avoided indirectly - by first creating a directory -
even for root, mkdir is an atomic operation, and will fail if the
directory already exists (mkdir(2), that is - but likewise for mkdir(1)
if one just creates a single directory).
For an example using mkdir, see:
http://www.rawbw.com/~mp/unix/sh/examples/viewman
But our "solution" can be even much simpler in this case.  Why even
create temporary file if it's not needed?:

So, ... a much more secure quick and dirty:
{
dig -t soa linuxmafia.com @NS.PRIMATE.NET +short | awk {'print $3'}
dig -t soa linuxmafia.com @NS.TX.PRIMATE.NET +short | awk {'print $3'}
dig -t soa linuxmafia.com @NS2.LINUXMAFIA.COM +short | awk {'print $3'}
dig -t soa linuxmafia.com @NS1.THECOOP.NET +short | awk {'print $3'}
dig -t soa linuxmafia.com @NS1.LINUXMAFIA.COM +short | awk {'print $3'}
} |
/usr/bin/mail -s "Domain linuxmafia.com SOA check" rick at linuxmafia.com

Now, the above doesn't first check for non-empty output, but we could
add that if we wanted (shove data securely into temporary file, or
shove data into shell variable, securely test if that's empty or not,
and if it's not empty, we could then feed it to the mail program, and
in the case of shell variable, we could use a here document and the
shell would take care of securely creating a temporary file for the
here document - if it needed a temporary file at all for it.)

I'd also make all those domains fully qualified - end in . - for the
root domain - that way there's no ambiguity or room for
misinterpretation by dig, resolver libraries, etc.  In nearly all
cases, one can use a fully qualified domain name (FQDN).  FQDNs end in
. and that will work in almost all circumstances, and it absolutely
works in the realm of DNS.

Of course there are various other tweaks we could make too - such as
just using a single invocation of awk (after we've consolidated our
output, but before going to mail program, file, or shell variable).

If we wanted to bother (would be handy when we have hundreds of
domains), could use a loop to process them, rather than have other
redundant content on each line - that would aid maintenance a bit (if
one ever wanted to change "those lines", there'd be only one line to
change)., e.g. something like:
#!/bin/sh
for ns in \
NS.PRIMATE.NET. \
NS.TX.PRIMATE.NET. \
NS2.LINUXMAFIA.COM. \
NS1.THECOOP.NET. \
NS1.LINUXMAFIA.COM.
do
    dig -t soa linuxmafia.com. @"$ns" +short
done |
awk '{print $3;};' |
/usr/bin/mail -s "Domain linuxmafia.com SOA check" rick at linuxmafia.com

Back to Rick's point, "of course" we could go on and on tweaking and
improving ... when we likely should be doing other more useful stuff
elsewhere instead.  But back to my point, I'd be inclined to at least do
the slight bit(s) to eliminate the insecure temporary file
vulnerability.  The rest is all most likely not critical, and could be
done, or not, based on various relative priorities of things to be
worked on.

So, what's a temporary file security vulnerability?  Let's say, with
our original example, /tmp/domains doesn't yet exist - perhaps the
script removed it, or a /tmp cleanup job removed it, or /tmp is
volatile and the system was rebooted, or a brand new system's been set
up, everything was copied over, but contents of /tmp were (quite
reasonably) not copied.  So, let's say there's some other user or
process on the system, ... perhaps that even knows of script that will
be run by cron that writes to /tmp/domains (there are even tools to
look for such vulnerabilities - e.g. by looking to stuff written in
/tmp or /var/tmp - even if it's only there for a short while).  So,
let's say, before the legitimate script writes (and thus creates) a new
/tmp/domains file, a nefarious user or process does something like:
$ ln -s /some/critical/security/file /tmp/domains
So, ... along comes the legitimate script, and it writes to
/tmp/domains - and in so doing truncates and overwrites
/some/critical/security/file.  That's one's basic temporary security
file vulnerability.  And before one thinks, "Oh, I can just remove it
first, and create it fresh, or I can test and then" - there's an
additional class of problems - race conditions.  Linux (and Unix, etc.)
- multiuser multitasking operating system, if you test, and then ... or
remove, and then ... well, other things can happen between the part
that comes before and then, and the part that comes after.  One of the
ways of solving race conditions is atomic operations.  E.g. mkdir(2) -
it's a single atomic system call, it either works and successfully
creates the directory where it didn't exist, or it fails - even for
root, so the test is implicit in the action attempt - one can then test
the results of the operation to know if the action succeeded (and to do
a secure temporary file via mkdir(2), we first securely create our
directory, with appropriate ownerships/permissions when we create the
directory (not after - that's too late), then we create our temporary
file(s) within the newly created directory - and paying appropriate
attention to umask - and possibly file names - when creating the files,
at least if the permissions on the directory are more lax than
drwx------).


----- End forwarded message -----




More information about the sf-lug mailing list