[Openvpn-devel,4/4,v3] create_temp_file/gen_path: prevent memory leak if gc == NULL

Message ID 20171101220342.14648-5-steffan@karger.me
State Accepted
Headers show
Series [Openvpn-devel,1/4,v3] pf: clean up temporary files if plugin init fails | expand

Commit Message

Steffan Karger Nov. 1, 2017, 11:03 a.m. UTC
If gc == NULL, the data allocated in the alloc_gc_buf() call in
create_temp_file or the string_mod_const call in gen_path would never
be free'd.

These functions are currently never called that way, but let's prevent
future problems.

While touching create_temp_file, also remove the counter variable, which is
never read, simplify the do-while to a while loop, and truncate the prefix
(if needed) to preserve the random and extension of the created filename.

Signed-off-by: Steffan Karger <steffan@karger.me>
---
v2:
 - change create_temp_file to avoid using a struct buffer (simpler)
 - add gc != NULL check for gen_path (avoid similar memleak pitfall)
v3:
 - Check the return value of openvpn_snprintf()

 src/openvpn/misc.c | 26 ++++++++++++++++++--------
 1 file changed, 18 insertions(+), 8 deletions(-)

Comments

Antonio Quartulli Nov. 8, 2017, 4:25 p.m. UTC | #1
Hi Steffan,

patch looks good, but I have just one comment below

