Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date: Fri, 7 Oct 2022 13:12:27 +0000
From: Pascal Cuoq <cuoq@...st-in-soft.com>
To: "libc-coord@...ts.openwall.com" <libc-coord@...ts.openwall.com>
Subject: fgets behavior for n<=0 (and =1)

Dear all,

the function fgets is defined in the C standard. In C17 7.21.7.2 (https://cigix.me/c17#7.21.7.2 ):

Synopsis

1
      #include <stdio.h>
      char *fgets(char * restrict s, int n,
            FILE * restrict stream);

Description

2
The fgets function reads at most one less than the number of characters specified by n from the stream pointed to by stream into the array pointed to by s. No additional characters are read after a new-line character (which is retained) or after end-of-file. A null character is written immediately after the last character read into the array.

Returns

3
The fgets function returns s if successful. If end-of-file is encountered and no characters have been read into the array, the contents of the array remain unchanged and a null pointer is returned. If a read error occurs during the operation, the array contents are indeterminate and a null pointer is returned.
___

It has come to our attention that it is possible to interpret the informal specification above in several ways for n <= 0, and that indeed several interpretations co-exist. All links are provided “as of this writing”, with snippets illustrating the interesting lines for clarity and against obsolescence of this message.

A/ For n <= 0, return a null pointer without setting errno.

Examples:

      if (n <= 0)       /* sanity check */
            return (NULL);
https://opensource.apple.com/source/Libc/Libc-594.9.5/stdio/FreeBSD/fgets.c.auto.html

  if (n <= 0) return NULL;
http://www.jbox.dk/sanos/source/lib/stdio.c.html

B/ Same as A but also do not write anything for n=1 (arguably incorrect)

  if (n < 2)                  /* sanity check */
    return 0;
https://github.com/eblot/newlib/blob/master/newlib/libc/stdio/fgets.c

C/ Have undefined behavior for n=INT_MIN

Since undefined behavior can be anything and worse, and the C standard's description does not explicitly allow “anything and worse” for any value of n, this is arguably incorrect.

      if (n--<=1) {
https://git.musl-libc.org/cgit/musl/tree/src/stdio/fgets.c

D/ Same as C (and rather a worse kind of UB in practice) but also write a nul character for n=0 (which is also arguably incorrect in itself)

      for (p = dst, max--; max > 0; max--) {
            if ((c = fgetc (fp)) == EOF)
                  break;
            *p++ = c;
            if (c == '\n')
                  break;
      }
      *p = 0;
http://mirror.fsf.org/pmon2000/pmon2000/src/lib/libc/fgets.c

E/ Document implementation-dependent result for n=1 and set errno to EINVAL for n<=0 in addition to returning a null pointer

From https://man.openbsd.org/fgets.3 :

> Whether fgets() can possibly fail with a size argument of 1 is implementation-dependent. On OpenBSD, fgets() will never return NULL when size is 1.
>
> ERRORS
> …
> [EINVAL]
>    The given size is less than or equal to 0.

Implementation:
      if (n <= 0) {           /* sanity check */
            errno = EINVAL;
            return (NULL);
      }
https://github.com/openbsd/src/blob/2207c4325726fdc5c4bcd0011af0fdf7d3dab137/lib/libc/stdio/fgets.c#L52-L55

Does anyone have any remarks about what the consensual behavior of fgets should be or how to get closer to a point where the consensual behavior is more widely implemented?

Pascal


Content of type "text/html" skipped

Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.