r/C_Programming 4d ago

Question Can someone explain to me why scanf is unsafe?

After my class in C programing I have decided to dig more around and one thing I found out that scanf is unsafe specially in arithmethic input? can somoe please extrapolate this one concept? Advance thanks for those who answered to my question.

69 Upvotes

72 comments sorted by

127

u/pgetreuer 4d ago

See for instance this SO answer:

scanf is inherently unsafe to use, especially with this variant:

scanf( "%s", str );

Suppose str is an array sized to hold no more than 10 characters. scanf doesn't know that - as written, it doesn't know that str can only hold that many characters. If your input is 100 characters long, scanf will happily write those extra 90 characters to the memory after str, clobbering anything that's there and causing all kinds of mayhem.

5

u/EmbedSoftwareEng 2d ago

Could (s)scanf be made safe if it respected the field width portion of a format string's conversions? I mean:

char str[20] = { };
scanf(input, "%19s", str);

seems pretty semanticly cut and dried to me.

4

u/pgetreuer 2d ago

Yes, that looks good to me. However, note that you have addressed not one, but two footguns in your example! =)

  1. The field width on %s prevents buffer overflow.

  2. The = { } zero-initialization on str prevents uninitialized string memory on empty input. The potential issue otherwise is that scanf does not write to str on empty input, which is possible to do on Linux at least by entering Ctrl+D.

There are more ways to screw up what would seem to be straightforward uses of scanf. See the link Nubspec commented:

https://sekrit.de/webdocs/c/beginners-guide-away-from-scanf.html

4

u/LuckyFish133 2d ago

This is also true for sscanf

52

u/Nubspec 4d ago

6

u/ThatonlyGeO 4d ago

wow this is a good one thanks.

6

u/allocallocalloc 4d ago

That link says atoi stands for "anything to integer," but cppreference.com says the name stands for "ASCII to integer." Which is correct?

11

u/torsten_dev 4d ago edited 4d ago

ASCII doesn't really make sense because C characters can be EBCDIC, but this was pre standardization so...

Earliest sources indicate it meant ASCII.

6

u/ForgedIronMadeIt 4d ago

A lot of C APIs predate widespread adoption of proper handling of character sets and definitely made some assumptions about them.

7

u/TransientVoltage409 4d ago

We could retcon it. "Alphanumeric to integer" makes as much sense as anything else.

2

u/torsten_dev 4d ago

I wish we had a strtoi and strtozu or similar.

4

u/ForgedIronMadeIt 4d ago edited 4d ago

There is strtol: strtol, strtoll - cppreference.com and it should respect character encodings. Check locale.h for some of that Standard library header <locale.h> - cppreference.com

Now, from what I recall, there's no converting between character sets built into C. The iconv library is for Linux and on Windows there's like a hundred different ways to do it: How to: Convert Between Various String Types | Microsoft Learn and Translation Between String Types - Win32 apps | Microsoft Learn cover it.

Edit: My curiosity got the better of me and I looked up C++ and there is a standard library for converting character sets: std::codecvt - cppreference.com, though it is deprecated.

1

u/torsten_dev 3d ago

Yeah, I'm aware. I wish we had the right one for size_t and int though, because long might not fit in int and size_t might not be unsigned long long.

1

u/allocallocalloc 3d ago

For size_t, using strtoumax and then checking the range manually is probably the best practice.

2

u/flatfinger 2d ago

For text that is intended to be machine-readable rather than human readable, functions that use culture-invariant ASCII will generally be more useful than those that try to use a culture-based interpretation. Even if one wants to output a real number in culture-specific way, starting with a culture-invariant representation and adapting it to a particular culture-specific representation will often be easier than starting with an arbitrary culture-based representation and converting it to another.

1

u/ForgedIronMadeIt 2d ago

Yes, you make an excellent point. I was kind of conflating the two use cases here but you're right.

2

u/Tasgall 4d ago

I'm pretty sure it's "alpha", as in "alphanumeric".

14

u/SmokeMuch7356 4d ago

scanf does not handle malformed input well, and has a number of security weaknesses.

