Wie kann verhindert werden, dass scanf einen Pufferüberlauf in C verursacht?

Lesezeit: 7 Minuten

Benutzeravatar von goe
geh

Ich verwende diesen Code:

while ( scanf("%s", buf) == 1 ){

Was wäre der beste Weg, um einen möglichen Pufferüberlauf zu verhindern, damit Zeichenfolgen mit zufälliger Länge übergeben werden können?

Ich weiß, dass ich die Eingabezeichenfolge einschränken kann, indem ich zum Beispiel aufrufe:

while ( scanf("%20s", buf) == 1 ){

Aber ich würde es vorziehen, alle Benutzereingaben verarbeiten zu können. Oder kann dies nicht sicher mit scanf durchgeführt werden und ich sollte fgets verwenden?

Benutzeravatar von Jonathan Leffler
Jonathan Leffler

In ihrem Buch The Practice of Programming (das sehr lesenswert ist) diskutieren Kernighan und Pike dieses Problem, und sie lösen es, indem sie es verwenden snprintf() um die Zeichenfolge mit der richtigen Puffergröße für die Übergabe an die zu erstellen scanf() Familie von Funktionen. In Kraft:

int scanner(const char *data, char *buffer, size_t buflen)
{
    char format[32];
    if (buflen == 0)
        return 0;
    snprintf(format, sizeof(format), "%%%ds", (int)(buflen-1));
    return sscanf(data, format, buffer);
}

Beachten Sie, dass dies die Eingabe immer noch auf die Größe begrenzt, die als “Puffer” angegeben ist. Wenn Sie mehr Speicherplatz benötigen, müssen Sie die Speicherzuweisung vornehmen oder eine nicht standardmäßige Bibliotheksfunktion verwenden, die die Speicherzuweisung für Sie übernimmt.


Beachten Sie, dass die POSIX 2008 (2013)-Version der scanf() Funktionsfamilie unterstützt einen Formatmodifikator m (ein Zuweisungs-Zuweisungszeichen) für Zeichenfolgeneingaben (%s, %c, %[). Instead of taking a char * argument, it takes a char ** argument, and it allocates the necessary space for the value it reads:

char *buffer = 0;
if (sscanf(data, "%ms", &buffer) == 1)
{
    printf("String is: <<%s>>\n", buffer);
    free(buffer);
}

If the sscanf() function fails to satisfy all the conversion specifications, then all the memory it allocated for %ms-like conversions is freed before the function returns.

  • @Sam: Yes, it should be buflen-1 — Thank You. You then have to worry about unsigned underflow (wrapping to a rather large number), hence the if test. I’d be sorely tempted to replace that with an assert(), or back it up with an assert() before the if that fires during development if anyone is careless enough to pass 0 as the size. I’ve not carefully reviewed the documentation for what %0s means to sscanf() — the test might be better as if (buflen < 2).

    – Jonathan Leffler

    Sep 11, 2013 at 16:35

  • So snprintf writes some data to a string buffer, and sscanf reads from that created string. Where exactly does this replace scanf in that it reads from stdin?

    – krb686

    Apr 26, 2015 at 14:18

  • It’s also quite confusing that you use the word “format” for your result string and thus pass in “format” as the first argument to snprintf yet it is not the actual format parameter.

    – krb686

    Apr 26, 2015 at 14:24

  • @krb686: This code is written so that the data to be scanned is in the parameter data and hence sscanf() is appropriate. If you want to read from standard input instead, drop the data parameter and call scanf() instead. As to the choice of the name format for the variable that becomes the format string in the call to sscanf(), you are entitled to rename it if you wish, but its name is not inaccurate. I am not sure what alternative makes sense; would in_format make it any clearer? I am not planning to change it in this code; you may if you use this idea in your own code.

    – Jonathan Leffler

    Apr 26, 2015 at 15:28

  • @mabraham: It is still true under macOS Sierra 10.12.5 (up to 2017-06-06) — the scanf() on macOS is not documented as supporting %ms, useful though it would be.

    – Jonathan Leffler

    Jun 6, 2017 at 19:36


John Ledbetter's user avatar
John Ledbetter

If you are using gcc, you can use the GNU-extension a specifier to have scanf() allocate memory for you to hold the input:

int main()
{
  char *str = NULL;

  scanf ("%as", &str);
  if (str) {
      printf("\"%s\"\n", str);
      free(str);
  }
  return 0;
}

Edit: As Jonathan pointed out, you should consult the scanf man pages as the specifier might be different (%m) and you might need to enable certain defines when compiling.

  • That’s more an issue of using glibc (the GNU C Library) than of using the GNU C Compiler.

    – Jonathan Leffler

    Oct 25, 2009 at 20:24

  • And note that the POSIX 2008 standard provides the m modifier to do the same job. See scanf(). You’ll need to check whether the systems you use do support this modifier.

    – Jonathan Leffler

    Feb 22, 2014 at 21:15

  • GNU (as found on Ubuntu 13.10, at any rate) supports %ms. The notation %a is a synonym for %f (on output, it requests hexadecimal floating point data). The GNU man page for scanf() says: _ It is not available if the program is compiled with gcc -std=c99 or gcc -D_ISOC99_SOURCE (unless _GNU_SOURCE is also specified), in which case the a is interpreted as a specifier for floating-point numbers (see above)._

    – Jonathan Leffler

    Feb 22, 2014 at 21:16

Most of the time a combination of fgets and sscanf does the job. The other thing would be to write your own parser, if the input is well formatted. Also note your second example needs a bit of modification to be used safely:

#define LENGTH          42
#define str(x)          # x
#define xstr(x)         str(x)

/* ... */ 
int nc = scanf("%"xstr(LENGTH)"[^\n]%*[^\n]", Array); 

Das obige verwirft den Eingabestrom bis einschließlich des Zeilenumbruchs (\n) Charakter. Sie müssen ein hinzufügen getchar() diese zu konsumieren. Überprüfen Sie auch, ob Sie das Ende des Streams erreicht haben:

if (!feof(stdin)) { ...

und das war es auch schon.

  • Könntest du die setzen feof Code in einen größeren Kontext? Ich frage, da diese Funktion oft falsch verwendet wird.

    – Roland Illig

    24. Oktober 2016 um 21:52 Uhr

  • array muss sein char array[LENGTH+1];

    – jxh

    19. März 2019 um 22:58 Uhr

  • Stimmen Sie für die Präsentation des Berüchtigten ab !feof Muster ohne Kontext oder Erklärung und ohne es für 5 Jahre zu reparieren.

    – Roland Illig

    23. Dezember 2021 um 11:17 Uhr


Direkt verwenden scanf(3) und seine Varianten werfen eine Reihe von Problemen auf. Typischerweise werden Benutzer und nicht interaktive Anwendungsfälle in Form von Eingabezeilen definiert. Es kommt selten vor, dass mehr Zeilen das Problem lösen, wenn nicht genügend Objekte gefunden werden, aber das ist der Standardmodus für scanf. (Wenn ein Benutzer nicht wusste, dass er in der ersten Zeile eine Nummer eingeben muss, werden eine zweite und dritte Zeile wahrscheinlich nicht helfen.)

Zumindest wenn Sie fgets(3) Sie wissen, wie viele Eingabezeilen Ihr Programm benötigt, und Sie haben keine Pufferüberläufe …

Die Begrenzung der Länge der Eingabe ist definitiv einfacher. Sie könnten eine beliebig lange Eingabe akzeptieren, indem Sie eine Schleife verwenden, Stück für Stück einlesen und den Platz für die Zeichenfolge nach Bedarf neu zuweisen …

Aber das ist eine Menge Arbeit, also schneiden die meisten C-Programmierer die Eingabe einfach mit beliebiger Länge ab. Ich nehme an, Sie wissen das bereits, aber die Verwendung von fgets() wird es Ihnen nicht erlauben, beliebige Textmengen zu akzeptieren – Sie müssen immer noch ein Limit setzen.

  • Weiß jemand, wie man das dann mit scanf macht?

    – geh

    25. Oktober 2009 um 17:17 Uhr

  • Wenn Sie fgets in einer Schleife verwenden, können Sie beliebige Textmengen akzeptieren – behalten Sie einfach realloc()ing Ihren Puffer.

    – bdonlan

    25. Oktober 2009 um 18:53 Uhr

Es ist nicht so viel Arbeit, eine Funktion zu erstellen, die den benötigten Speicher für Ihre Zeichenfolge zuweist. Das ist eine kleine C-Funktion, die ich vor einiger Zeit geschrieben habe, ich benutze sie immer, um Strings einzulesen.

Es wird der gelesene String zurückgegeben oder wenn ein Speicherfehler auftritt NULL. Beachten Sie jedoch, dass Sie Ihren String mit free() versehen und immer auf seinen Rückgabewert prüfen müssen.

#define BUFFER 32

char *readString()
{
    char *str = malloc(sizeof(char) * BUFFER), *err;
    int pos;
    for(pos = 0; str != NULL && (str[pos] = getchar()) != '\n'; pos++)
    {
        if(pos % BUFFER == BUFFER - 1)
        {
            if((err = realloc(str, sizeof(char) * (BUFFER + pos + 1))) == NULL)
                free(str);
            str = err;
        }
    }
    if(str != NULL)
        str[pos] = '\0';
    return str;
}

  • Weiß jemand, wie man das dann mit scanf macht?

    – geh

    25. Oktober 2009 um 17:17 Uhr

  • Wenn Sie fgets in einer Schleife verwenden, können Sie beliebige Textmengen akzeptieren – behalten Sie einfach realloc()ing Ihren Puffer.

    – bdonlan

    25. Oktober 2009 um 18:53 Uhr

1420240cookie-checkWie kann verhindert werden, dass scanf einen Pufferüberlauf in C verursacht?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy