Re: safestr alpha (Safe C String Library)

From: Matt Messier (mmessierat_private)
Date: Wed Feb 12 2003 - 13:22:15 PST

  • Next message: Dr.Tek: "New Tool: Malloc() FWScrape"

    On Tuesday, February 11, 2003, at 09:54  PM, kevindat_private wrote:
    
    > On Tue, Feb 11, 2003 at 11:01:25AM -0800, Kim Reece wrote:
    >>
    >> Perhaps an acknowledgement system of some type?  ie. having 
    >> parameters for
    >> "die" or "don't die" or "die if they don't check it", then a function 
    >> the
    >> user of the library would call to say "return code checked, 
    >> acknowledge so
    >> I can use the results".  Basically a taint system.
    >
    > The Unix signal()/sigaction() mechanism provides some inspiration here,
    > where you have these possibilities with SIG_DFL, SIG_IGN and a handler.
    > A callback function for string function errors achieves much of the 
    > desired result.
    >
    > A caveat - I'd better admit that I haven't looked at the current 
    > proposed library,
    > so I don't even know its API.  I've just been following the discussion 
    > as it's happened here.
    > Please apply grains of salt accordingly.
    
    Fair enough.  I've been keeping pretty quiet, letting John field the 
    mails both privately and on the list, but at this point, it feels to me 
    like we're getting to the point of beating a dead horse.  I think the 
    discussion has been useful and helped John and I solidify a solution 
    for the error handling problem.
    
    One point that I don't think has been made to date is that the vast 
    majority of errors that the library can throw are programming 
    errors--errors that should never occur in production level code.  There 
    are exceptions, of course.  Most notably, dynamically growing a string 
    could yield an out of memory error.  Let's take a quick look at the 
    possible errors in the version that has been released:
    
    1. Expected a safestr_t and got a NULL instead
    2. Expected a safestr_t and got something that isn't a safestr_t (which 
    could potentially even cause a read access violation or unaligned data 
    access)
    3. Attempting to modify an immutable string
    4. Attempting to resize a non-resizable string
    5. Out of memory attempting to resize a string
    6. Could not open /dev/urandom (or create a crypto context on Windows)
    7. Formatting string errors (including misuse of format modifiers; 
    overflowing int on width, precision, or C99 argument specifier; or 
    using %n)
    8. Possible double free attempt in safestr_free()/safestr_release()
    9. Index out of bounds in safestr_charat()/safestr_setcharat()
    
    So there are nine basic errors, at least eight of which can be 
    considered programming errors (out of memory being the straggler here). 
      Arguably, #3, #4, #7, and #9 (perhaps also #8, but that's a bit more 
    of a stretch) could signal a possible attack.  For any of the errors 
    possibly thrown when an attack happens, what are you going to do 
    programmatically to defend against or recover from the attack?  There's 
    really not much you can do.  Not safely anyway because the integrity of 
    your application has been compromised, and you have no idea to what 
    extent.  We're back to one possible error that you might be able to do 
    something about, and that's this out of memory problem (persistent 
    little bugger, ain't it?)
    
    My personal feeling is that the default behavior of the library should 
    be to abort on any one of these errors.  However, it is reasonable to 
    provide a callback mechanism for anybody that does want to make use of 
    it.  This callback should receive an error code, the problem string, 
    and the function that's throwing the error.  If the callback returns, 
    return an error code to the caller.  My strong advice would be for any 
    callback to print an appropriate message, maybe make a log entry or 
    something (though the ability to do so reliably at this point is shaky 
    at best), and abort.
    
    It would seem reasonable to handle out of memory conditions in an 
    alternate manner, but you can already do this (mostly--you still can't 
    return an error code and avoid the abort yet) using safestr_setmemfns() 
    and providing a replacement for malloc()/realloc() that handle the out 
    of memory condition (maybe by freeing some reserved memory or 
    something).
    
    To sum up: At this point, we do plan to add a callback mechanism for 
    errors.  The default callback if none is specified will simply print an 
    error message and abort, much like what already happens at present.  If 
    a callback is set and it returns, an error code will be returned from 
    the function that threw the error, leaving it up to the caller to deal 
    with, though the caller will not be given any details of what type of 
    error occurred.  It will be up to the callback function to make that 
    information available, possibly by some kind of errno type global 
    variable.
    
    > Calls to the various string routines share a lot of common error 
    > conditions,
    > so much of that can be handled by the callback function,
    > particularly those parts which are outside the scope of the string 
    > manipulation.
    > There would also be benefit of removing a lot of the noise from the 
    > code -
    > the error-return checking that Dana mentioned - so it becomes more 
    > readable.
    >
    > The callback function should probably take a few parameters:
    > - an error code
    > - perhaps a reference to the string function and the arguments given 
    > to it
    > - a user supplied argument.  My usual practice is a (void *).
    > Its return value, seen by the string function, would indicate such 
    > things as
    > whether to retry the operation, or fail and pass the error back to the 
    > original call.
    >
    > The user-supplied argument is intentionally versatile, being weakly 
    > typed.
    > It could be as simple as a flag to indicate a global error condition,
    > or as complex as a structure pointer holding state information.
    > While this weak typing is normally seen as lower-level programming 
    > security,
    > it would be near impossible in many languages to retain the 
    > versatility otherwise.
    > An alternative could be to have the user define the type of the 
    > parameter
    > but this only allows that single type within its scope
    > and it would also have to be defined before the declaration of the 
    > string functions
    > (ie before the include file), which separates it from where it's used.
    >
    > The level of security can the be set by the user.
    > A simplistic approach would be to call the function and let it 
    > terminate
    > the process on any error:
    >
    > 	(void) somestringfunc(str, STRING_DEFAULT_ERROR, NULL);
    >
    > The semantics of STRING_DEFAULT_ERROR might include some generic 
    > operation before exit(),
    > perhaps indicated by the user argument, such as printing an error.
    > Anything more substantial (and maybe even this) is better managed by a 
    > handler.
    >
    > The typical use would be where you have error handling managed in a 
    > fairly general way:
    >
    > string_error_result_t
    > handler(int error, string_function_t func, void *arg)
    > {
    > 	string_error_result_t	result;
    >
    > 	/* code dependent on the arguments and required error processing */
    > 	return result;
    > }
    >
    > 	.
    > 	.
    > 	arg = ...;
    > 	(void) somestringfunc(str1, handler, (void *) &arg);
    > 	if (anotherstringfunc(str1, str2, handler, (void *) &arg) == 
    > STRING_ERROR_NONE)
    > 		(void) somestringfunc(str2, handler, NULL);
    > 	.
    > 	.
    >
    > Finally, STRING_FAIL_ERROR could be used when the caller wants to do 
    > all checking
    > (or do no checking at all, when it is known there will be no errors).
    > A NULL callback function would do the same thing.
    > This would normally be the only case where the function call starts to 
    > disappear
    > under the noise as it does with the current string functions:
    >
    > 	switch (somestringfunc(str, STRING_FAIL_ERROR, NULL))
    > 	{
    > 	case STRING_ERROR_NONE:
    > 		break;
    >
    > 	case STRING_ERROR_this:
    > 	case STRING_ERROR_that:
    > 		/* whatever */
    >
    > 	default:
    > 		/* unknown or unmanaged error */
    > 	}
    >
    > 	(void) anotherstringfunc(str1, str2, NULL, NULL);
    >
    >
    > Whilst all the above examples are in C, there is nothing particularly 
    > fundamental
    > that requires this.  For languages that implement exception handling 
    > natively,
    > it would be worth investigating that whole approach.
    >
    > It's tempting to implement the existing string functions with macros
    > because those that add to strings all "succeed" anyway:
    >
    > #define		strcpy(d, s)	((void) safestrcpy(d, s, STRING_FAIL_ERROR, 
    > NULL), d)
    >
    > but be aware of the usual pitfalls:
    >
    > 	strcpy(*pps++, something);	/* char **pps; */
    > 	strcpy(func(), something);	/* where func() has side effects */
    >
    > Kevin
    



    This archive was generated by hypermail 2b30 : Wed Feb 12 2003 - 13:59:58 PST