Skip to contentSkip to conversation

Harden create_temp_filename()

PATCH Superseded David Sommerseth 4 participants 10 messages Apr 15, 2010 – Apr 16, 2010

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Harden create_temp_filename()

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi James,

As promised in the meeting today, a patch for hardening create_temp_filename().

I've added more checks to what create_temp_filename() returns where it is called in addition, to make it even safer.

Please let me know what you think. If you give it an ACK, I'll add it to the bugfix2.1 branch and you can pull it in this way via the git tree.

kind regards,

David Sommerseth

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org/

iEYEARECAAYFAkvHgHcACgkQDC186MBRfrqMJQCglUwA/PADkK68C0w8RKYfEXtS TO8Anj3WYgx8dLQjdFILRYfRd26+7fw8
=eAtA
-----END PGP SIGNATURE-----

From 4eb6bce934492e3b6bf00e86bbaae4026d4da51d Mon Sep 17 00:00:00 2001
From: David Sommerseth <[email protected]...>
Date: Thu, 15 Apr 2010 23:00:27 +0200
Subject: [PATCH] Harden create_temp_filename()

In a Debian bug report [1] there were worries that the --client-connect script hook was prune to a "symlink" attack. Even though this can be recognised if --tmp-dir is set to a world writable directory, it is not considered standard practice to do so.

By hardening the create_temp_filename() function to check if the generated filename exists and to create the temp file with only S_IRUSR|S_IWUSR bit files set before calling the script, it should become even more difficult to exploit such a scenario.

[1] <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=534908>

---
 misc.c    |   38 ++++++++++++++++++++++++++++++++------
 multi.c   |   15 ++++++++++++---
 openvpn.8 |    2 +-
 pf.c      |   31 ++++++++++++++++---------------
 ssl.c     |   32 +++++++++++++++++++-------------
 5 files changed, 80 insertions(+), 38 deletions(-)
