Harden create_temp_filename()
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
@@ -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
@@ -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 *mimulti_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 *misetenv_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
@@ -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
@@ -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
@@ -1095,9 +1095,10 @@ key_state_gen_auth_control_file (struct key_state *ks, const struct tls_optionskey_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 *upstruct 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
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...
-----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_EXCLto creat()'s mode flags:|
O_EXCLEnsure that this call creates the file: if this flag is
speci‐
| fied in conjunction withO_CREAT, and pathname already
exists,
| thenopen()will fail. The behavior ofO_EXCLis
undefined if
|O_CREATis not specified.
Quoted 2 lines
Otherwise, there would be a race-condition between
test_file()andcreat()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 thecreat()call into the loop. The
exit condition would then be "successfulcreat()" 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-----
-----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_EXCLto creat()'s mode flags:|
O_EXCLEnsure that this call creates the file: if this flag is
speci
| fied in conjunction withO_CREAT, and pathname already
exists,
| thenopen()will fail. The behavior ofO_EXCLis
undefined if
|O_CREATis not specified.
Quoted 5 lines
Quoted 2 lines
Otherwise, there would be a race-condition between
test_file()andcreat()and someone could create a symlink under you feet.I honestly doubt that
O_EXCLwould 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-----
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]...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/passwdor 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.
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_EXCLandO_CREATare 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]...-----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/passwdor 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-----
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 usestat()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_EXCLdo 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_EXCLEnsure that this call creates the file: if this flag is speci‐
fied in conjunction withO_CREAT, and pathname already exists, thenopen()will fail. The behavior ofO_EXCLis undefined ifO_CREATis not specified.When these two flags are specified, symbolic links are not fol‐
lowed: if pathname is a symbolic link, thenopen()fails regard‐ less of where the symbolic link points to.
Quoted 11 lines
O_EXCLis only supported on NFS when using NFSv3 or later on kernel 2.6 or later. In environments where NFSO_EXCLsupport 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 forO_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
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Quoted 52 lines · Fabian Knittel
Hi David,
David Sommerseth wrote:
(BTW, I thoughtcreat()took a flags parameter, but it only takes a mode param. My mistake. So you're correct in wanting to useopen()instead ofcreat().)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_EXCLdo 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_EXCLEnsure that this call creates the file: if this flag is speci‐
fied in conjunction withO_CREAT, and pathname already exists, thenopen()will fail. The behavior ofO_EXCLis undefined ifO_CREATis not specified.When these two flags are specified, symbolic links are not fol‐
lowed: if pathname is a symbolic link, thenopen()fails regard‐ less of where the symbolic link points to.
O_EXCLis 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_EXCLsupport
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 forO_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 supportO_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-----