Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Avoid in-band error indicators while designing interfaces. This practice is commonly used by C library functions but is not recommended. One example from the C standard Standard of a troublesome in-band error indicator is EOF. (See rules FIO34-C. Use int to capture the return value of character IO functions and FIO35-C. Use feof() and ferror() to detect end-of-file and file errors when sizeof(int) == sizeof(char).) Another problematic use of in-band error indicators from the C standard Standard involving the size_t and time_t types is described by rule MSC31-C. Ensure that return values are compared against the proper type.

Noncompliant Code Example (sprintf())

This specific noncompliant code example is from the Linux Kernel Mailing List archive site, although similar examples are common.

Code Block
bgColor#FFCCCC
langc

int i;
ssize_t count = 0;

for (i = 0; i < 9; ++i) {
  count += sprintf(
    buf + count, "%02x ", ((u8 *)&slreg_num)[i]
  );
}
count += sprintf(buf + count, "\n");

The sprintf() function returns the number of characters written in the array, not counting the terminating null character. This number is frequently added to an existing counter to keep track of the location of the index into the array. However, the call to sprintf() can (and will) return -1 −1 on error conditions, such as an encoding error. If this error happens on the first call (which is likely), the count variable, already at zero, is decremented. If this index is subsequently used, it will result in an out-of-bounds read or write.

...

Code Block
bgColor#ccccff
langc

errno_t sprintf_m(
  string_m buf, 
  const string_m fmt, 
  int *count, 
  ...
);

The sprintf_m() API separates out the return status of the function from information about the number of characters written. In this case, *count is set to the number of characters written in buf, while  and the return value indicates the return status. Returning the status as the return value of the function increases the likelihood that a programmer will check the return status of the function.

The previous The preceding code example can be amended as follows:

Code Block
bgColor#ccccff
langc

int i;
rsize_t count = 0;
errno_t err;

for (i = 0; i < 9; ++i) {
  err = sprintf_m(
    buf + count, "%02x ", &count, ((u8 *)&slreg_num)[i]
  );
  if (err != 0) {
    /* Handle print error */
  }
}
err = sprintf_m(
  buf + count, "%02x ", &count, ((u8 *)&slreg_num)[i]
);
if (err != 0) {
  /* Handle print error */
}

...

Code Block
bgColor#FFCCCC
langc

ssize_t read(int fildes, void *buf, size_t nbyte);

read() returns -1 −1 if an error occurs, or, if no errors occur, it returns the number of bytes actually read.

As with all in-band error indicators, this type is not recommended . This is because developers are tempted to ignore the possibility that a ssize_t value is negative.

Compliant Solution (POSIX size_t)

An alternative hypothetical signature for the read() function would beis

Code Block
bgColor#CCCCFF
langc

errno_t read(int fildes, void *buf, size_t nbyte, size_t* rbytes);

where rbytes is a pointer to a size_t. If no error occurs, and rbytes is not NULL, its value is set to the total number of bytes read, and read() would return 0. If an error occurs, read() returns a nonzero value indicating the error.

...

In this noncompliant code example, the error handler returns normally, while  but the strcpy_s() function's return value is not checked.

Code Block
bgColor#FFCCCC
langc

constraint_handler_t handle_errors(void) {
  constraint_handler_t data;
  /* define what to do when error occurs */
  return data;
}

/*...*/

set_constraint_handler(handle_errors);

/*...*/

/* Returns zero on success */
errno_t function(char *dst1){
  char src1[100] = "hello";

  strcpy_s(dst1, sizeof(dst1), src1);
  /* At this point strcpy_s may have yielded an
     error and handle_errors() might have returned */

  /* ... */
  return 0;
}

...

Code Block
bgColor#CCCCFF
langc

/*
 * The abort_handler_s() function writes a message on the
 * standard error stream and then calls the abort() function.
 */
set_constraint_handler(abort_handler_s);

/*...*/

/* Returns zero on success */
errno_t function(char *dst1){
  char src1[100] = "hello";

  strcpy_s(dst1, sizeof(dst1), src1);
  /* Because abort_handler_s() never returns,
     we only get here if strcpy_s() succeeds. */

  /* ... */
  return 0;
}

...

ERR02-EX1: Null pointers are another example of an in-band error indicator. Use of null pointers is allowed because it is supported by the language. According to C99the C Standard, Section 6.3.2.3:

If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.

ERR02-EX2: You may use a function returning in-band error indicators if you can securely guarantee the program will not try to continue processing should an error occur in the function. For example, the functions defined in TR24731-1 provide hooks for internal constraint violations. If a constraint violation handler is guaranteed not to return upon an error occurring, then you may safely ignore errors returned by these functions. You might accomplish this by having the constraint-violation handler call abort(), or longjmp(), for instance.

See recommendation ERR03-C. Use runtime-constraint handlers when calling the bounds-checking interfaces for more on the functions defined in TR24731-1.

...