misc.c +32 -6
index b52a042..435d745 100644
@@ -1169,21 +1169,47 @@ create_temp_filename (const char *directory, const char *prefix, struct gc_arena
 {
   static unsigned int counter;
   struct buffer fname = alloc_buf_gc (256, gc);
+  int fd;
+  const char *retfname = NULL;
+  unsigned int attempts = 0;
 
-  mutex_lock_static (L_CREATE_TEMP);
-  ++counter;
-  mutex_unlock_static (L_CREATE_TEMP);
-
-  {
+  do {
     uint8_t rndbytes[16];
     const char *rndstr;
 
+    attempts++;
+    mutex_lock_static (L_CREATE_TEMP);
+    ++counter;
+    mutex_unlock_static (L_CREATE_TEMP);
+
     prng_bytes (rndbytes, sizeof (rndbytes));
     rndstr = format_hex_ex (rndbytes, sizeof (rndbytes), 40, 0, NULL, gc);
     buf_printf (&fname, PACKAGE "_%s_%s.tmp", prefix, rndstr);
+
+    retfname = gen_path (directory, BSTR (&fname), gc);
+    if( !retfname ) {
+      msg (M_FATAL, "Failed to create temporary filename and path");
+      return NULL;
+    }
+  } while ( test_file (retfname) && (attempts < 6) );
+
+  if( attempts > 5 ) {
+    msg (M_FATAL, "Failed to create temporary file after %i attempts", attempts);
+    return NULL;
+  }
+
+  /* Create the tempfile to avoid a race condition */
+  fd = creat (retfname, S_IRUSR | S_IWUSR);
+  if( fd == -1 ) {
+    struct gc_arena gcerr = gc_new ();
+    msg (M_FATAL, "Could not create temporary file '%s': %s",
+         retfname, strerror_ts (errno, &gcerr));
+    gc_free (&gcerr);
+    return NULL;
   }
+  close (fd);
 
-  return gen_path (directory, BSTR (&fname), gc);
+  return retfname;
 }
 
 /*
multi.c +12 -3
index 2b04428..e8d5eb4 100644
@@ -1531,6 +1531,12 @@ multi_connection_established (struct multi_context *m, struct multi_instance *mi
 	{
 	  struct argv argv = argv_new ();
 	  const char *dc_file = create_temp_filename (mi->context.options.tmp_dir, "cc", &gc);
+
+          if( !dc_file ) {
+            cc_succeeded = false;
+            goto script_depr_failed;
+          }
+
 	  argv_printf (&argv, "%s", dc_file);
 	  delete_file (dc_file);
 	  if (plugin_call (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT, &argv, NULL, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
@@ -1543,6 +1549,7 @@ multi_connection_established (struct multi_context *m, struct multi_instance *mi
 	      multi_client_connect_post (m, mi, dc_file, option_permissions_mask, &option_types_found);
 	      ++cc_succeeded_count;
 	    }
+        script_depr_failed:
 	  argv_reset (&argv);
 	}
 
@@ -1579,8 +1586,10 @@ multi_connection_established (struct multi_context *m, struct multi_instance *mi
 	  setenv_str (mi->context.c2.es, "script_type", "client-connect");
 
 	  dc_file = create_temp_filename (mi->context.options.tmp_dir, "cc", &gc);
-
-	  delete_file (dc_file);
+          if( !dc_file ) {
+            cc_succeeded = false;
+            goto script_failed;
+          }
 
 	  argv_printf (&argv, "%sc %s",
 		       mi->context.options.client_connect_script,
@@ -1593,7 +1602,7 @@ multi_connection_established (struct multi_context *m, struct multi_instance *mi
 	    }
 	  else
 	    cc_succeeded = false;
-
+        script_failed:
 	  argv_reset (&argv);
 	}
 
openvpn.8 +1 -1
index 82e7d24..301cd37 100644
@@ -2814,7 +2814,7 @@ on client connection.  The script is passed the common name
 and IP address of the just-authenticated client
 as environmental variables (see environmental variable section
 below).  The script is also passed
-the pathname of a not-yet-created temporary file as $1
+the pathname of a freshly created temporary file as $1
 (i.e. the first command line argument), to be used by the script
 to pass dynamically generated config file directives back to OpenVPN.
 
pf.c +16 -15
index 027eb69..c979461 100644
@@ -555,23 +555,24 @@ pf_init_context (struct context *c)
   if (plugin_defined (c->plugins, OPENVPN_PLUGIN_ENABLE_PF))
     {
       const char *pf_file = create_temp_filename (c->options.tmp_dir, "pf", &gc);
-      delete_file (pf_file);
-      setenv_str (c->c2.es, "pf_file", pf_file);
-
-      if (plugin_call (c->plugins, OPENVPN_PLUGIN_ENABLE_PF, NULL, NULL, c->c2.es) == OPENVPN_PLUGIN_FUNC_SUCCESS)
-	{
-	  event_timeout_init (&c->c2.pf.reload, 1, now);
-	  c->c2.pf.filename = string_alloc (pf_file, NULL);
-	  c->c2.pf.enabled = true;
+      if( pf_file ) {
+        setenv_str (c->c2.es, "pf_file", pf_file);
+
+        if (plugin_call (c->plugins, OPENVPN_PLUGIN_ENABLE_PF, NULL, NULL, c->c2.es) == OPENVPN_PLUGIN_FUNC_SUCCESS)
+          {
+            event_timeout_init (&c->c2.pf.reload, 1, now);
+            c->c2.pf.filename = string_alloc (pf_file, NULL);
+            c->c2.pf.enabled = true;
 #ifdef ENABLE_DEBUG
-	  if (check_debug_level (D_PF_DEBUG))
-	    pf_context_print (&c->c2.pf, "pf_init_context#1", D_PF_DEBUG);
+            if (check_debug_level (D_PF_DEBUG))
+              pf_context_print (&c->c2.pf, "pf_init_context#1", D_PF_DEBUG);
 #endif
-	}
-      else
-	{
-	  msg (M_WARN, "WARNING: OPENVPN_PLUGIN_ENABLE_PF disabled");
-	}
+          }
+        else
+          {
+            msg (M_WARN, "WARNING: OPENVPN_PLUGIN_ENABLE_PF disabled");
+          }
+      }
     }
 #endif
 #ifdef MANAGEMENT_PF
ssl.c +19 -13
index ddd5ee7..b467368 100644
@@ -1095,9 +1095,10 @@ key_state_gen_auth_control_file (struct key_state *ks, const struct tls_options
 
   key_state_rm_auth_control_file (ks);
   acf = create_temp_filename (opt->tmp_dir, "acf", &gc);
-  ks->auth_control_file = string_alloc (acf, NULL);
-  setenv_str (opt->es, "auth_control_file", ks->auth_control_file);
-
+  if( acf ) {
+    ks->auth_control_file = string_alloc (acf, NULL);
+    setenv_str (opt->es, "auth_control_file", ks->auth_control_file);
+  } /* FIXME: Should have better error handling? */
   gc_free (&gc);					  
 }
 
@@ -3145,16 +3146,21 @@ verify_user_pass_script (struct tls_session *session, const struct user_pass *up
 	  struct status_output *so;
 
 	  tmp_file = create_temp_filename (session->opt->tmp_dir, "up", &gc);
-	  so = status_open (tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE);
-	  status_printf (so, "%s", up->username);
-	  status_printf (so, "%s", up->password);
-	  if (!status_close (so))
-	    {
-	      msg (D_TLS_ERRORS, "TLS Auth Error: could not write username/password to file: %s",
-		   tmp_file);
-	      goto done;
-	    }
-	}
+          if( tmp_file ) {
+            so = status_open (tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE);
+            status_printf (so, "%s", up->username);
+            status_printf (so, "%s", up->password);
+            if (!status_close (so))
+              {
+                msg (D_TLS_ERRORS, "TLS Auth Error: could not write username/password to file: %s",
+                     tmp_file);
+                goto done;
+              }
+          } else {
+            msg (D_TLS_ERRORS, "TLS Auth Error: could not create write "
+                 "username/password to temp file");
+          }
+        }
       else
 	{
 	  setenv_str (session->opt->es, "username", up->username);
Signature · 1 line
1.6.6.1
#1 lore raw

Hi David,

Quoted 2 lines · David Sommerseth

As promised in the meeting today, a patch for hardening create_temp_filename().

Great! :)

