<<< Date Index >>>     <<< Thread Index >>>

[patch] Re: security problem with temp files [was Re: mutt_adv_mktemp() ?]



On 2006-10-04 18:16:19 -0400, Derek Martin wrote:

> On Wed, Oct 04, 2006 at 10:58:40PM +0200, Thomas Roessler wrote:
> > On 2006-10-04 16:39:29 -0400, Derek Martin wrote:
> > 
> > > It would seem that Mutt's temp file creation mechanisms all
> > > suffer from a potentially exploitable race condition.  Actually
> > > there are two: one in functions that call safe_open(), which only
> > > affects users creating temp files on NFS file systems (due to the
> > > O_EXCL problem), and one in functions that make use of
> > > safe_fopen(), because the resulting file is not adequately
> > > checked to determine if other users can modify it before it is
> > > written to.
> > 
> > Well, umask (077) is one of the first things mutt does, so the
> > second problem looks like a non-issue to me.  (I.e., other users
> > can't modify the temporary file unless the owner deliberately
> > changed the permissions.  Oh well.)
> 
> Not so.  An attacker could create the file first, in between the
> call to mktemp() and fopen().  As I said in my last message, the
> call to fopen(blah, "w+") will truncate the file if it exists,
> but it does nothing to ensure the modes of any existing file, nor
> will your umask.

Where do you see the fopen (blah, "w+")?

A safe_fopen (blah, "w+") dispatches to an safe_open with O_EXCL.
So, if at all, problem 1 can occur in the NFS context when O_EXCL is
broken.  O_EXCL is the crucial one here...

>> I'll admit that my memory of NFS subtleties is getting rusty at
>> this point.  Mind to elaborate on what safe_open ought to do
>> besides the check of comparing fstat and lstat results?

> The scheme I outlined in the previous message of creating a
> temporary directory with permissions 700 and creating temp files
> in there, checking permissions before trusting temp files, is
> suitable to fix both problems.  Best of all, using that solution
> makes temp file creation consistent for all uses of temp files,
> which currently is not the case.  I'll note that mutt should be
> careful to remove any such temp directory upon exit or receiving
> a signal which would normally terminate it.  

Agree for temporary files.

> Anywhere mutt needs to do this for files which are intended to
> hang around, e.g. new mbox folders, you can do that and then
> link(2) it to the temp file.

I'm trying to parse what you are suggesting here: Do you mean that
mutt should create all new files in the temporary directory and then
use a hard link to "move" (abuse of language, sorry) them to the
right place?  Sounds like a nice idea in theory, but it will fail as
soon as the temporary directory is on a file system different from
the target directory.

We could go to the length of creating a new temporary directory
around every new file that we create, and then do the linking dance,
but that sounds a bit like overkill.  

Then again...  That could actually localize the change to safe_open,
no?

Patch attached; comments very welcome.  

This one does keep the failure mode of somebody putting a file in
place and mutt not succeeding to create a temporary file.  At the
same time, it takes care of the failure mode of somebody attacking
an NFS-using mutt and corrupting folders, or wreaking other havoc on
the data that mutt processes.  Not yet committed.

-- 
Thomas Roessler   <roessler@xxxxxxxxxxxxxxxxxx>
Index: lib.c
===================================================================
RCS file: /cvs/mutt/mutt/lib.c,v
retrieving revision 3.20
diff -u -r3.20 lib.c
--- lib.c       18 May 2006 17:35:29 -0000      3.20
+++ lib.c       5 Oct 2006 08:20:38 -0000
@@ -481,14 +481,85 @@
   return 0;
 }
 
+/* Create a temporary directory next to a file name */
+
+int mutt_mkwrapdir (const char *path, char *newfile, size_t nflen, 
+                   char *newdir, size_t ndlen)
+{
+  const char *basename;
+  char parent[_POSIX_PATH_MAX];
+  char *p;
+  int rv;
+
+  strfcpy (parent, NONULL (path), sizeof (parent));
+  
+  if ((p = strrchr (parent, '/')))
+  {
+    *p = '\0';
+    basename = p + 1;
+  }
+  else
+  {
+    strfcpy (parent, ".", sizeof (parent));
+    basename = path;
+  }
+
+  do 
+  {
+    snprintf (newdir, ndlen, "%s/%s", parent, ".muttXXXXXX");
+    mktemp (newdir);
+  } 
+  while ((rv = mkdir (newdir, 0700)) == -1 && errno == EEXIST);
+  
+  if (rv == -1)
+    return -1;
+  
+  snprintf (newfile, nflen, "%s/%s", newdir, NONULL(basename));
+  return 0;  
+}
+
+int mutt_put_file_in_place (const char *path, const char *safe_file, const 
char *safe_dir)
+{
+  int rv;
+  
+  rv = safe_rename (safe_file, path);
+  unlink (safe_file);
+  rmdir (safe_dir);
+  return rv;
+}
+
 int safe_open (const char *path, int flags)
 {
   struct stat osb, nsb;
   int fd;
 
-  if ((fd = open (path, flags, 0600)) < 0)
-    return fd;
+  if (flags & O_EXCL) 
+  {
+    char safe_file[_POSIX_PATH_MAX];
+    char safe_dir[_POSIX_PATH_MAX];
 
+    if (mutt_mkwrapdir (path, safe_file, sizeof (safe_file),
+                       safe_dir, sizeof (safe_dir)) == -1)
+      return -1;
+    
+    if ((fd = open (safe_file, flags, 0600)) < 0)
+    {
+      rmdir (safe_dir);
+      return fd;
+    }
+    
+    if (mutt_put_file_in_place (path, safe_file, safe_dir) == -1)
+    {
+      close (fd);
+      return -1;
+    }
+  }
+  else
+  {
+    if ((fd = open (path, flags, 0600)) < 0)
+      return fd;
+  }
+    
   /* make sure the file is not symlink */
   if (lstat (path, &osb) < 0 || fstat (fd, &nsb) < 0 ||
       compare_stat(&osb, &nsb) == -1)

Attachment: pgpK1FK8O26Xi.pgp
Description: PGP signature