View Issue Details

IDProjectCategoryView StatusLast Update
0000576LDMud 3.3Implementationpublic2018-01-29 21:57
Reporterzesstra Assigned Tozesstra  
PriorityhighSeveritycrashReproducibilityalways
Status resolvedResolutionfixed 
Product Version3.3 
Fixed in Version3.3.718 
Summary0000576: save_object() and restore_object() may crash with large argument strings
Descriptionsave_object() and restore_object() allocate a temporary buffer on the stack which has the size of the given string. Because stack memory is usually a very limited ressource and alloca() has no error handling (does not return NULL if the stack limit would be exceeded) but just moves the stack pointer, after the allocation either system libs or the heap might be overwritten or in the best case non-allocated memory areas are accessed, which will terminate the driver.
Steps To Reproducesave_object("a"*8000000);

and

mixed m;
void test() {restore_object("a"*8000000);}
TagsNo tags attached.
Attached Files
save_restore__object.diff (10,909 bytes)   
Index: object.c
===================================================================
--- object.c	(Revision 2426)
+++ object.c	(Arbeitskopie)
@@ -6599,7 +6599,8 @@
         save_version = sp->u.number >= 0 ? sp->u.number
                                          : CURRENT_VERSION;
 
-        /* The main code wants sp == filename */
+        /* The main code wants sp == filename (T_NUMBER svalues need no free.)
+         */
         sp--;
         numarg--;
         break;
@@ -6651,18 +6652,19 @@
 
 
         /* Create the final and the temporary filename */
-
         len = (long)mstrsize(sfile);
-        name = alloca(len + (sizeof save_file_suffix) +
-                      len + (sizeof save_file_suffix) + 4);
-
+        inter_sp = sp;
+        name = alloc_with_error_handler(len + (sizeof save_file_suffix) +
+                                        len + (sizeof save_file_suffix) + 4);
         if (!name)
         {
             free_mstring(sfile);
-            errorf("Stack overflow in save_object('%s')\n", get_txt(sp->u.str));
+            errorf("Out of memory (%ld bytes) in save_object('%s')\n", 
+                   2*len+2*sizeof(save_file_suffix)+4, get_txt(sp->u.str));
             /* NOTREACHED */
             return sp;
         }
+        sp = inter_sp;
 
         tmp_name = name + len + sizeof save_file_suffix;
         strcpy(name, get_txt(sfile));
@@ -6804,8 +6806,9 @@
             close(f);
             unlink(tmp_name);
             add_message("Failed to save to file '%s'. Disk could be full.\n", file);
-            if (numarg)
-                sp = pop_n_elems(numarg, sp);
+            /* free the error handler and the arguments (numarg + 1  from sp).
+             */
+            sp = pop_n_elems(numarg + 1, sp);
             sp++;
             put_number(sp, 1);
             return sp;
@@ -6836,21 +6839,24 @@
         unlink(tmp_name);
 #endif
 
-        if (numarg)
-            sp = pop_n_elems(numarg, sp);
+        /* free the error handler and the arguments (numarg + 1  from sp) and
+         * push result on the stack.
+         */
+        sp = pop_n_elems(numarg + 1, sp);
         sp++;
         put_number(sp, i);
-    }
+    } /* if (file) */
     else
     {
         /* Finish up the operation. Note that there propably is some
          * data pending in the save_buffer.
          */
 
-        if (numarg)
-            sp = pop_n_elems(numarg, sp);
+        /* free the error handler and the arguments (numarg + 1  from sp).
+         */
+        sp = pop_n_elems(numarg + 1, sp);
+      
         sp++; /* for the result */
-
         if (failed)
             put_number(sp, 0); /* Shouldn't happen */
         else if (buf_left != SAVE_OBJECT_BUFSIZE)
@@ -8497,7 +8503,8 @@
     int      * pNesting;  /* The nesting counter */
     char     * buff;      /* The optional allocated line buffer. */
     FILE     * f;         /* The optional input file */
-    struct discarded * dp; 
+    struct discarded * dp;
+    char     * filename;  /* optional buffer for the filename */
       /* List of values for which the variables no longer exist. */
 } restore_cleanup_t;
 
@@ -8531,6 +8538,10 @@
     (*data->pNesting)--;
 
     free_shared_restored_values();
+  
+    if (data->filename)
+        xfree(data->filename);
+  
     xfree(arg);
 } /* restore_object_cleanup() */
 
@@ -8576,6 +8587,7 @@
     size_t len;
     FILE *f;
     struct stat st;  /* stat() info of the savefile */
+    svalue_t *arg;   /* pointer to the argument on the stack - for convenience */
 
     int var_rest;    /* Number of variables left after rover */
     int num_var;     /* Number of variables in the object */
