Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Disadvantages of scanf

I want to know the disadvantages of scanf().

In many sites, I have read that using scanf might cause buffer overflows. What is the reason for this? Are there any other drawbacks with scanf?

like image 796
karthi_ms Avatar asked Mar 12 '10 03:03

karthi_ms


People also ask

Why we Cannot use scanf in strings?

Noncompliant Code Example In general, do not use scanf() to parse integers or floating-point numbers from input strings because the input could contain numbers not representable by the argument type.

Is scanf safer than get?

Even answers that are 4 years old say to avoid gets like the plague. @remyabel and they are also not mentioning that scanf() is equally unsafe... and everybody is ignoring poor fgets() which should be The One Way of getting user input. Indeed...

Why scanf is not working in C?

The other answers are correct - %c does not skip whitespace. The easiest way to make it do so is to place whitespace before the %c : scanf(" %c", &achar); (Any whitespace in the format string will make scanf consume all consecutive whitespace).

Why We Use get instead of scanf?

No. The scanf() function can read input from keyboard and stores them according to the given format specifier. It reads the input till encountering a whitespace, newline or EOF. On other hand gets() function is used to receive input from the keyboard till it encounters a newline or EOF.


2 Answers

Most of the answers so far seem to focus on the string buffer overflow issue. In reality, the format specifiers that can be used with scanf functions support explicit field width setting, which limit the maximum size of the input and prevent buffer overflow. This renders the popular accusations of string-buffer overflow dangers present in scanf virtually baseless. Claiming that scanf is somehow analogous to gets in the respect is completely incorrect. There's a major qualitative difference between scanf and gets: scanf does provide the user with string-buffer-overflow-preventing features, while gets doesn't.

One can argue that these scanf features are difficult to use, since the field width has to be embedded into format string (there's no way to pass it through a variadic argument, as it can be done in printf). That is actually true. scanf is indeed rather poorly designed in that regard. But nevertheless any claims that scanf is somehow hopelessly broken with regard to string-buffer-overflow safety are completely bogus and usually made by lazy programmers.

The real problem with scanf has a completely different nature, even though it is also about overflow. When scanf function is used for converting decimal representations of numbers into values of arithmetic types, it provides no protection from arithmetic overflow. If overflow happens, scanf produces undefined behavior. For this reason, the only proper way to perform the conversion in C standard library is functions from strto... family.

So, to summarize the above, the problem with scanf is that it is difficult (albeit possible) to use properly and safely with string buffers. And it is impossible to use safely for arithmetic input. The latter is the real problem. The former is just an inconvenience.

P.S. The above in intended to be about the entire family of scanf functions (including also fscanf and sscanf). With scanf specifically, the obvious issue is that the very idea of using a strictly-formatted function for reading potentially interactive input is rather questionable.

like image 54
AnT Avatar answered Oct 16 '22 05:10

AnT


The problems with scanf are (at a minimum):

  • using %s to get a string from the user, which leads to the possibility that the string may be longer than your buffer, causing overflow.
  • the possibility of a failed scan leaving your file pointer in an indeterminate location.

I very much prefer using fgets to read whole lines in so that you can limit the amount of data read. If you've got a 1K buffer, and you read a line into it with fgets you can tell if the line was too long by the fact there's no terminating newline character (last line of a file without a newline notwithstanding).

Then you can complain to the user, or allocate more space for the rest of the line (continuously if necessary until you have enough space). In either case, there's no risk of buffer overflow.

Once you've read the line in, you know that you're positioned at the next line so there's no problem there. You can then sscanf your string to your heart's content without having to save and restore the file pointer for re-reading.

Here's a snippet of code which I frequently use to ensure no buffer overflow when asking the user for information.

It could be easily adjusted to use a file other than standard input if necessary and you could also have it allocate its own buffer (and keep increasing it until it's big enough) before giving that back to the caller (although the caller would then be responsible for freeing it, of course).

#include <stdio.h> #include <string.h>  #define OK         0 #define NO_INPUT   1 #define TOO_LONG   2 #define SMALL_BUFF 3 static int getLine (char *prmpt, char *buff, size_t sz) {     int ch, extra;      // Size zero or one cannot store enough, so don't even     // try - we need space for at least newline and terminator.      if (sz < 2)         return SMALL_BUFF;      // Output prompt.      if (prmpt != NULL) {         printf ("%s", prmpt);         fflush (stdout);     }      // Get line with buffer overrun protection.      if (fgets (buff, sz, stdin) == NULL)         return NO_INPUT;      // Catch possibility of `\0` in the input stream.      size_t len = strlen(buff);     if (len < 1)         return NO_INPUT;      // If it was too long, there'll be no newline. In that case, we flush     // to end of line so that excess doesn't affect the next call.      if (buff[len - 1] != '\n') {         extra = 0;         while (((ch = getchar()) != '\n') && (ch != EOF))             extra = 1;         return (extra == 1) ? TOO_LONG : OK;     }      // Otherwise remove newline and give string back to caller.     buff[len - 1] = '\0';     return OK; } 

And, a test driver for it:

// Test program for getLine().  int main (void) {     int rc;     char buff[10];      rc = getLine ("Enter string> ", buff, sizeof(buff));     if (rc == NO_INPUT) {         // Extra NL since my system doesn't output that on EOF.         printf ("\nNo input\n");         return 1;     }      if (rc == TOO_LONG) {         printf ("Input too long [%s]\n", buff);         return 1;     }      printf ("OK [%s]\n", buff);      return 0; } 

Finally, a test run to show it in action:

$ printf "\0" | ./tstprg     # Singular NUL in input stream. Enter string> No input  $ ./tstprg < /dev/null       # EOF in input stream. Enter string> No input  $ ./tstprg                   # A one-character string. Enter string> a OK [a]  $ ./tstprg                   # Longer string but still able to fit. Enter string> hello OK [hello]  $ ./tstprg                   # Too long for buffer. Enter string> hello there Input too long [hello the]  $ ./tstprg                   # Test limit of buffer. Enter string> 123456789 OK [123456789]  $ ./tstprg                   # Test just over limit. Enter string> 1234567890 Input too long [123456789] 
like image 30
paxdiablo Avatar answered Oct 16 '22 05:10

paxdiablo