Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: finally done; reviewed

...

Compliant Solution (snprintf())

A This compliant solution avoids assuming does not assume that snprintf() succeeds will succeed regardless of its arguments and tests the return value of the function before using the buffer the function was passed to format output into. This compliant solution also treats as an error the case where the provided buffer wasn't static buffer is not large enough for for snprintf() to append the terminating null as an error.

Code Block
bgColor#ccccff
langc
#include <stdio.h>
#include <string.h>
 
extern void log_message(const char *);

void f(int i, int width, int prec) {
  char buf[40];
  int n;
  n = snprintf(buf, sizeof(buf), "i = %*.*i", width, prec, i);
  if (n < 0 || n >= sizeof(buf)) {
    /* Handle snprintf() error */
    strcpy(buf, "unknown error");
  }
  log_message(buf);
}

When the length of the formatted string is unbounded, it is best to invoke the function twice, once with a NULL buffer you can first invoke snprintf()  with a null buffer pointer to determine the size of output, then dynamically allocate a buffer of sufficient size, and finally call snprintf() again to format the output into the dynamically allocated buffer. Even with this approach, the success of all calls still needs to be tested, and any errors still must be appropriately handled. A possible optimization is to first attempt to format the string into a reasonably small buffer allocated on the stack and, only when the buffer turns out to be too small, allocate one of a sufficient size dynamically. This approach is shown in the following compliant solution.:

 Note that this solution uses the goto statement, as suggested in MEM12-C. Consider using a goto chain when leaving a function on error when using and releasing resources.

Code Block
bgColor#ccccff
langc
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
extern void log_message(const char *); 
 
void f(int i, int width, int prec) {
  char buffer[20];
  char *buf = buffer;
  int n  = sizeof(buffer);
  const char fmt[] = "i = %*.*i";

  n = snprintf(buf, n, fmt, width, prec, i);
  if (n < 0) {
    /* Handle snprintf() error */
    strcpy(buffer, "unknown error");
    goto write_log;
  }

  if (n < sizeof(buffer)) {
    goto write_log;
  }

  buf = (char *)malloc(n + 1);
  if (NULL == buf) {
    /* Handle malloc() error */
    strcpy(buffer, "unknown error");
    goto write_log;
  }

  n = snprintf(buf, n, fmt, width, prec, i);
  if (n < 0) {
    /* Handle snprintf() error */
    strcpy(buffer, "unknown error");
  }

write_log:
  log_message(buf);

  if (buf != buffer) {
    free(buf);
  }
}

Note that this solution uses the goto statement, as suggested in MEM12-C. Consider using a goto chain when leaving a function on error when using and releasing resources.

Exceptions

ERR33-EX1: It is acceptable to ignore the return value of a function that cannot fail, or a function whose return value is inconsequential, or if an error condition need not be diagnosed. The function's results should be explicitly cast to void to signify programmer intent. Return values from the following functions do not need to be checked because their historical use has overwhelmingly omitted error checking, and the consequences are not relevant to security.

...