View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0000576 | LDMud 3.3 | Implementation | public | 2008-09-24 15:21 | 2018-01-29 21:57 |
Reporter | zesstra | Assigned To | zesstra | ||
Priority | high | Severity | crash | Reproducibility | always |
Status | resolved | Resolution | fixed | ||
Product Version | 3.3 | ||||
Fixed in Version | 3.3.718 | ||||
Summary | 0000576: save_object() and restore_object() may crash with large argument strings | ||||
Description | save_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 Reproduce | save_object("a"*8000000); and mixed m; void test() {restore_object("a"*8000000);} | ||||
Tags | No 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() */ /*-------------------------------------------------------------------------*/ | ||||
child of | 0000545 | new | Usages of alloca() have to be checked for possible stack overflow |
|
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. |
|
Should be fixed by r2435. |
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 |