J
jacob navia
Microsoft proposed recently (In the Technical Report 24173 presented to
the Standards Comitee) a change in the C library in the sense of more
security.
Basically, missing size information is passed to the new primitives like
strcat_s(), gets_s(), and many others.
The good thing in this proposal is that the specifications of the new
library are much more precise than in the existing library, an
improvement that made me start implementing it within the framework of
lcc-win32.
One of the most interesting features of that library is the proposal of
a mechanism for testing the requirements of a function, and the calling
of a routine that the user can modify when a requirement for a function
is missing.
This is a much more attenuated form of the "Programming by contract"
proposed by Betrand Meyer in his Eiffel language. A function call is
seen as a contract within two parties: the calling function, that must
furnish correct arguments to the called function, and the called
function that must furnish specific results to the caller.
In the proposal (I will call it TR 24731 for short) the requirements
of each function are specified in great detail. Let's take, for instance
the function
errno_t strncpy_s(char * restrict s1,
rsize_t s1max,
const char * restrict s2,
rsize_t n);
This function has the following runtime constraints:
1) Neither s1 nor s2 shall be a null pointer.
2) Neither s1max nor n shall be greater than RSIZE_MAX.
3) s1max shall not equal zero.
4) If n is not less than s1max, then s1max shall be greater than
strnlen_s(s2, s1max).
5) Copying shall not take place between objects that overlap.
A simple implementation of this function could be like this:
errno_t strncpy_s(char * restrict DestinationString,
rsize_t DestinationLength,
const char * restrict SourceString,
rsize_t n )
{
errno_t rc = -1;
// strnlen_s is safe to use as it has no rt constraints
rsize_t srcLen = strnlen_s( SourceString, DestinationLength );
// Verify runtime-constraints
if (require( DestinationString != NULL) &&
require(SourceString != NULL) &&
require( DestinationLength <= RSIZE_MAX) &&
require( n <= RSIZE_MAX) &&
((n < DestinationLength) ||
require( srcLen <= DestinationLength-1 )) &&
__check_overlap(DestinationString, DestinationLength,
SourceString, srcLen )) {
for( ; n; --n) {
if( *SourceString == 0 )
break;
*DestinationString++ = *SourceString++;
}
*DestinationString = 0;
rc = 0;
} else {
// Runtime-constraints found, store zero in receiving field
if( (DestinationString != NULL) && DestinationLength > 0 &&
DestinationLength <= RSIZE_MAX ) {
DestinationString[0] = 0;
}
}
return( rc );
}
I have defined the require macro as follows:
#define require(constraint) \
((constraint) ? 1 : ConstraintFailed(__func__,#constraint,NULL) )
Note that this must be a macro since it uses the __func__ identifier,
that is the name of the current function being compiled, in the example
the function strncpy_s.
The "ConstraintFailed" function is defined like this:
#define MSG_MAX 512
int ConstraintFailed( const char *fn, const char *reason,
void *reserved )
{
constraint_handler_t handlerFn = get_constraint_handler_s();
char msg[MSG_MAX];
snprintf(msg,sizeof(msg),
"In function %s, assertion\n%s\nfailed\n",
fn,reason);
handlerFn(msg,reserved,EINVAL);
return 0;
}
The ConstraintFailed function gets the current active handler and calls
it. If the handler returns, it returns zero, what makes the whole series
of tests separated by the && construct fail.
Note that this function will be called only once for the first
constraint that failed, as specified in TR 24731.
What would be interesting is to generalize this mechanism to be able to
use it in other contexts and within other libraries. Obviously this is
not a replacement for try/catch (what lcc-win32 already offers) but it
is *much* better than nothing, as now.
Note that the constraint handlers are set per thread within the context
of lcc-win32 even if this is not specified in the Microsoft proposal.
(You know the tune: "embrace and extend"
Since the handler function can be changed with the function
set_constraint_handler(), you can modify the handler to just log the
errors instead of aborting the program, mailing you an error report
if it detects an internet connection, or whatever.
True, you can say that the assert macro is already there, but it doesn't
have the possibility of dynamically changing the handler according to
the importance of the failure, like this one has.
Comments welcome.
jacob
the Standards Comitee) a change in the C library in the sense of more
security.
Basically, missing size information is passed to the new primitives like
strcat_s(), gets_s(), and many others.
The good thing in this proposal is that the specifications of the new
library are much more precise than in the existing library, an
improvement that made me start implementing it within the framework of
lcc-win32.
One of the most interesting features of that library is the proposal of
a mechanism for testing the requirements of a function, and the calling
of a routine that the user can modify when a requirement for a function
is missing.
This is a much more attenuated form of the "Programming by contract"
proposed by Betrand Meyer in his Eiffel language. A function call is
seen as a contract within two parties: the calling function, that must
furnish correct arguments to the called function, and the called
function that must furnish specific results to the caller.
In the proposal (I will call it TR 24731 for short) the requirements
of each function are specified in great detail. Let's take, for instance
the function
errno_t strncpy_s(char * restrict s1,
rsize_t s1max,
const char * restrict s2,
rsize_t n);
This function has the following runtime constraints:
1) Neither s1 nor s2 shall be a null pointer.
2) Neither s1max nor n shall be greater than RSIZE_MAX.
3) s1max shall not equal zero.
4) If n is not less than s1max, then s1max shall be greater than
strnlen_s(s2, s1max).
5) Copying shall not take place between objects that overlap.
A simple implementation of this function could be like this:
errno_t strncpy_s(char * restrict DestinationString,
rsize_t DestinationLength,
const char * restrict SourceString,
rsize_t n )
{
errno_t rc = -1;
// strnlen_s is safe to use as it has no rt constraints
rsize_t srcLen = strnlen_s( SourceString, DestinationLength );
// Verify runtime-constraints
if (require( DestinationString != NULL) &&
require(SourceString != NULL) &&
require( DestinationLength <= RSIZE_MAX) &&
require( n <= RSIZE_MAX) &&
((n < DestinationLength) ||
require( srcLen <= DestinationLength-1 )) &&
__check_overlap(DestinationString, DestinationLength,
SourceString, srcLen )) {
for( ; n; --n) {
if( *SourceString == 0 )
break;
*DestinationString++ = *SourceString++;
}
*DestinationString = 0;
rc = 0;
} else {
// Runtime-constraints found, store zero in receiving field
if( (DestinationString != NULL) && DestinationLength > 0 &&
DestinationLength <= RSIZE_MAX ) {
DestinationString[0] = 0;
}
}
return( rc );
}
I have defined the require macro as follows:
#define require(constraint) \
((constraint) ? 1 : ConstraintFailed(__func__,#constraint,NULL) )
Note that this must be a macro since it uses the __func__ identifier,
that is the name of the current function being compiled, in the example
the function strncpy_s.
The "ConstraintFailed" function is defined like this:
#define MSG_MAX 512
int ConstraintFailed( const char *fn, const char *reason,
void *reserved )
{
constraint_handler_t handlerFn = get_constraint_handler_s();
char msg[MSG_MAX];
snprintf(msg,sizeof(msg),
"In function %s, assertion\n%s\nfailed\n",
fn,reason);
handlerFn(msg,reserved,EINVAL);
return 0;
}
The ConstraintFailed function gets the current active handler and calls
it. If the handler returns, it returns zero, what makes the whole series
of tests separated by the && construct fail.
Note that this function will be called only once for the first
constraint that failed, as specified in TR 24731.
What would be interesting is to generalize this mechanism to be able to
use it in other contexts and within other libraries. Obviously this is
not a replacement for try/catch (what lcc-win32 already offers) but it
is *much* better than nothing, as now.
Note that the constraint handlers are set per thread within the context
of lcc-win32 even if this is not specified in the Microsoft proposal.
(You know the tune: "embrace and extend"
Since the handler function can be changed with the function
set_constraint_handler(), you can modify the handler to just log the
errors instead of aborting the program, mailing you an error report
if it detects an internet connection, or whatever.
True, you can say that the assert macro is already there, but it doesn't
have the possibility of dynamically changing the handler according to
the importance of the failure, like this one has.
Comments welcome.
jacob