fgets can read in up to 28 bytes into an 8 byte array.

char input[8];

...

fgets(&input[0], 28, stdin);

Type in 28 bytes of data when prompted, and you'll overflow the input variable. This means that the both guess and number will be overwritten with the first 8 bytes of data past the 8 bytes the array can hold. Any more data provided will continue to overwrite stack protections, and eventually the return address of the function (and beyond).

You can learn more about buffer overflows in this question.

This can be mitigated by making sure you don't read in more bytes than your buffer is allocated.

Answer from RoraΖ on Stack Exchange
🌐
Jvns.ca
jvns.ca › blog › 2013 › 10 › 28 › day-17-buffer-overflows
Day 17: How to write a buffer overflow exploit
October 28, 2013 - Here is a program with a vulnerability, made extra easy to exploit: #include <stdio.h> #include <string.h> char password[] = "super_secret"; void foo(void) { printf("You hacked me! Here is the secret password: %s\n", password); fflush(stdout); } int main(int argc, char *argv[]) { char buf[4]; printf("Here is the address of foo: %p\nWhat is your hacking text?
Discussions

c - Understanding the need for fflush() and problems associated with it - Stack Overflow
Only problem I can think of is that doing it too often is bad for performance. Also, you should know that closing a file or program termination implies an automatic flush. A short-running program will seldom need to call fflush() explicitly. More on stackoverflow.com
🌐 stackoverflow.com
fflush() regression
There was an error while loading. Please reload this page · should have printed ok, but instead gave a warning about not having fflushed More on github.com
🌐 github.com
9
November 6, 2023
c - Using fflush(stdin) - Stack Overflow
So a quick Google search for fflush(stdin) for clearing the input buffer reveals numerous websites warning against using it. And yet that's exactly how my CS professor taught the class to do it. H... More on stackoverflow.com
🌐 stackoverflow.com
What is the use of fflush(stdin) in c programming? - Stack Overflow
However, note that this use of fflush() is unportable. I've tested right now on a Linux machine, and it does not work, for example. More on stackoverflow.com
🌐 stackoverflow.com
🌐
SEI CERT
wiki.sei.cmu.edu › confluence › display › c › FIO23-C.+Do+not+exit+with+unflushed+data+in+stdout+or+stderr
FIO23-C. Do not exit with unflushed data in stdout or stderr - SEI CERT C Coding Standard - Confluence
#include <stdio.h> void cleanup(void) { /* Do cleanup */ printf("All cleaned up!\n"); if (fflush(stdout) == EOF) { /* Handle error */ } } int main(void) { atexit(cleanup); printf("Doing important stuff\n"); /* Do important stuff */ if (fflush(stdout) == EOF) { /* Handle error */ } return 0; } FIO23-C-EX1: Programs that do not send data to either output stream need not close them. FIO23-C-EX2: Programs that never run with buffered output streams need not close them. Failing to flush data buffered for standard output or standard error may result in lost data. Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Top answer
1 of 3
63

It's a little hard to say what "can be problems with" (excessive?) use of fflush. All kinds of things can be, or become, problems, depending on your goals and approaches. Probably a better way to look at this is what the intent of fflush is.

The first thing to consider is that fflush is defined only on output streams. An output stream collects "things to write to a file" into a large(ish) buffer, and then writes that buffer to the file. The point of this collecting-up-and-writing-later is to improve speed/efficiency, in two ways:

  • On modern OSes, there's some penalty for crossing the user/kernel protection boundary (the system has to change some protection information in the CPU, etc). If you make a large number of OS-level write calls, you pay that penalty for each one. If you collect up, say, 8192 or so individual writes into one large buffer and then make one call, you remove most of that overhead.
  • On many modern OSes, each OS write call will try to optimize file performance in some way, e.g., by discovering that you've extended a short file to a longer one, and it would be good to move the disk block from point A on the disk to point B on the disk, so that the longer data can fit contiguously. (On older OSes, this is a separate "defragmentation" step you might run manually. You can think of this as the modern OS doing dynamic, instantaneous defragmentation.) If you were to write, say, 500 bytes, and then another 200, and then 700, and so on, it will do a lot of this work; but if you make one big call with, say, 8192 bytes, the OS can allocate a large block once, and put everything there and not have to re-defragment later.

So, the folks who provide your C library and its stdio stream implementation do whatever is appropriate on your OS to find a "reasonably optimal" block size, and to collect up all output into chunk of that size. (The numbers 4096, 8192, 16384, and 65536 often, today, tend to be good ones, but it really depends on the OS, and sometimes the underlying file system as well. Note that "bigger" is not always "better": streaming data in chunks of four gigabytes at a time will probably perform worse than doing it in chunks of 64 Kbytes, for instance.)

But this creates a problem. Suppose you're writing to a file, such as a log file with date-and-time stamps and messages, and your code is going to keep writing to that file later, but right now, it wants to suspend for a while and let a log-analyzer read the current contents of the log file. One option is to use fclose to close the log file, then fopen to open it again in order to append more data later. It's more efficient, though, to push any pending log messages to the underlying OS file, but keep the file open. That's what fflush does.

Buffering also creates another problem. Suppose your code has some bug, and it sometimes crashes but you're not sure if it's about to crash. And suppose you've written something and it's very important that this data get out to the underlying file system. You can call fflush to push the data through to the OS, before calling your potentially-bad code that might crash. (Sometimes this is good for debugging.)

Or, suppose you're on a Unix-like system, and have a fork system call. This call duplicates the entire user-space (makes a clone of the original process). The stdio buffers are in user space, so the clone has the same buffered-up-but-not-yet-written data that the original process had, at the time of the fork call. Here again, one way to solve the problem is to use fflush to push buffered data out just before doing the fork. If everything is out before the fork, there's nothing to duplicate; the fresh clone won't ever attempt to write the buffered-up data, as it no longer exists.

The more fflush-es you add, the more you're defeating the original idea of collecting up large chunks of data. That is, you are making a tradeoff: large chunks are more efficient, but are causing some other problem, so you make the decision: "be less efficient here, to solve a problem more important than mere efficiency". You call fflush.

Sometimes the problem is simply "debug the software". In that case, instead of repeatedly calling fflush, you can use functions like setbuf and setvbuf to alter the buffering behavior of a stdio stream. This is more convenient (fewer, or even no, code changes required—you can control the set-buffering call with a flag) than adding a lot of fflush calls, so that could be considered a "problem with use (or excessive-use) of fflush".

2 of 3
2

Well, @torek's answer is almost perfect, but there's one point which is not so accurate.

The first thing to consider is that fflush is defined only on output streams.

According to man fflush, fflush can also be used in input streams:

For output streams, fflush() forces a write of all user-space buffered data for the given output or update stream via the stream's underlying write function. For input streams, fflush() discards any buffered data that has been fetched from the underlying file, but has not been consumed by the application. The open status of the stream is unaffected. So, when used in input, fflush just discard it.

Here is a demo to illustrate it:

#include<stdio.h>

#define MAXLINE 1024

int main(void) {
  char buf[MAXLINE];

  printf("prompt: ");
  while (fgets(buf, MAXLINE, stdin) != NULL)
    fflush(stdin);
    if (fputs(buf, stdout) == EOF)
      printf("output err");

  exit(0);
}
🌐
Quora
quora.com › Is-fflush-stdin-unsafe-to-use
Is fflush(stdin) unsafe to use? - Quora
Answer: Yes, almost certainly. The standard defines the behavior of [code c]fflush[/code] only for output streams. Unlike [code c]fflush(stdout)[/code] which flushes any buffered output to the console, [code c]fflush(stdin)[/code] may actually discard an unpredictable amount of input from the use...
🌐
Medium
medium.com › @iphelix › open-security-training-introduction-to-software-exploits-uninitialized-variable-overflow-811a7cd75bd8
Open Security Training — Introduction to Software Exploits — Uninitialized Variable Overflow | by Peter Kacherginsky | Medium
November 26, 2018 - The following walkthrough goes into the exact exploitation steps for this class of vulnerabilities. Below is the source code for the uninit_overflow.c which you can also find in the labs directory of the class virtual machine. #include <stdio.h> #include <stdlib.h> #include <string.h> int do_auth(void) { char username[1024]; char password[1024]; printf("Username: "); fgets(username,1024,stdin); fflush(stdin); printf("Password: "); fgets(password,1024,stdin); printf("username at: 0x%x\n", &username); printf("password at 0x%x\n", &password); if (!strcmp(username, "user") && !strcmp(password, "password") == 0) { return 0; } return -1; } int log_error(int farray, char *msg) { char *err, *mesg; char buffer[24]; printf("mesg: 0x%x\n", mesg); memset(buffer,0x00,sizeof(buffer)); sprintf(buffer, "Error: %s", mesg); printf("%s\n", buffer); return 0; } int main(void) { switch(do_auth()) { case…
🌐
GitHub
github.com › emscripten-core › emscripten › issues › 20622
fflush() regression · Issue #20622 · emscripten-core/emscripten
November 6, 2023 - fflush() regression#20622 · Copy link · juj · opened · on Nov 6, 2023 · Issue body actions · a.cpp · #include <stdio.h> int main () { printf("ok"); fflush(stdout); } Results in · should have printed ok, but instead gave a warning about not having fflushed.
Author   juj
🌐
Apple
support.apple.com › en-is › 103713
About the security content of iOS 9 - Apple Support (IS)
November 6, 2023 - Description: A memory corruption issue existed in the fflush function.
Find elsewhere
🌐
Microsoft Learn
learn.microsoft.com › en-us › cpp › c-runtime-library › reference › fflush
fflush | Microsoft Learn
December 2, 2022 - If fflush returns EOF, data may have been lost due to a write failure.
Top answer
1 of 7
95

