Versions Compared

Key

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

The fgets() function is and fgetws() functions are typically used to read a newnewline-line terminated line of input from a stream. The fgets() function takes a size parameter for the destination buffer and copies, at most, size-1 characters Both functions read at most one less than the number of narrow or wide characters specified by an argument n from a stream to a string. Truncation errors can occur if the programmer assumes that the last character in the destination string is a newline.The fgetws() function is similarly affected n - 1 is less than the number of characters appearing in the input string prior to the new-line narrow or wide character (which is retained) or after end-of-file.  This can result in the accidental truncation of user input.

Noncompliant Code Example

This noncompliant code example attempts to remove the trailing newline (\n) from an input linecopies the input string into a buffer, and assumes it captured all of the user's input.

Code Block
bgColor#FFCCCC
langc
#include <stdbool.h>
#include <stdio.h>
 
bool get_data(char buf[BUFSIZ + 1];

 *buffer, int size) {
  if (fgets(bufbuffer, sizeof(buf)size, stdin)) {
    return true;
  }
  if (*bufreturn false;
}
 
void func(void) {
 /* see FIO37-C */char buf[8];
  if  buf[strlen(get_data(buf, sizeof(buf)) - 1] = '\0') {
    printf("The user input %s\n", buf);
  }
}
 else {
     /* Handle error condition */
}
printf("Error getting data from the user\n");
  }
}

However, if the last character in buf is not a new-line, this code overwrites an otherwise valid newline and the stream is not at the end-of-file marker, the buffer was too small to contain all of the data from the user.  For example, because the buffer is only 8 characters in length, if the user input "Hello World\n", the buffer would contain "Hello W" terminated by a null character.

Compliant Solution (Fail on Truncation)

This compliant solution uses strchr() to replace the newline character in the string (if it exists). The equivalent solution for fgetws() would use wcschr()examines the end-of-file marker for the stream and the last character in the buffer to determine whether it is a newline or not.  If it is the end of file, or the last character is a newline, then the buffer contains all of the user's input.  However, if the last character is not at the end-of-file and not a newline then the user's input has been truncated.

Code Block
bgColor#ccccff
langc
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
 
bool get_data(char *buffer, int size) {
  if (fgets(buffer, size, stdin)) {
    size_t len = strlen(buffer);
    return feof(stdin) || (len != 0 && buffer[len-1] == '\n');
  }
  return false;
}
 
void func(void) {
  char buf[8];
  if (get_data(buf, sizeof(buf))) {
    printf("The user input %s\n", buf);
  } else {
    printf("Error getting data from the user\n");
  }
}

Compliant Solution (Expanding Buffer)

This compliant solution solves the problem by expanding the buffer to read the entire contents from stdin instead of failing if the caller did not allocate enough space.  If the allocation fails, it will return NULL, but otherwise, it returns a buffer of the received data, which the caller must free.

Code Block
bgColor#ccccff
langc
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char buf[BUFSIZ + 1 *get_filled_buffer(void) {
  char temp[32];
  char *p;

ifret = NULL;
  size_t full_length = 0;
   
  while (fgets(buftemp, sizeof(buftemp), stdin)) {
    size_t len = strlen(temp);
    if (SIZE_MAX - len - 1 < full_length) {
  p      break;
    }
    char *r_temp = strchrrealloc(buf, '\n'ret, full_length + len + 1);
    if (pr_temp == NULL) {
      break;
    }
    ret = r_temp;
    *p strcpy(ret + full_length, temp); /* concatenate */
    full_length += len;
   
    if (feof(stdin) || temp[len-1] == '\0'n') {
      return ret;
    }
  }
else {
  /* Handle error condition */
}

...

free(ret);
  return NULL;
}

Compliant Solution (POSIX getline())

The getline() function was originally a GNU extension, but is now standard in POSIX.1-2008. It also fills a string with characters from an input stream. In this case, the program passes it a NULL pointer for a string, indicating that getline() should allocate sufficient space for the string and the caller frees it later.

Code Block
bgColor#ccccff
langc
#include <stdio.h>

void func(void) {
  char* buf = NULL;
  size_t dummy = 0;
  if (getline(&buf, &dummy, stdin) == -1) {
	/* handle error */
  }
  printf("The user input %s\n", buf);
  free(buf);
}

Risk Assessment

Incorrectly assuming a new-line newline character is read by fgets() or fgetws() can result in data truncation.

Rule Recommendation

Severity

Likelihood

Remediation Cost

Priority

Level

FIO36FIO20-C

medium Medium

likely Likely

medium Medium

P12

L1

Automated Detection

Tool

Version

Checker

Description

LDRA tool suite
Include Page
LDRA_V
LDRA_V
44 SEnhanced enforcement

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Other Languages

This rule appears in the C++ Secure Coding Standard as FIO36-CPP. Do not assume a new-line character is read when using fgets().

References

Wiki Markup
\[[ISO/IEC 9899:1999|AA. C References#ISO/IEC 9899-1999]\] Section 7.19.7.2, "The {{fgets}} function"
\[[Lai 06|AA. C References#Lai 06]\]
\[[Seacord 05a|AA. C References#Seacord 05]\] Chapter 2, "Strings"

Bibliography

[Lai 2006] 
[Seacord 2013]Chapter 2, "Strings"

 

...

Image Added Image Added Image AddedFIO35-C. Use feof() and ferror() to detect end-of-file and file errors when sizeof(int) == sizeof(char)      09. Input Output (FIO)       FIO37-C. Do not assume character data has been read