@@ -8584,17 +8596,30 @@
        * to restore is searched from here, taking advantage of
        * the locality of save_object().
        */
-
     restore_cleanup_t * rcp;
       /* Cleanup structure */
-
-#define FREE_BUFF() MACRO( \
-          if (nesting > 1) xfree(buff); else mb_free(mbFile); \
-        )
-      /* Free buff correctly.
-       */
-
-
+    
+    arg = sp;
+    
+    /* Allocate memory for the error cleanup structure first. We need it 
+     * anway and if we can't allocate it, then this is anyway a lost cause. */
+    rcp = xalloc(sizeof(*rcp));
+    if (!rcp)
+    {
+      errorf("(restore) Out of memory: (%zu bytes) for cleanup structure\n"
+             , sizeof(*rcp));
+      /* NOTREACHED */
+      return sp;
+    }
+    rcp->pNesting = &nesting;
+    rcp->buff = NULL;
+    rcp->f = NULL;
+    rcp->dp = NULL;
+    rcp->filename = NULL;
+    /* Push it on top of the argument on the stack. */
+    push_error_handler(restore_object_cleanup, &(rcp->head));
+    sp = inter_sp;
+  
     /* Keep track of recursive calls */
     nesting++;
 
@@ -8605,17 +8630,18 @@
     ob = current_object;
     if (ob->flags & O_DESTRUCTED)
     {
-        free_svalue(sp);
+        sp = pop_n_elems(2, sp); /* pop and free error handler + argument */
+        sp++;
         put_number(sp, 0);
-        nesting--;
         return sp;
     }
 
+    /* no need to restore objects without variables */
     if (ob->prog->num_variables == 0)
     {
-        free_svalue(sp);
+        sp = pop_n_elems(2, sp); /* pop and free error handler + argument */
+        sp++;
         put_number(sp, 1);
-        nesting--;
         return sp;
     }
 
@@ -8625,24 +8651,25 @@
     file = NULL;
     f = NULL;
     lineno = 0;
-    if (get_txt(sp->u.str)[0] == '#')
+    if (get_txt(arg->u.str)[0] == '#')
     {
         /* We need a copy of the value string because we're
          * going to modify it a bit.
          */
-        len = mstrsize(sp->u.str);
+        len = mstrsize(arg->u.str);
         buff = (nesting > 1) ? xalloc(len+1) : mb_alloc(mbFile, len+1);
         if (buff == NULL)
         {
-            nesting--;
             outofmem(len+1, "copy of value string");
         }
-        memcpy(buff, get_txt(sp->u.str), len);
+        /* keep track of buff in the cleanup structure. */
+        rcp->buff = buff;
+        memcpy(buff, get_txt(arg->u.str), len);
         buff[len] = '\0';
     }
     else
     {
-        file = get_txt(sp->u.str);
+        file = get_txt(arg->u.str);
     }
 
     /* If restoring from a file, set it up */
@@ -8653,29 +8680,25 @@
 
         /* Get a valid filename */
 
-        sfile = check_valid_path(sp->u.str, ob, STR_RESTORE_OBJECT, MY_FALSE);
+        sfile = check_valid_path(arg->u.str, ob, STR_RESTORE_OBJECT, MY_FALSE);
         if (sfile == NULL)
         {
-            nesting--;
-            errorf("Illegal use of restore_object('%s')\n", get_txt(sp->u.str));
+            errorf("Illegal use of restore_object('%s')\n", get_txt(arg->u.str));
             /* NOTREACHED */
             return sp;
         }
 
-
         /* Create the full filename */
-
         len = mstrsize(sfile);
-        name = alloca(len + (sizeof save_file_suffix));
-
+        name = xalloc(len + (sizeof save_file_suffix));
         if (!name)
         {
             free_mstring(sfile);
-            nesting--;
-            errorf("Stack overflow in restore_object('%s')\n", get_txt(sp->u.str));
+            errorf("Stack overflow in restore_object('%s')\n", get_txt(arg->u.str));
             /* NOTREACHED */
             return sp;
         }
+        rcp->filename = name;  /* in case of errrors -> cleanup structure */
 
         strcpy(name, get_txt(sfile));
         if (name[len-2] == '.' && name[len-1] == 'c')
@@ -8685,22 +8708,21 @@
         free_mstring(sfile);
 
         /* Open the file and gets its length */
-
         f = fopen(name, "r");
         if (!f || fstat(fileno(f), &st) == -1) {
             if (f)
                 fclose(f);
-            free_svalue(sp);
+            sp = pop_n_elems(2, sp); /* pop and free error handler + argument */
+            sp++;
             put_number(sp, 0);
-            nesting--;
             return sp;
         }