Simple: this is undefined behavior, since fflush is meant to be called on an output stream. This is an excerpt from the C standard:

int fflush(FILE *ostream);

ostream points to an output stream or an update stream in which the most recent operation was not input, the fflush function causes any unwritten data for that stream to be delivered to the host environment to be written to the file; otherwise, the behavior is undefined.

So it's not a question of "how bad" this is. fflush(stdin) is simply not portable, so you should not use it if you want your code to be portable between compilers.

2 of 7
54

Converting comments into an answer.

TL;DR — Portable code doesn't use fflush(stdin)

The rest of this answer explains why portable code does not use fflush(stdin). It is tempting to add "reliable code doesn't use fflush(stdin)", which is also generally true.

Standard C and POSIX leave fflush(stdin) as undefined behaviour

The POSIX, C and C++ standards for fflush() explicitly state that the behaviour is undefined (because stdin is an input stream), but none of them prevent a system from defining it.

ISO/IEC 9899:2011 — the C11 Standard — says:

§7.21.5.2 The fflush function

¶2 If stream points to an output stream or an update stream in which the most recent operation was not input, the fflush function causes any unwritten data for that stream to be delivered to the host environment to be written to the file; otherwise, the behavior is undefined.

POSIX mostly defers to the C standard but it does mark this text as a C extension.

[CX] ⌦ For a stream open for reading, if the file is not already at EOF, and the file is one capable of seeking, the file offset of the underlying open file description shall be set to the file position of the stream, and any characters pushed back onto the stream by ungetc() or ungetwc() that have not subsequently been read from the stream shall be discarded (without further changing the file offset). ⌫

Note that terminals are not capable of seeking; neither are pipes or sockets.

Microsoft defines the behaviour of fflush(stdin) as a no-op

In 2015, Microsoft and the Visual Studio runtime used to define the behaviour of fflush() on an input stream like this (but the link leads to different text in 2021 — and even the text in the '2015' version is different):

If the stream is open for input, fflush clears the contents of the buffer.

M.M notes:

Cygwin is an example of a fairly common platform on which fflush(stdin) does not clear the input.

This is why this answer version of my comment notes 'Microsoft and the Visual Studio runtime' — if you use a non-Microsoft C runtime library, the behaviour you see depends on that library.

Weather Vane pointed out to me in a comment to another question that, at some time before June 2021, Microsoft changed its description of fflush() compared with what was originally specified when this answer was written in 2015. It now says:

If the stream was opened in read mode, or if the stream has no buffer, the call to fflush has no effect, and any buffer is retained. A call to fflush negates the effect of any prior call to ungetc for the stream.

Caveat Lector: it is probably best not to rely on fflush(stdin) on any platform.

Linux documentation and practice seem to contradict each other

Surprisingly, Linux nominally documents the behaviour of fflush(stdin) too, and even defines it the same way (miracle of miracles). This quote is from 2015.

For input streams, fflush() discards any buffered data that has been fetched from the underlying file, but has not been consumed by the application.

In 2021, the quote changes to:

For input streams, fflush() discards any buffered data that has been fetched from the underlying file, but has not been consumed by the application. The open status of the stream is unaffected.

And another source for fflush(3) on Linux agrees (give or take paragraph breaks):

For input streams associated with seekable files (e.g., disk files, but not pipes or terminals), fflush() discards any buffered data that has been fetched from the underlying file, but has not been consumed by the application.

Neither of these explicitly addresses the points made by the POSIX specification about ungetc().

In 2021, zwol commented that the Linux documentation has been improved. It seems to me that there is still room for improvement.