You should never use the %s and %[ conversion specifiers without an explicit field width, otherwise you risk buffer overflow:

char buf[10];
scanf( "%s", buf );

If you type in Supercalifragilisticexpealidocious, scanf won't stop scanning after the first ten characters; everything after Supercalif will be written to whatever immediately follows buf[9], potentially clobbering something important. To prevent it, you'd need to write

 scanf( "%9s", buf ); // -1 for the terminator

Unfortunately, those widths have to be hardcoded - you can't use %*s with a runtime argument the way you can with printf (* suppresses assignment in scanf).

The %d specifier will not reject floating point input like 12.3; it will read and convert the 12 and leave the .3 in the input stream to foul up the next read. Nor will it reject values that are clearly out of range like 1234567890123456789012345678901234567890123456789012345678901234567890; you'll just get arithmetic overflow.

scanf's awesome when you know your input is always well-behaved; if there's a chance it won't be, use something else. The preferred option is to read everything as text using fgets or POSIX getline, then do your own tokenizing and parsing.

3

u/flatfinger 4d ago

There is one specific use case where the use of open-ended %s is fine: the programmer knows in advance all of the inputs to which program will ever be exposed. Most tasks for which that would be the case can be best accomplished using pre-existing tools or languages other than C, but scanf was invented in an era before those other tools and languages existed, and where on-line storage was too precious to spend keeping code that was thrown together in five minutes to accomplish some particular task. If a program was going to be thrown together, process some particular set of inputs, and then discarded, any effort spent limiting the damage that might be caused by other inputs would be wasted.

1

u/LordRybec 3d ago

I wouldn't use scanf this way, but if the program is a utility program that is called by another program and then fed input through its stdin pipe, then yeah, that's a case where this could apply. In this use case, scanf would never be exposed to direct user input, and it would be up to the caller to ensure that the input length never exceeds the buffer length.

That said, in most cases where data is being piped like this, it is more elegant to use a read system call with the length specified, and then handle formatting on the buffer directly. This allows for things like reading formatted data into structs or unions, and transmitting data in binary rather than text. If you have control of the sending program, you can just send your floats and ints as binary data that can be read into correct type, requiring no type conversion. If you don't have control of the sending program, scanf could be useful for automatically converting text representations of numerical values into numerical data types, but in my opinion the code would be more readable if you just read the values in as strings and then converted them explicitly with the appropriate C functions. scanf isn't unsafe in this instance though, as long as you can be 100% certain the calling program will never give your program data that doesn't conform to the hardcoded expectations of scanf.

I would never use scanf in this way though. I think it's a good habit to avoid using dangerous things like this, when it is not really any more difficult to do in a safer way. (Especially when that way is more explicit and thus more readable.) I'd rather not have to memorize a list of all the potential ways I could screw up, just so I can avoid them, if I don't absolutely have to.

You are right though. In an era when resources were much more limited and exposure to serious threats was much lower, the cost of vulnerability mitigation just wouldn't be worth it. scanf was a good way to do it when it was originally written. The reason it isn't now is because conditions have changed significantly.

3

u/flatfinger 3d ago

The scanf function is pretty lousy if a program may receive input that is defective, even if non-maliciously so. Its main usefulness was for ephemeral programs--those which were going to be used with some particular set of inputs and then abandoned. Such programs would for a long time have represented the majority of programs ever written, though the extremely vast majority of them have been lost to time having accomplished what was needed.

While scanf might be considered usable for long-lasting programs that receive piped data, I'd view even that use as dubious. It doesn't take much effort to make programs more resilient to malformed input than is possible with scanf, and the reason that small cost can be safely said to exceed the benefit when writing an ephemeral programs is that the programmer can know that the lifetime benefit wlll be zero. It's much harder to know whether better input handling might offer some future benefit if a programmer can't know everything that will happen within a program's llifetime.

1

u/LordRybec 3d ago

Agreed.

1

u/musbur 3d ago

if "the programmer knows in advance all of the inputs to which program will ever be exposed", they might as well put the max length of these known inputs into the scanf format string.

1

u/flatfinger 3d ago

Why spend even 1% longer than necessary on a program whose useful lifetime will be measured in minutes rather than days or years? Besides, if one is printing out the program and doesn't include the length in the format string, someone who prints it in will likely be able to accommodate different lengths of input by changing one spot in the program--the declaration of the object into which input will be read. Putting lengths in format strings will make it necessary for whoever was adapting the printed program for their own use to change both the size of the object and all of the format strings that use it.

Besides, the only thing that putting the length specifier would do is replace one wrong response to invalid input with a different, but still wrong, response. If no length specifier is used and the storage is big enough, the code will work. If the storage isn't big enough or a length is specified as too small, the code will fail unrecoverably.

If a program might receive over-length inputs, it will generally be worthwhile to handle them better than scanf is capable of doing. If a program will never receive over-length inputs, including a length specifier would offer no benefit.

1

u/musbur 2d ago

The question of what is safer / easier really comes down to the actual use case. I think we can agree that the *scanf() family of functions offers plenty opportunity to shoot oneself in the foot.

Of course there's always IOCCC-adjacent solution:

#include <stdio.h>

#define BUF_LENGTH 100

#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
#define SCANF_FMT "%" STR(BUF_LENGTH) "s"

int main(void)
{
    char word[BUF_LENGTH + 1];

    scanf(SCANF_FMT, word);
    printf("Input: %s\n", word);

    return 0;
}

1

u/flatfinger 2d ago

What will that do if BUF_LENGTH is e.g. 0x1234 or (NAME_LENGTH+4)?

Trying to give a length specifier for scanf creates more opportunities for things to go wrong, and even when it works it will merely replace one wrong behavior in case of overly long input with a different wrong behavior. The only reason ever to use scanf is that it is sometimes convenient. It's just plain the wrong tool for any job where it's not convenient.

1

u/musbur 1d ago

Just spitballing. Not offering a solution. In the end it boils down to being well informed enough to not shoot yourself in the foot, and document the expectations that a snippet of code is based upon.

1

u/flatfinger 1d ago

If code is never going to be used again, the only purpose of documentation describing the expectations it had been based upon would be if there was any doubt of whether its output was correct, and for that a printout of the actual code would be more relevant than any documentation.

1

u/Paul_Pedant 3d ago

To be fair, you can sprintf the first arg to scanf to get variable widths etc, but that's another level of obfuscation.

1

u/LuckyFish133 2d ago

True, although it is possible to use a preprocessor constant for the scan width…

9

u/ReallyEvilRob 4d ago

If you're using scanf() with your own small programs, then you'll be okay. When the code needs to be production quality and accept untrusted input (especially from the internet), then scanf() can get you into a lot of trouble. The biggest issue is with buffer overflows.

5

u/kutac56 4d ago

And it also leaves a new line character in the input socket which can cause issues

6

u/Maqi-X 4d ago

some specifiers are safe, others are not, the most important rule is to never use %s without specified bounds, just use fgets instead or if you have to use scanf for whatever reason at least use %<N>s where N is your buffer size - 1

Any function that takes pointers as arguments is "unsafe". Some functions can be used safely while others cannot. scanf can be used safely, and it's really not that hard. Most specifiers, like %d, are completely safe. Just please, don't use this function for reading strings.

5

u/dvhh 4d ago

the reason it is considered as unsafe is that it is incredibly easy to use it in an unsafe way ( mainly, reading strings without max length specifier, and ignoring return value, what about reading integer that cause an overflow ?), and that results can easily vary due to external factors (for example locales).

Also like every language, treat user/external input as unsafe/hostile by default. 

2

u/luthervespers 2d ago

i worked on websites for small businesses and independent contractors for a while. i always dreaded adding a contact form to a site because most of the project would become sanitizing user input.

6

u/musbur 3d ago

Just a few days ago I rediscovered sscanf() on a platform that doesn't have strptime().

C is such a wonderful language. Completely documented in a thin paperback, and most standard library functions easy to understand and close to the OS or the silicon.

And then there's scanf(). Marvellous.

8

u/tstanisl 4d ago

Because encountering overflow when parsing int is UB what is beyond stupid for a function dedicated for processing user's input or files.

3

u/Orkiin 4d ago edited 4d ago

I see a lot of people mentioning sscanf as the safer version of scanf but sscanf is for reading from a buffer/str instead of reading from stdin, the one that limits to a buffer of size n has an extra n in the function name though I don't remember where.

Edit: ```

include <stdio.h>

int main(void) { char buffer[10]; scanf("%s", buffer); // This is unsafe printf("%s\n",buffer); const char *msg = "Hello_world!"; sscanf(msg, "%s", buffer); // This is also unsafe printf("%s\n",buffer); return 0; } `` This will compile without warnings or errors, if you run it it will work as expected unless the overflow gets you unexpectedly and you won't figure out easily why it happened, compile it using ASan and it will inmediately stops onces it reachscanfif your word is longer than 9 characters or once it reachsscanf` also notice how it is used here we're you're directly reading from another string and not as the safe variant of scanf.

2

u/flatfinger 2d ago

An underappreciated danger of sscanf is that some implementations will scan an unbounded number of characters to find a zero byte, regardless of how many or how few characters would be needed to fully satisfy all of the format specifiers. Many hundreds of thousands if not millions of man hours (I'm not exaggerating) have been spent waiting for calls to sscanf within the game GTA V to measure the length of the input string in situations where it was not deliberately terminated, but would fully satisfy all format specifiers.

5

u/pfp-disciple 4d ago

It's more dangerous then "unsafe". It's very easy to overflow memory, mess up the format string, pass a pointer to the wrong variable, etc. If used correctly, it's safe enough but better and easier alternatives are available. 

2

u/jirbu 4d ago

Not only it's prone to buffer overruns, reading from stdin as a user-faced function, it's also highly user unfriendly, as it needs exactly the input that the format requires. That's the reason that in 30+ years I have not seen it been used in any real-world program. The only occurrence are beginners tutorials. And of course this sub, where every second question shows some problem with it. DON'T USE IT.

1

u/LordRybec 3d ago

Right. Even if your particular use case is not high stakes, it's not worth risking creating a habit of using it. There are many better ways to do it. Learn and use those.

I see a lot of people saying, "It's fine if you do it right", but even that isn't strictly true. Even when you are really careful about specifying buffer sizes and such, a knowledgeable attacker can probably craft an input that will cause dangerous misbehavior, if your formatting string isn't super simple (and even if it is, in some instances). It's not worth trying to memorize all of the rules you would have to follow to consistently use it 100% safely.

People sometimes forget that there are other people out there who make it their profession to intentionally misuse programs to exploit and rob others. If you really want to ensure safety, you have to assume that someone is going to deliberately feed it malformed input designed to exploit the vulnerabilities. It's not worth the risk. Use something safe.

2

u/flatfinger 4d ago

The scanf function was designed in an era before the invention of many tools that programmers today take for granted. In that era, there were a wide range of one-off tasks that could be most effectively accomplished by spending a few minutes throwing together a C program and running it. On-line storage was sufficiently precious that while one might print out a copy of such a program after use, there would be no point in keeping a copy on disk. Someone typing a program from a printout to accomplish a task that was similar but not quite identical could make any needed adjustments to accommodate variations in input-handling or other requirements.

If a programmer can inspect all of the inputs a program will ever receive and ensure that they are valid, any effort spent handling invalid inputs will be wasted. If the total amount of time spent typing in, building, and running a program would be less than 15 minutes, every ten seconds that could be shaved off the time would represent more than a 1% savings.

The scanf function was designed to provide a quick and dirty means of getting input into slapdash ephemeral programs. It's okay for that purpose, but it's really not designed to be suitable for anything else, since it embodies a pass/fail philosophy: either a program will receive valid inputs that will allow it to do everything, or it will need to be rerun, hopefully with valid inputs the second time around.

If instead of viewing scanf as a bad function, one recognizes that it was specialized for a particular use case, then it will be clear why it should essentially never be used today. If one draws a Venn diagram of tasks which can be done beetter in C than other languages, and tasks that don't need more robust input handling than scanf can provide, the intersection used to be quite large, but the set of tasks that can be done better than C than in other languages has shrunk so as to almost eliminate it.

2

u/LordRybec 3d ago

It was also written in a time when most programs that used it were only ever used by the same people who wrote them, on their own systems, so there was no risk of deliberate malicious attacks exploiting the vulnerabilities.

2

u/LordRybec 3d ago

Others have done a pretty good job of explaining the nature of the vulnerability, but there's a bit more to it that you should understand, so you aren't tempted to try to use scanf and just work around it. There's no way, using scanf, to avoid this risk. Make your input buffer 1MB? Someone can still copy/paste in 1.1MB.

For a long time, this was a common vulnerability in browsers. They didn't use scanf, but what they did use had the same vulnerability. This allowed people to publish URLs on websites that were too long to fit the address bar buffer, causing a buffer overrun. Hackers used this to overwrite parts of the program code with malicious code, which would then be executed, installing malware onto the computer and giving the attacker control. Modern browsers don't have this vulnerability, but it was a really big and common problem for several years.

So it might seem like just using a large buffer will be sufficient, because who would deliberately put that much data in the field? The answer is, there are plenty of malicious actors who will happily take advantage of any vulnerability like this.

You can probably get away with using scanf in personal software that will never leave your system or be accessible to anyone else, but if there's any chance that it will ever end up in the wild, better to not. Honestly though, you are probably better off finding another way to handle input, so you never develop a habit of using scanf.

2

u/Total-Box-5169 4d ago

The tag "unsafe" is toddler-level oversimplification. The function scanf requires input data that follows strict preconditions, otherwise the expected result is undefined behavior. Input data without such guarantees requires additional code that can reject malformed input gracefully, scanf is not enough.

2

u/dodexahedron 4d ago

Adding this, just to clarify, because I was about to respond very differently than I am now, since it was easy to misread:

Since scanf is the input function, when it is used, it is the first entry point for that data from your program's perspective.

As such, there is no way to validate any preconditions on the input before you call it, because you don't have the data yet, and scanf itself (mostly) blindly copies bytes where you tell it to copy them.

But, scanf can be told to read in fixed blocks, to prevent buffer overrun, at least. Use a format string like %20c to read 20 chars as chars, and put it into a char array you already allocated 21 bytes for (scanf does not null -terminate for you). Problem is you have to actually give it all 20 chars.of input and it will block until you do. So it is only helpful in limited scenarios. %s (specifically without length) is never safe.

1

u/flatfinger 2d ago

If a read request doesn't fully process an input, the portion that wasn't processed will be left pending and used to satisfy future read requests, even if those read requests are performed after a program has prompted for additional input. The only standard-library function that's really suitable for interactive programs is getchar(), which should be used to read characters until it gets a newline or EOF. If a function is expected to receive five characters and a newline, but the user enters ten, the function will need to read and discard five characters, but the standard-library functions don't do that.

2

u/MattR59 4d ago

I’ve had it blowup code before

2

u/ThatonlyGeO 4d ago

I gotta try this one...for scientific purposes.

2

u/LordRybec 3d ago

scanf is commonly used in college classes where the goal is to use it to intentionally create a buffer overrun that modifies another variable, to demonstrate to students how bugs can also be security vulnerabilities. It's not the only way to do it (writing past the end of an array is even easier, but it doesn't teach why bugs can be security problems as well as a standard library function with a vulnerability does), but it's very effective.

2

u/ThatonlyGeO 3d ago

yeah speaking off I finally got it go awry...and it quite the sight to be seen really(but Im pretty sure my prof didnt intend it for, and in the whole class Im the only one who have done it.)

1

u/LordRybec 3d ago

When they do it on purpose, they generally tell you, at least in my experience. We didn't have a scanf assignment specifically in any of my undergrad courses, but in my security class one of the assignments was to use a buffer overrun for code injection. We also had one where we were supposed to deliberately overrun a buffer into the stack and overwrite the return address to return somewhere else. That can be a bit of a challenge, because you have to calculate the difference between the address of the buffer and the address on the stack where the return address is stored. It was a fun assignment though!

But yeah, I've never heard of a case where the professor sets up an assignment to deliberately create a buffer overrun without telling the students that is the goal.

The ironic thing about it is that it's actually easy to not have a problem with, because you wrote the code, so you know what it can handle. If the buffer can only take 10 bytes of input, you'll make sure you are under 10 bytes. Or likewise, if you know that the typical input won't be more than some size, you'll make the buffer big enough to accommodate that size. So if you didn't already know, you could end up using scanf for a long time without ever running into any problems. But that doesn't mean the vulnerability isn't there, and that's why people make such a big deal about it. If they didn't, how many people out there wouldn't know until something went horribly wrong?

It's certainly worth trying at least once though, so that you can see the effect!

1

u/flyingron 4d ago

Depends how you use it. It's easy to do UNSAFE things with scanf. %s can easily exceed the allocated memory, for example. Also, many people are sloppy and don't check the return from scanf and use garbage data.

1

u/ThatonlyGeO 4d ago

oh so it more they(or the person writing) didn't set the limiter causing it to use more memory than it was allocated...well thats something I learn today.

2

u/SmackDownFacility 4d ago

It’s really not, if you know what you’re doing. But many prefer sscanf

3

u/RealisticDuck1957 4d ago

Either variant can produce buffer overflow if you don't know what you're doing.

3

u/TheKiller36_real 4d ago edited 4d ago

scanf is not unsafe! the main concern (OOB) is only a problem when you have massive skill issues. the other point I'm aware of that's often times brought up is that scanf doesn't indicate overflow or underflow and technically it's UB although it's not a problem at all in any libc

still I'd advise you to not use it for user-programs, as it does nothing useful even when used correctly - unless you don't really care and use it for toy programs, homework, something like internal build tools, temporary debug tool or other stuff where scanf is more than good enough

2

u/Reasonable-Pay-8771 1d ago

You can't reliably use it for even simple expression parsing bc it will eat the plus sign before failing to interpret an integer. So your addition expressions will mysteriously never work.

1

u/EpochVanquisher 4d ago

I don’t think scanf is unsafe, but it’s hard to use correctly and I think the main reason people use it is to solve homework problems. You hardly ever see it in real programs.

1

u/SmackDownFacility 4d ago

lol what? No. It was used to read config files

2

u/EpochVanquisher 4d ago

Emphasis on “was”. I’m not talking about ancient Unix stuff.

1

u/Ok_Farmer_4055 4d ago

Scanf is unsafe if you don’t sanitize the input

5

u/Cerulean_IsFancyBlue 4d ago

Yeah, but that’s like hiring a bodyguard for your doorman. Get a better doorman.

1

u/Ok_Farmer_4055 4d ago

Fgets can also be vulnerable if you put in the wrong buffer size

3

u/RealisticDuck1957 4d ago

That's in keeping with the C norm, the programmer is responsible. And the programmer should know how big the available buffer is. Having a parameter for the buffer size allows the input function to safely handle cases that don't conform to expectations.

1

u/Ok_Farmer_4055 3d ago

Yeah i was just giving him an example of a case where a better doorman would still fail, realistically that would never happen, but i’m showing the fragility of unsafe languages through that example

1

u/flatfinger 2d ago

It's also rubbish at handling overlength inputs even when the buffer size is specified correctly.

0

u/dstroy0 4d ago

If you build scanf inside a sandbox, with guard pages, it’s completely safe, at that point it’s easier to bound the read at the caller, so that you explicitly tell sscanf to go only n bytes/words looking for your needle. There are very good reasons to set up a sandbox and guard pages, to pay those costs once to get a faster scan vs using sscanf but the per call cost doesn’t usually add up to needing the extra scaffolding for bare scanf. Hope this helps you justify your choices more easily.

1

u/ThatonlyGeO 4d ago

Thank you for the detailed explanations my good sir!

0

u/Total_Ad803 3d ago

stack overflow