+        rcp->f = f;    /* keep track of f in case of errors */
         if (st.st_size == 0)
         {
-            fclose(f);
-            free_svalue(sp);
+            sp = pop_n_elems(2, sp); /* pop and free error handler + argument */
+            sp++;
             put_number(sp, 0);
-            nesting--;
             return sp;
         }
         FCOUNT_REST(name);
@@ -8708,13 +8730,10 @@
         /* Allocate the linebuffer. Unfortunately, the whole file
          * can be one single line.
          */
-
         buff = (nesting > 1) ? xalloc((size_t)(st.st_size + 1))
                              : mb_alloc(mbFile, (size_t)(st.st_size+1));
         if (!buff)
         {
-            fclose(f);
-            nesting--;
             /* TODO: st_size is off_t which is most often int64_t or int32_t.
              * TODO:: PRIdMAX or PRId64 should be used, I think. */
             errorf("(restore) Out of memory (%ld bytes) for linebuffer.\n"
@@ -8722,6 +8741,7 @@
             /* NOTREACHED */
             return sp;
         }
+        rcp->buff = buff;
     } /* if (file) */
 
     /* Initialise the variables */
@@ -8739,37 +8759,12 @@
 
     if (!shared_restored_values)
     {
-        if (f)
-            fclose(f);
-        FREE_BUFF();
-        nesting--;
         errorf("(restore) Out of memory (%lu bytes) for shared values.\n"
              , sizeof(svalue_t)*(unsigned long)max_shared_restored);
         /* NOTREACHED */
         return sp;
     }
-
-    /* Setup the error cleanup */
-    inter_sp = sp;
-    rcp = xalloc(sizeof(*rcp));
-    if (!rcp)
-    {
-        if (f)
-            fclose(f);
-        FREE_BUFF();
-        nesting--;
-        errorf("(restore) Out of memory: (%zu bytes) for cleanup structure\n"
-             , sizeof(*rcp));
-        /* NOTREACHED */
-        return sp;
-    }
-
-    rcp->pNesting = &nesting;
-    rcp->buff = buff;
-    rcp->f = f;
-    rcp->dp = NULL;
-    push_error_handler(restore_object_cleanup, &(rcp->head));
-
+  
     num_var = ob->prog->num_variables;
     var_rest = 0;
     restored_version = -1;
@@ -8829,7 +8824,8 @@
             }
 
             /* No version line: illegal format.
-             * Deallocate what we allocated so far and return.
+             * Most of the cleanup will be done by the error handler during
+             * stack unwinding.
              */
             if (file)
                 errorf("Illegal format (version line) when restoring %s "
@@ -8962,13 +8958,11 @@
                      , time_stamp(), get_txt(ob->name)
                      , file ? name : "passed value");
 
-    free_svalue(inter_sp--); /* calls the cleanup handler */
-
-    free_svalue(inter_sp);
-    put_number(inter_sp, 1);
+    free_svalue(sp--); /* calls the cleanup handler */
+    free_svalue(sp);  /* frees the argument */
+    put_number(sp, 1);
     return sp;
 
-#undef FREE_BUFF
 } /* f_restore_object() */
 
 /*-------------------------------------------------------------------------*/
save_restore__object.diff (10,909 bytes)   

Relationships

child of 0000545 new Usages of alloca() have to be checked for possible stack overflow 

Activities

zesstra

2008-09-24 15:31

administrator   ~0000793

Patch is attached. It depends on the errorhandler.diff attached to bug 0000575.
In restore_object() the error handling is slightly changed. Instead of keeping track of allocated ressources on every errorf() the error handler is pushed much earlier on the stack and ressources are referenced in the cleanup structure as soon as they are allocated. It is slightly less efficient but I think the code is more robust.

zesstra

2008-10-04 10:48

administrator   ~0000798

Should be fixed by r2435.

Issue History

Date Modified Username Field Change
2008-09-24 15:21 zesstra New Issue
2008-09-24 15:21 zesstra Status new => assigned
2008-09-24 15:21 zesstra Assigned To => zesstra
2008-09-24 15:23 zesstra Relationship added child of 0000545
2008-09-24 15:23 zesstra File Added: save_restore__object.diff
2008-09-24 15:31 zesstra Note Added: 0000793
2008-10-04 10:48 zesstra Status assigned => resolved
2008-10-04 10:48 zesstra Fixed in Version => 3.3.718
2008-10-04 10:48 zesstra Resolution open => fixed
2008-10-04 10:48 zesstra Note Added: 0000798
2010-11-16 09:42 zesstra Source_changeset_attached => ldmud.git master c9562f1d
2018-01-29 18:59 zesstra Source_changeset_attached => ldmud.git master c9562f1d
2018-01-29 21:57 zesstra Source_changeset_attached => ldmud.git master c9562f1d