In 2015, I was a bit puzzled and surprised at the Linux documentation saying that fflush(stdin) will work. Despite that suggestion, it most usually does not work on Linux. I just checked the documentation on Ubuntu 14.04 LTS; it says what is quoted above, but empirically, it does not work — at least when the input stream is a non-seekable device such as a terminal.

demo-fflush.c

#include <stdio.h>

int main(void)
{
    int c;
    if ((c = getchar()) != EOF)
    {
        printf("Got %c; enter some new data\n", c);
        fflush(stdin);
    }
    if ((c = getchar()) != EOF)
        printf("Got %c\n", c);

    return 0;
}

Example output

$ ./demo-fflush
Alliteration
Got A; enter some new data
Got l
$

This output was obtained on both Ubuntu 14.04 LTS and Mac OS X 10.11.2. To my understanding, it contradicts what the Linux manual says. If the fflush(stdin) operation worked, I would have to type a new line of text to get information for the second getchar() to read.

Given what the POSIX standard says, maybe a better demonstration is needed, and the Linux documentation should be clarified.

demo-fflush2.c

#include <stdio.h>

int main(void)
{
    int c;
    if ((c = getchar()) != EOF)
    {
        printf("Got %c\n", c);
        ungetc('B', stdin);
        ungetc('Z', stdin);
        if ((c = getchar()) == EOF)
        {
            fprintf(stderr, "Huh?!\n");
            return 1;
        }
        printf("Got %c after ungetc()\n", c);
        fflush(stdin);
    }
    if ((c = getchar()) != EOF)
        printf("Got %c\n", c);

    return 0;
}

Example output

Note that /etc/passwd is a seekable file. On Ubuntu, the first line looks like:

root:x:0:0:root:/root:/bin/bash

On Mac OS X, the first 4 lines look like:

##
# User Database
# 
# Note that this file is consulted directly only when the system is running

In other words, there is commentary at the top of the Mac OS X /etc/passwd file. The non-comment lines conform to the normal layout, so the root entry is:

root:*:0:0:System Administrator:/var/root:/bin/sh

Ubuntu 14.04 LTS:

$ ./demo-fflush2 < /etc/passwd
Got r
Got Z after ungetc()
Got o
$ ./demo-fflush2
Allotrope
Got A
Got Z after ungetc()
Got B
$

Mac OS X 10.11.2:

$ ./demo-fflush2 < /etc/passwd
Got #
Got Z after ungetc()
Got B
$

The Mac OS X behaviour ignores (or at least seems to ignore) the fflush(stdin) (thus not following POSIX on this issue). The Linux behaviour corresponds to the documented POSIX behaviour, but the POSIX specification is far more careful in what it says — it specifies a file capable of seeking, but terminals, of course, do not support seeking. It is also much less useful than the Microsoft specification.

Summary

Microsoft documents the behaviour of fflush(stdin), but that behaviour has changed between 2015 and 2021. Apparently, it works as documented on the Windows platform, using the native Windows compiler and C runtime support libraries.

Despite documentation to the contrary, it does not work on Linux when the standard input is a terminal, but it seems to follow the POSIX specification which is far more carefully worded. According to the C standard, the behaviour of fflush(stdin) is undefined. POSIX adds the qualifier 'unless the input file is seekable', which a terminal is not. The behaviour is not the same as Microsoft's.

Consequently, portable code does not use fflush(stdin). Code that is tied to Microsoft's platform may use it and it may work as expected, but beware of the portability issues.

POSIX way to discard unread terminal input from a file descriptor

The POSIX standard way to discard unread information from a terminal file descriptor (as opposed to a file stream like stdin) is illustrated at How can I flush unread data from a tty input queue on a Unix system. However, that is operating below the standard I/O library level.