Quoted 21 lines

I've added more checks to what create_temp_filename() returns where it is called in addition, to make it even safer.

misc.c +12 -0
+  do {
     uint8_t rndbytes[16];
     const char *rndstr;
 
+    attempts++;
+    mutex_lock_static (L_CREATE_TEMP);
+    ++counter;
+    mutex_unlock_static (L_CREATE_TEMP);
+
     prng_bytes (rndbytes, sizeof (rndbytes));
     rndstr = format_hex_ex (rndbytes, sizeof (rndbytes), 40, 0, NULL, gc);
     buf_printf (&fname, PACKAGE "_%s_%s.tmp", prefix, rndstr);
+
+    retfname = gen_path (directory, BSTR (&fname), gc);
+    if( !retfname ) {
+      msg (M_FATAL, "Failed to create temporary filename and path");
+      return NULL;
+    }

misc.c create_temp_filename (const char *directory, const char *prefix, struct gc_arena · line 1203

Quoted 10 lines
misc.c +10 -0
+  } while ( test_file (retfname) && (attempts < 6) );
+
+  if( attempts > 5 ) {
+    msg (M_FATAL, "Failed to create temporary file after %i attempts", attempts);
+    return NULL;
+  }
+
+  /* Create the tempfile to avoid a race condition */
+  fd = creat (retfname, S_IRUSR | S_IWUSR);
+  if( fd == -1 ) {

You'll probably want to add O_EXCL to creat()'s mode flags:

| O_EXCL Ensure that this call creates the file: if this flag is
speci‐
| fied in conjunction with O_CREAT, and pathname already
exists,
| then open() will fail. The behavior of O_EXCL is
undefined if
| O_CREAT is not specified.

Otherwise, there would be a race-condition between test_file() and creat() and someone could create a symlink under you feet.

And as the important "does it exist?" test thereby moves to creat(), I would like to suggest that you move the creat() call into the loop. The
exit condition would then be "successful creat()" or "max attempts".

Cheers
Fabian

PS: Is there some kind of code style guide for OpenVPN? I notice that
you're using a different style than I expected to see...

#2 lore raw

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Quoted 50 lines · Fabian Knittel

Hi David,

Quoted 2 lines · David Sommerseth

As promised in the meeting today, a patch for hardening create_temp_filename().

Great! :)

Quoted 21 lines

I've added more checks to what create_temp_filename() returns where it is called in addition, to make it even safer.

misc.c +12 -0
+  do {
     uint8_t rndbytes[16];
     const char *rndstr;
 
+    attempts++;
+    mutex_lock_static (L_CREATE_TEMP);
+    ++counter;
+    mutex_unlock_static (L_CREATE_TEMP);
+
     prng_bytes (rndbytes, sizeof (rndbytes));
     rndstr = format_hex_ex (rndbytes, sizeof (rndbytes), 40, 0, NULL, gc);
     buf_printf (&fname, PACKAGE "_%s_%s.tmp", prefix, rndstr);
+
+    retfname = gen_path (directory, BSTR (&fname), gc);
+    if( !retfname ) {
+      msg (M_FATAL, "Failed to create temporary filename and path");
+      return NULL;
+    }

misc.c create_temp_filename (const char *directory, const char *prefix, struct gc_arena · line 1203

Quoted 10 lines
misc.c +10 -0
+  } while ( test_file (retfname) && (attempts < 6) );
+
+  if( attempts > 5 ) {
+    msg (M_FATAL, "Failed to create temporary file after %i attempts", attempts);
+    return NULL;
+  }
+
+  /* Create the tempfile to avoid a race condition */
+  fd = creat (retfname, S_IRUSR | S_IWUSR);
+  if( fd == -1 ) {

You'll probably want to add O_EXCL to creat()'s mode flags:

| O_EXCL Ensure that this call creates the file: if this flag is
speci‐
| fied in conjunction with O_CREAT, and pathname already
exists,
| then open() will fail. The behavior of O_EXCL is
undefined if
| O_CREAT is not specified.

Quoted 2 lines

Otherwise, there would be a race-condition between test_file() and creat() and someone could create a symlink under you feet.

I honestly doubt that O_EXCL would help much, as the file will be closed immediately.

Quoted 3 lines

And as the important "does it exist?" test thereby moves to creat(), I would like to suggest that you move the creat() call into the loop. The
exit condition would then be "successful creat()" or "max attempts".

I agree that there is a tiny theoretical possibility for this to happen.
The only possible place where you can manage this, is to create a
symlink is after the test_file() in the while() loop and before the the creat(), in between there are only two integer checks (attempts > 6 and attempts < 5).

We're basically talking about a handful set of CPU cycles. And in these cycles, a malicious process needs to access the memory region of OpenVPN where the newly decided temp filename is stored and to create a symlink just before creat() is called. Of course this is more doable if you're able to preempt the OpenVPN process just before creat().

But if you have a malicious process being able to preempt OpenVPN or even read the memory regions of OpenVPN, I'd say you have a much bigger problem than this code section in OpenVPN. Especially if you have configured OpenVPN properly, meaning that the temp directory it uses is not world writable. By default, OpenVPN do not use /tmp for that, AFAIK.

I think "fixing" this is to be overly paranoid, as you will only narrow the whole "attack scope" by a few CPU cycles and you will still not remove the "attack scope". It is still possible to manage that even if you're inside the while() loop, it's just two integer comparisons being skipped.

But please render me wrong, if you think I'm overseeing something here!

On the other hand, if such a change renders the code more readable, that's another question - but then that should be the argument.

Quoted 2 lines

PS: Is there some kind of code style guide for OpenVPN? I notice that
you're using a different style than I expected to see...

Uhh ... I am trying to follow the existing style as best as I can ... but I see I've "failed" on parentheses around the 'if' statements. Or was it something else you noticed? Could maybe be tab vs space?

kind regards,

David Sommerseth
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org/

iEYEARECAAYFAkvIJNEACgkQDC186MBRfroycwCfQqmYQnSjuAa4Io4toU9e8zOp GGYAoIj3VOCz/t0BKG285JMQtmdNZoN/
=7Hq1
-----END PGP SIGNATURE-----

#3 lore raw

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Quoted 51 lines · David Sommerseth
Quoted 49 lines · Fabian Knittel

Hi David,

Quoted 2 lines · David Sommerseth

As promised in the meeting today, a patch for hardening create_temp_filename().

Great! :)

Quoted 21 lines

I've added more checks to what create_temp_filename() returns where it is called in addition, to make it even safer.

misc.c +12 -0
+  do {
     uint8_t rndbytes[16];
     const char *rndstr;
 
+    attempts++;
+    mutex_lock_static (L_CREATE_TEMP);
+    ++counter;
+    mutex_unlock_static (L_CREATE_TEMP);
+
     prng_bytes (rndbytes, sizeof (rndbytes));
     rndstr = format_hex_ex (rndbytes, sizeof (rndbytes), 40, 0, NULL, gc);
     buf_printf (&fname, PACKAGE "_%s_%s.tmp", prefix, rndstr);
+
+    retfname = gen_path (directory, BSTR (&fname), gc);
+    if( !retfname ) {
+      msg (M_FATAL, "Failed to create temporary filename and path");
+      return NULL;
+    }

misc.c create_temp_filename (const char *directory, const char *prefix, struct gc_arena · line 1203

Quoted 10 lines
misc.c +10 -0
+  } while ( test_file (retfname) && (attempts < 6) );
+
+  if( attempts > 5 ) {
+    msg (M_FATAL, "Failed to create temporary file after %i attempts", attempts);
+    return NULL;
+  }
+
+  /* Create the tempfile to avoid a race condition */
+  fd = creat (retfname, S_IRUSR | S_IWUSR);
+  if( fd == -1 ) {

You'll probably want to add O_EXCL to creat()'s mode flags:

| O_EXCL Ensure that this call creates the file: if this flag is
speci
| fied in conjunction with O_CREAT, and pathname already
exists,
| then open() will fail. The behavior of O_EXCL is
undefined if
| O_CREAT is not specified.

Quoted 5 lines
Quoted 2 lines

Otherwise, there would be a race-condition between test_file() and creat() and someone could create a symlink under you feet.

I honestly doubt that O_EXCL would help much, as the file will be closed immediately.

Actually, you might be right! This might be useful. But when checking up the man pages for creat() and open(), I do get a bit confused about the Linux implementation. To explain this a little bit further, the man page on Linux says:

	"O_EXCL is only supported on NFS when using NFSv3 or later on
	 kernel 2.6 or later."[1]

But comparing against other (older) Linux man pages, it says:

	"O_EXCL is broken on NFS filesystems"[2]
	(== will not work when being used on NFS)

So this is a bit confusing - are they _only_ talking about NFS or not? For SunOS[3], Solaris[4] and FreeBSD[5], it behaves as you do expect.

Further, to use O_EXCL you need to use open() instead, as creat() is on Linux open() with the O_CREAT|O_WRONLY|O_TRUNC flags set.

I'll look more into this, as the only advantage is that if open() with O_EXCL|O_CREAT fails if the file exists, it should be used instead.

kind regards,

David Sommerseth

[1] <http://www.kernel.org/doc/man-pages/online/pages/man2/open.2.html>
[2] <http://www.cl.cam.ac.uk/cgi-bin/manpage?2+creat>
[3] <http://wwwcgi.rdg.ac.uk:8081/cgi-bin/cgiwrap/wsi14/poplog/man/2/open>
[4]
<http://www.scribd.com/doc/4001670/Solaris-10-Reference-Manual-Collection-Man-Pages-Section-2-System-Calls>
[5] <http://www.manpages.info/freebsd/open.2.html>
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org/

iEYEARECAAYFAkvIKu0ACgkQDC186MBRfrrUpQCgl5PTn7a5B8TUgCEIk9wz5OU1 SPIAoKGdFpgeesbttaq/iI5FZykbnSs0
=uOsr
-----END PGP SIGNATURE-----

#4 lore raw

Hi,

Quoted 2 lines · David Sommerseth

I'll look more into this, as the only advantage is that if open() with O_EXCL|O_CREAT fails if the file exists, it should be used instead.

Unfortunately, this won't help against symlink attacks directed to non-existant files (like "-> /etc/nologin").

It *will* protect against symlink attacks to existing files (overwriting /etc/passwd or something similarily nasty).

gert

Signature · 4 lines
USENET is *not* the non-clickable part of WWW!
                                                           //www.muc.de/~gert/
Gert Doering - Munich, Germany                             [email protected]...
fax: +49-89-35655025                        [email protected]...
#5 lore raw
Quoted 9 lines · On Friday 16 Apr 2010 10:35:54 Gert Doering wrote:
Quoted 2 lines · David Sommerseth

I'll look more into this, as the only advantage is that if open() with O_EXCL|O_CREAT fails if the file exists, it should be used instead.

Unfortunately, this won't help against symlink attacks directed to non-existant files (like "-> /etc/nologin").

It *will* protect against symlink attacks to existing files (overwriting /etc/passwd or something similarily nasty).

"If O_EXCL and O_CREAT are set, and path names a symbolic link, open() shall fail and set errno to [EEXIST], regardless of the contents of the symbolic link."

Seems to me that it will fail in any case (or at least it should). I may be misunderstanding what you mean though.

Signature · 1 line
D.
#7 lore raw
In reply to Gert Doering

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Quoted 8 lines · Gert Doering

Hi,

Quoted 2 lines · David Sommerseth

I'll look more into this, as the only advantage is that if open() with O_EXCL|O_CREAT fails if the file exists, it should be used instead.

Unfortunately, this won't help against symlink attacks directed to non-existant files (like "-> /etc/nologin").

That's right, this could create a local DoS. I'm going to have a more careful look at test_file() afterwards. Considering to make it use stat() instead of just trying to open the file for reading.

Quoted 3 lines

It *will* protect against symlink attacks to existing files (overwriting /etc/passwd or something similarily nasty).

Agreed! And that's why I've decided to rewrite the patch to use open() with O_EXCL. With an improved test_file() function, this potential bug should be closed.

I've dived into the kernel code to see what it *really* does (when the man page are so unclear), and it should behave as those other Unices does as well. So, O_EXCL do make sense to avoid overwriting existing files if it is a symlink to an existing file.

Btw ... When diving into the kernel code, I stumbled upon this comment in fs/namei.c:1872:

	/* Does someone understand code flow here? Or it is only
	 * me so stupid? Anathema to whoever designed this non-sense
	 * with "intent.open".
	 */

Thought that one was worth sharing ;-) Having that said, this part of the kernel code is not too easy to follow.

Kind regards,

David Sommerseth
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org/

iEYEARECAAYFAkvIOFoACgkQDC186MBRfrr2UgCgrunMn9SRRTnlMB6606oTQgAd 07UAoJZmr8xAX7KKb8ERKW30X1Nuo8ZW
=KmQ+
-----END PGP SIGNATURE-----

#6 lore raw
In reply to Davide Brini

Hi,

Quoted 6 lines · Davide Brini
Quoted 2 lines

Unfortunately, this won't help against symlink attacks directed to non-existant files (like "-> /etc/nologin").

"If O_EXCL and O_CREAT are set, and path names a symbolic link, open() shall fail and set errno to [EEXIST], regardless of the contents of the symbolic link."

Oh? One learns something new every day :-) - just checked with the FreeBSD manpage, and it's even more explicit:

If O_EXCL is set and
the last component of the pathname is a symbolic link, open() will fail even if the symbolic link points to a non-existent name.

- so please disregard my comment. Everything is fine.

gert

Signature · 4 lines
USENET is *not* the non-clickable part of WWW!
                                                           //www.muc.de/~gert/
Gert Doering - Munich, Germany                             [email protected]...
fax: +49-89-35655025                        [email protected]...
#8 lore raw
In reply to David Sommerseth

Hi David,

Quoted 4 lines · David Sommerseth
Quoted 3 lines · Gert Doering

Hi,

On Fri, Apr 16, 2010 at 11:16:32AM +0200, David Sommerseth wrote:

Quoted 8 lines
Quoted 4 lines
Quoted 2 lines

I'll look more into this, as the only advantage is that if open() with O_EXCL|O_CREAT fails if the file exists, it should be used instead.

Unfortunately, this won't help against symlink attacks directed to non-existant files (like "-> /etc/nologin").

That's right, this could create a local DoS. I'm going to have a more careful look at test_file() afterwards. Considering to make it use stat() instead of just trying to open the file for reading.

As Davide already pointed out, the problem is taken care of by open(). So ... there should be no need for additional checking after a successful open() with O_CREAT|O_EXCL.

(BTW, I thought creat() took a flags parameter, but it only takes a mode param. My mistake. So you're correct in wanting to use open() instead of creat().)

To let the code speak, here's my suggestion (not compile-tested):

Quoted 38 lines

do

  {
    uint8_t rndbytes[16];
    const char *rndstr;

    ++attempts;
    mutex_lock_static (L_CREATE_TEMP);
    ++counter;
    mutex_unlock_static (L_CREATE_TEMP);

    prng_bytes (rndbytes, sizeof rndbytes);
    rndstr = format_hex_ex (rndbytes, sizeof rndbytes, 40, 0, NULL, gc);
    buf_printf (&fname, PACKAGE "_%s_%s.tmp", prefix, rndstr);

    retfname = gen_path (directory, BSTR (&fname), gc);
    if (!retfname)
      {
        msg (M_FATAL, "Failed to create temporary filename and path");
        return NULL;
      }

    /* Atomically create the file.  Errors out if the file already
       exists.  */
    fd = open (retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
    if (fd != -1)
      {
        close (fd);
        return retfname;
      }
    else if (fd == -1 && errno != EEXIST)
      {
        /* Something else went wrong, no need to retry.  */
        struct gc_arena gcerr = gc_new ();
        msg (M_FATAL, "Could not create temporary file '%s': %s",
             retfname, strerror_ts (errno, &gcerr));
        gc_free (&gcerr);
        return NULL;
      }
Quoted 5 lines

}
while (attempts < 6);

msg (M_FATAL, "Failed to create temporary file after %i attempts", attempts); return NULL;

(Regarding the code style: The above uses what I had thought would be the current style, but as I've only read parts of the OpenVPN code, I'm likely to be wrong. Are there any code-parts / newer code-parts which should be considered reference material?)

Quoted 4 lines

I've dived into the kernel code to see what it *really* does (when the man page are so unclear), and it should behave as those other Unices does as well. So, O_EXCL do make sense to avoid overwriting existing files if it is a symlink to an existing file.

IMHO the Linux man-page ist quite clear (I'm using version 3.24):

Quoted 9 lines

O_EXCL Ensure that this call creates the file: if this flag is speci‐
fied in conjunction with O_CREAT, and pathname already exists, then open() will fail. The behavior of O_EXCL is undefined if O_CREAT is not specified.

When these two flags are specified, symbolic links are not fol‐
lowed: if pathname is a symbolic link, then open() fails regard‐ less of where the symbolic link points to.

Quoted 11 lines

O_EXCL is only supported on NFS when using NFSv3 or later on kernel 2.6 or later. In environments where NFS O_EXCL support is not provided, programs that rely on it for performing locking
tasks will contain a race condition. Portable programs that
want to perform atomic file locking using a lockfile, and need to avoid reliance on NFS support for O_EXCL, can create a unique file on the same file system (e.g., incorporating hostname and
PID), and use link(2) to make a link to the lockfile. If
link(2) returns 0, the lock is successful. Otherwise, use stat(2) on the unique file to check if its link count has increased to 2, in which case the lock is also successful.

Using open with O_CREAT|O_EXCL:
- makes the atomic guarantee, that the file was created by us (or the function call fails)
- does not follow symlinks (if a symlink exists, it fails)
- works over NFS for NFS implementations that support O_EXCL. (This should apply to all current Linux NFS implementations.)

So, anything remaining that you'd like cleared up?

Quoted 9 lines

Btw ... When diving into the kernel code, I stumbled upon this comment in fs/namei.c:1872:

	/* Does someone understand code flow here? Or it is only
	 * me so stupid? Anathema to whoever designed this non-sense
	 * with "intent.open".
	 */

Thought that one was worth sharing ;-)

:-D

Cheers
Fabian

#9 lore raw

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Quoted 52 lines · Fabian Knittel

Hi David,

David Sommerseth wrote:
(BTW, I thought creat() took a flags parameter, but it only takes a mode param. My mistake. So you're correct in wanting to use open() instead of creat().)

To let the code speak, here's my suggestion (not compile-tested):

Quoted 43 lines

do

  {
    uint8_t rndbytes[16];
    const char *rndstr;

    ++attempts;
    mutex_lock_static (L_CREATE_TEMP);
    ++counter;
    mutex_unlock_static (L_CREATE_TEMP);

    prng_bytes (rndbytes, sizeof rndbytes);
    rndstr = format_hex_ex (rndbytes, sizeof rndbytes, 40, 0, NULL, gc);
    buf_printf (&fname, PACKAGE "_%s_%s.tmp", prefix, rndstr);

    retfname = gen_path (directory, BSTR (&fname), gc);
    if (!retfname)
      {
        msg (M_FATAL, "Failed to create temporary filename and path");
        return NULL;
      }

    /* Atomically create the file.  Errors out if the file already
       exists.  */
    fd = open (retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
    if (fd != -1)
      {
        close (fd);
        return retfname;
      }
    else if (fd == -1 && errno != EEXIST)
      {
        /* Something else went wrong, no need to retry.  */
        struct gc_arena gcerr = gc_new ();
        msg (M_FATAL, "Could not create temporary file '%s': %s",
             retfname, strerror_ts (errno, &gcerr));
        gc_free (&gcerr);
        return NULL;
      }

}
while (attempts < 6);

msg (M_FATAL, "Failed to create temporary file after %i attempts", attempts); return NULL;

I like it! It looks cleaner! Thanks for standing the argument ;-) I'll look at a new patch, hopefully today.

Quoted 4 lines

(Regarding the code style: The above uses what I had thought would be the current style, but as I've only read parts of the OpenVPN code, I'm likely to be wrong. Are there any code-parts / newer code-parts which should be considered reference material?)

I don't think so. You might find some few differences throughout the code, but the coding style is not documented anywhere, to my knowledge.

Quoted 5 lines
Quoted 4 lines

I've dived into the kernel code to see what it *really* does (when the man page are so unclear), and it should behave as those other Unices does as well. So, O_EXCL do make sense to avoid overwriting existing files if it is a symlink to an existing file.

Quoted 13 lines

IMHO the Linux man-page ist quite clear (I'm using version 3.24):

Quoted 11 lines

O_EXCL Ensure that this call creates the file: if this flag is speci‐
fied in conjunction with O_CREAT, and pathname already exists, then open() will fail. The behavior of O_EXCL is undefined if O_CREAT is not specified.

When these two flags are specified, symbolic links are not fol‐
lowed: if pathname is a symbolic link, then open() fails regard‐ less of where the symbolic link points to.

O_EXCL is only supported on NFS when using NFSv3 or later on kernel 2.6 or later.

This sentence is the one which made me uncertain. Because here it restricts things more to NFS. I've already suggested a patch to the man-pages maintainer clarifying this paragraph.

Quoted 11 lines
Quoted 10 lines

In environments where NFS O_EXCL support
is not provided, programs that rely on it for performing locking
tasks will contain a race condition. Portable programs that
want to perform atomic file locking using a lockfile, and need to avoid reliance on NFS support for O_EXCL, can create a unique file on the same file system (e.g., incorporating hostname and
PID), and use link(2) to make a link to the lockfile. If
link(2) returns 0, the lock is successful. Otherwise, use stat(2) on the unique file to check if its link count has increased to 2, in which case the lock is also successful.

Quoted 8 lines

Using open with O_CREAT|O_EXCL:
- makes the atomic guarantee, that the file was created by us (or the function call fails)
- does not follow symlinks (if a symlink exists, it fails)
- works over NFS for NFS implementations that support O_EXCL. (This should apply to all current Linux NFS implementations.)

So, anything remaining that you'd like cleared up?

Nope :)

kind regards,

David Sommerseth
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org/

iEUEARECAAYFAkvIrLUACgkQDC186MBRfrroVwCfWA0XoZHtQ/oTWC0pSTIL1qum 54gAmMH1al5cXbaEafUS6PMkqnmMm2U=
=E8fx
-----END PGP SIGNATURE-----