On 02/11/17 06:03, Steffan Karger wrote:
> If gc == NULL, the data allocated in the alloc_gc_buf() call in
> create_temp_file or the string_mod_const call in gen_path would never
> be free'd.
> 
> These functions are currently never called that way, but let's prevent
> future problems.
> 
> While touching create_temp_file, also remove the counter variable, which is
> never read, simplify the do-while to a while loop, and truncate the prefix
> (if needed) to preserve the random and extension of the created filename.
> 
> Signed-off-by: Steffan Karger <steffan@karger.me>
> ---
> v2:
>  - change create_temp_file to avoid using a struct buffer (simpler)
>  - add gc != NULL check for gen_path (avoid similar memleak pitfall)
> v3:
>  - Check the return value of openvpn_snprintf()
> 
>  src/openvpn/misc.c | 26 ++++++++++++++++++--------
>  1 file changed, 18 insertions(+), 8 deletions(-)
> 
> diff --git a/src/openvpn/misc.c b/src/openvpn/misc.c
> index 25f38003..67011169 100644
> --- a/src/openvpn/misc.c
> +++ b/src/openvpn/misc.c
> @@ -723,21 +723,26 @@ test_file(const char *filename)
>  const char *
>  create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc)
>  {
> -    static unsigned int counter;
> -    struct buffer fname = alloc_buf_gc(256, gc);
>      int fd;
>      const char *retfname = NULL;
>      unsigned int attempts = 0;
> +    char fname[256] = { 0 };
> +    const char *fname_fmt = PACKAGE "_%.*s_%08lx%08lx.tmp";
> +    const int max_prefix_len = sizeof(fname) - (sizeof(PACKAGE) + 7 + (2 * 8));
>  
> -    do
> +    while (attempts < 6)
>      {
>          ++attempts;
> -        ++counter;
>  
> -        buf_printf(&fname, PACKAGE "_%s_%08lx%08lx.tmp", prefix,
> -                   (unsigned long) get_random(), (unsigned long) get_random());
> +        if (!openvpn_snprintf(fname, sizeof(fname), fname_fmt, max_prefix_len,
> +                              prefix, (unsigned long) get_random(),
> +                              (unsigned long) get_random()))
> +        {
> +            msg(M_WARN, "ERROR: temporary filename too long");
> +            return NULL;
> +        }
>  
> -        retfname = gen_path(directory, BSTR(&fname), gc);
> +        retfname = gen_path(directory, fname, gc);
>          if (!retfname)
>          {
>              msg(M_WARN, "Failed to create temporary filename and path");
> @@ -760,7 +765,6 @@ create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc)
>              return NULL;
>          }
>      }
> -    while (attempts < 6);
>  
>      msg(M_WARN, "Failed to create temporary file after %i attempts", attempts);
>      return NULL;
> @@ -812,6 +816,12 @@ gen_path(const char *directory, const char *filename, struct gc_arena *gc)
>  #else
>      const int CC_PATH_RESERVED = CC_SLASH;
>  #endif
> +
> +    if (!gc)
> +    {
> +        return NULL; /* Would leak memory otherwise */

As far as I understood the ratio behind this change, we are basically
saying that passing gc == NULL is a real programming error and not a
runtime problem. If so, wouldn't it be better to assert(gc) directly?
I am saying so because we should make this failure louder, so that it
can be recognized in an early test.

Or can we have cases when this function is called with NULL because of a
temporary failure (my understanding is that this should not be possible)?


Cheers,

> +    }
> +
>      const char *safe_filename = string_mod_const(filename, CC_PRINT, CC_PATH_RESERVED, '_', gc);
>  
>      if (safe_filename
>
Steffan Karger Nov. 9, 2017, 2:46 a.m. UTC | #2
Hi,

On 09-11-17 04:25, Antonio Quartulli wrote:
>> @@ -812,6 +816,12 @@ gen_path(const char *directory, const char *filename, struct gc_arena *gc)
>>  #else
>>      const int CC_PATH_RESERVED = CC_SLASH;
>>  #endif
>> +
>> +    if (!gc)
>> +    {
>> +        return NULL; /* Would leak memory otherwise */
> 
> As far as I understood the ratio behind this change, we are basically
> saying that passing gc == NULL is a real programming error and not a
> runtime problem. If so, wouldn't it be better to assert(gc) directly?
> I am saying so because we should make this failure louder, so that it
> can be recognized in an early test.
> 
> Or can we have cases when this function is called with NULL because of a
> temporary failure (my understanding is that this should not be possible)?

I'm a bit in doubt here - if this parameter is NULL, that's a
programming error.  But we've seen before that programming errors
sometimes can be triggered by external input.  In this case, this
function can be called when a client connects.  It *should* never hit
this case, but if it somehow does, that becomes a DoS vuln.  So I think
I prefer 'graceful error handling' in paths that are exposed like this,
unless the error indicates a system integrity error such as an
out-of-bounds read or write.

-Steffan
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
Antonio Quartulli Nov. 9, 2017, 3:14 a.m. UTC | #3
On 09/11/17 21:46, Steffan Karger wrote:
> Hi,
> 
> On 09-11-17 04:25, Antonio Quartulli wrote:
>>> @@ -812,6 +816,12 @@ gen_path(const char *directory, const char *filename, struct gc_arena *gc)
>>>  #else
>>>      const int CC_PATH_RESERVED = CC_SLASH;
>>>  #endif
>>> +
>>> +    if (!gc)
>>> +    {
>>> +        return NULL; /* Would leak memory otherwise */
>>
>> As far as I understood the ratio behind this change, we are basically
>> saying that passing gc == NULL is a real programming error and not a
>> runtime problem. If so, wouldn't it be better to assert(gc) directly?
>> I am saying so because we should make this failure louder, so that it
>> can be recognized in an early test.
>>
>> Or can we have cases when this function is called with NULL because of a
>> temporary failure (my understanding is that this should not be possible)?
> 
> I'm a bit in doubt here - if this parameter is NULL, that's a
> programming error.  But we've seen before that programming errors
> sometimes can be triggered by external input.  In this case, this
> function can be called when a client connects.  It *should* never hit
> this case, but if it somehow does, that becomes a DoS vuln.  So I think
> I prefer 'graceful error handling' in paths that are exposed like this,
> unless the error indicates a system integrity error such as an
> out-of-bounds read or write.

Yeah, makes sense. I was also worried about the DoS, that's why I was
trying to convince myself that passing NULL was not possible at all. But
I like your defensive approach more.

Acked-by: Antonio Quartulli <a@unstable.cc>
Gert Doering Nov. 24, 2017, 1:42 a.m. UTC | #4
Your patch has been applied to the master branch.

commit bd89ebd6a82856c7939b4ade35d14d0178a96986
Author: Steffan Karger
Date:   Wed Nov 1 23:03:42 2017 +0100

     create_temp_file/gen_path: prevent memory leak if gc == NULL

     Signed-off-by: Steffan Karger <steffan@karger.me>
     Acked-by: Antonio Quartulli <antonio@openvpn.net>
     Message-Id: <20171101220342.14648-5-steffan@karger.me>
     URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg15703.html
     Signed-off-by: Gert Doering <gert@greenie.muc.de>


--
kind regards,

Gert Doering


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

Patch

diff --git a/src/openvpn/misc.c b/src/openvpn/misc.c
index 25f38003..67011169 100644
--- a/src/openvpn/misc.c
+++ b/src/openvpn/misc.c
@@ -723,21 +723,26 @@  test_file(const char *filename)
 const char *
 create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc)
 {
-    static unsigned int counter;
-    struct buffer fname = alloc_buf_gc(256, gc);
     int fd;
     const char *retfname = NULL;
     unsigned int attempts = 0;
+    char fname[256] = { 0 };
+    const char *fname_fmt = PACKAGE "_%.*s_%08lx%08lx.tmp";
+    const int max_prefix_len = sizeof(fname) - (sizeof(PACKAGE) + 7 + (2 * 8));
 
-    do
+    while (attempts < 6)
     {
         ++attempts;
-        ++counter;
 
-        buf_printf(&fname, PACKAGE "_%s_%08lx%08lx.tmp", prefix,
-                   (unsigned long) get_random(), (unsigned long) get_random());
+        if (!openvpn_snprintf(fname, sizeof(fname), fname_fmt, max_prefix_len,
+                              prefix, (unsigned long) get_random(),
+                              (unsigned long) get_random()))
+        {
+            msg(M_WARN, "ERROR: temporary filename too long");
+            return NULL;
+        }
 
-        retfname = gen_path(directory, BSTR(&fname), gc);
+        retfname = gen_path(directory, fname, gc);
         if (!retfname)
         {
             msg(M_WARN, "Failed to create temporary filename and path");
@@ -760,7 +765,6 @@  create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc)
             return NULL;
         }
     }
-    while (attempts < 6);
 
     msg(M_WARN, "Failed to create temporary file after %i attempts", attempts);
     return NULL;
@@ -812,6 +816,12 @@  gen_path(const char *directory, const char *filename, struct gc_arena *gc)
 #else
     const int CC_PATH_RESERVED = CC_SLASH;
 #endif
+
+    if (!gc)
+    {
+        return NULL; /* Would leak memory otherwise */
+    }
+
     const char *safe_filename = string_mod_const(filename, CC_PRINT, CC_PATH_RESERVED, '_', gc);
 
     if (safe_filename