Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect `snprintf` errors?

Tags:

c

printf

int snprintf(char * restrict s, size_t n, const char * restrict format, ...);

snprintf() nicely prevents overrunning the destination s. Yet when the destination is insufficient for the complete result, how to detect that and other errors?

Is the following sufficient?.

char buf[11 + 10 + 1];
if (snprintf(buf, sizeof buf, "Random int %d", rand()) >= sizeof buf) {
  fprintf(stderr, "Buffer too small");  // Maybe `int` was 64-bit?
  exit (EXIT_FAILURE);
}
like image 988
chux - Reinstate Monica Avatar asked Jan 08 '19 02:01

chux - Reinstate Monica


People also ask

Is Snprintf unsafe?

Snprintf is more secure and if the string number overruns the characters, the string is protected in the buffer even if the format is different. It works with n characters and nth location and hence the location of null character is not considered at all. Allocation of null character memory is preserved in sprintf.

Is Snprintf safe from buffer overflow?

It is safe as you long as you provide the correct length for the buffer.

What does Snprintf return?

The snprintf function returns an integer value that equals, in magnitude, the number of characters written to the area addressed by dest . If the value returned is negative, then either the maxlen character limit was reached or some other error, such as an invalid format specification, has occurred.

What is the difference between Sprintf and Snprintf?

The snprintf function is similar to sprintf , except that the size argument specifies the maximum number of characters to produce. The trailing null character is counted towards this limit, so you should allocate at least size characters for the string s .


1 Answers

This is part of Can I answer my own question?. Additional answers are welcome.

How to detect snprintf errors in C?

Short answer

Recall snprintf() returns an int.

Use the wider of size_t or unsigned cast.

if ((size_t) snprintf(... ) >= sizeof buf) {
  error();
}

or pedantically

int length_needed = snprintf(... );
if (length_needed < 0 || (unsigned) length_needed >= sizeof buf) {
  error();
}

Testing for truncation

Sometimes it is very important to detect a truncated string from snprintf(). Lacking a test can lead to trouble:

char buf[13];
char *command = "format_drive";
char *sub_command = "cancel";  
snprintf(buf, sizeof buf, "%s %s", command, sub_command);
system(buf); // system("format_drive") leads to bye-bye data

OK code

A single test if the return value meets or exceeds the size of the destination array is nearly sufficient.

char buf[20];
if (snprintf(buf, sizeof buf, "Random int %d", rand()) >= sizeof buf) {
  fprintf(stderr, "Buffer too small");
  exit(EXIT_FAILURE);
}

Negative value

The snprintf function returns the number of characters that would have been written had n been sufficiently large, not counting the terminating null character, or a negative value if an encoding error occurred. Thus, the null-terminated output has been completely written if and only if the returned value is nonnegative and less than n. C11dr §7.21.6.5 3

Robust code would directly check for a negative value for the rare encoding error. if (some_int <= some_size_t) unfortunately is not sufficient as the int will be converted to a size_t. An int negative return value then becomes a large positive size_t. This usually is far larger than the size of the array yet is not specified to be so.

// Pedantic check for negative values 
int length_needed = snprintf(... as above ...);
if (length_needed < 0 || length_needed >= sizeof buf) {
  fprintf(stderr, "Buffer too small (or encoding error)");
  exit(EXIT_FAILURE);
}

Mis-match sign-ness

Some compiler warnings whine about comparing integers of different sign-ness such as gcc's -Wsign-compare with int and size_t. Casting to size_t seems reasonable.

warning: comparison between signed and unsigned integer expressions [-Wsign-compare]

// Quiet different sign-ness warnings
int length_needed = snprintf(... as above ...);
if (length_needed < 0 || (size_t) length_needed >= sizeof buf) {
  fprintf(stderr, "Buffer too small (or encoding error)");
  exit(EXIT_FAILURE);
}

Pedantic

C does not specify that the positive values of int are a sub-range of size_t. size_t could be unsigned short and then SIZE_MAX < INT_MAX. (I know of no such implementation.) Thus a cast to (size_t) some_int could alter the value. Instead, casting the positive return value to unsigned (INT_MAX <= UINT_MAX is always true) will not alter the value and will ensure the compare is done with the widest unsigned type between unsigned and size_t.

// Quiet different sign-ness warnings
int length_needed = snprintf(... as above ...);
if (length_needed < 0 || (unsigned) length_needed >= sizeof buf) {
  fprintf(stderr, "Buffer too small (or encoding error)");
  exit(EXIT_FAILURE);
}
like image 129
chux - Reinstate Monica Avatar answered Sep 29 '22 23:09

chux - Reinstate Monica