🌐
Samsclass
samsclass.info › 127 › proj › p1-lbci.htm
Proj 1: Linux Buffer Overflow: Command Injection (10 pts. + 15 pts. extra credit)
December 16, 2014 - #include <string.h> #include <stdio.h> main(){ char name[200]; printf("What is your name?\n"); scanf("%s", name); bo(name, "uname -a"); } int bo(char *name, char *cmd){ char c[40]; char buffer[40]; printf("Name buffer address: %x\n", buffer); printf("Command buffer address: %x\n", c); strcpy(c, cmd); strcpy(buffer, name); printf("Goodbye, %s!\n", buffer); printf("Executing command: %s\n", c); fflush(stdout); system(c); }
🌐
CVE Details
cvedetails.com › cve › CVE-2014-8611
CVE-2014-8611 : The __sflush function in fflush.c in stdio in libc in FreeBSD 10.1 and the kerne
April 6, 2016 - CVE-2014-8611 : The __sflush function in fflush.c in stdio in libc in FreeBSD 10.1 and the kernel in Apple iOS before 9 mishandles failures of the write system call,
🌐
Gitbook
666isildur.gitbook.io › ethical-hacking › binary-exploitation-exploit-development › shellcoders-handbook › chapter-2-stack-overflows › linux-buffer-overflow-with-command-injection
Linux Buffer Overflow With Command Injection | Cyber Security / Ethical Hacking
May 7, 2020 - #include <string.h> #include <stdio.h> #include <stdlib.h> int bo(char *name, char *cmd){ char c[40], n[40]; printf("Name is at %p; command is at %p\n", n, c); strcpy(c, cmd); strcpy(n, name); printf("Goodbye, %s!\n", n); printf("Executing command: %s\n", c); fflush(stdout); system(c); } int main(){ char name[200]; printf("What is your name?\n"); scanf("%s", name); bo(name, "date"); }
🌐
sploitF-U-N
sploitfun.wordpress.com › 2015 › 06 › 07 › off-by-one-vulnerability-stack-based-2
Off-By-One Vulnerability (Stack Based) – sploitF-U-N
July 5, 2015 - As always enough of definitions, lets look into an off-by-one vulnerable code!! ... //vuln.c #include <stdio.h> #include <string.h> void foo(char* arg); void bar(char* arg); void foo(char* arg) { bar(arg); /* [1] */ } void bar(char* arg) { char buf[256]; strcpy(buf, arg); /* [2] */ } int main(int argc, char *argv[]) { if(strlen(argv[1])>256) { /* [3] */ printf("Attempted Buffer Overflow\n"); fflush(stdout); return -1; } foo(argv[1]); /* [4] */ return 0; }
🌐
Exploit-DB
exploit-db.com › exploits › 2013
Linux Kernel 2.6.17.4 - 'proc' Local Privilege Escalation - Linux local Exploit
July 15, 2006 - : error ("madvise"); prctl (PR_SET_DUMPABLE, 1, 0, 0, 0); sched_yield (); } else { nice(10); while (!(*c)); sched_yield (); execve (t, a, e); error ("failed"); } waitpid (i, NULL, 0); exit (0); } int main (int ac, char **av) { int i, j, k, s; char *p; memset (e, 0, sizeof (e)); memset (a, 0, sizeof (a)); a[0] = strdup (av[0]); a[1] = strdup (av[0]); a[2] = strdup (av[1]); if (ac < 2) error ("usage: binary <big file name>"); if (ac > 2) exploit (av[2]); printf ("\npreparing"); fflush (stdout); /* make setuid a.out */ memset (&ex, 0, sizeof (ex)); N_SET_MAGIC (ex, NMAGIC); N_SET_MACHTYPE (ex, M_
🌐
Linux Man Pages
man7.org › linux › man-pages › man3 › fflush.3.html
fflush(3) - Linux manual page
EBADF stream is not an open stream, or is not open for writing. The function fflush() may also fail and set errno for any of the errors specified for write(2).
🌐
SUSE
suse.com › security › cve › CVE-2006-5974.html
CVE-2006-5974 Common Vulnerabilities and Exposures | SUSE
fetchmail 6.3.5 and 6.3.6 before 6.3.6-rc4, when refusing a message delivered via the mda option, allows remote attackers to cause a denial of service (crash) via unknown vectors that trigger a NULL pointer dereference when calling the (1) ferror ...
🌐
ZeroBone
zerobone.net › blog › cs › call-stack-buffer-overflow
Call Stack - buffer overflow vulnerability | ZeroBone
March 3, 2021 - #include <stdio.h> #include <stdlib.h> void __attribute__((noinline)) fun(int a, int b, int c) { char buffer[16] = {0}; int* prevEbp = &a - 2; int* ret = &a - 1; printf("Buffer start: %p Buffer start pointer address: %p\n", buffer, &buffer); printf("Previous EBP: %p Value: %d Value as hex: %x\n", prevEbp, *prevEbp, *prevEbp); printf("Return address: %p Value: %x\n", ret, *ret); printf("Buffer end: %p\n", buffer + 16); fflush(stdout); } int main() { printf("Ptr size: %d bytes\n", sizeof(void*)); fun(1, 2, 3) return 0; }