On all versions of glibc, getline and getdelim do not NUL-terminate the
line buffer if the first read character is EOF as required by POSIX
[1]. The easiest way to demonstrate this is with this test program and
an empty file:

    $ cat main.c 
    #define _GNU_SOURCE 1
    #include <stdio.h>
    #include <stdlib.h>
    #ifdef USE_GETLINE
    # define GETLINE getline (&line, &line_size, fp)
    #else
    # define GETLINE getdelim (&line, &line_size, '\n', fp)
    #endif
    int
    main (void)
    {
      FILE *fp = fopen ("file", "r");
      if (fp == NULL)
        abort ();
      char *line = malloc (1);
      size_t line_size = 1;
      if (line == NULL)
        abort ();
      line[0] = 'A';
      if (GETLINE != -1 || line[0] != '\0')
        {
          fprintf (stderr, "Expected first character to be NUL; got %c\n",
                   line[0]);
          abort ();
        }
      return 0;
    }
    $ touch file
    $ gcc main.c
    $ ./a.out 
    Expected first character to be NUL; got A
    Aborted (core dumped)
    $ gcc -DUSE_GETLINE main.c 
    $ ./a.out 
    Expected first character to be NUL; got A
    Aborted (core dumped)

I pushed a fix for this for glibc 2.43 [2].

Will push the attatched patches tomorrow, unless they need changes. :)

Collin

[1] https://sourceware.org/bugzilla/show_bug.cgi?id=28038
[2] 
https://forge.sourceware.org/glibc/glibc-mirror/commit/33eff78c8b28adc4963987880e10d96761f2a167

>From 8510e0f28f65d42f8d4d68efd3447125aecd1d26 Mon Sep 17 00:00:00 2001
Message-ID: <8510e0f28f65d42f8d4d68efd3447125aecd1d26.1760068419.git.collin.fu...@gmail.com>
From: Collin Funk <[email protected]>
Date: Thu, 9 Oct 2025 20:36:15 -0700
Subject: [PATCH 1/4] getdelim: Work around a glibc bug.

* m4/getdelim.m4 (gl_FUNC_GETDELIM): Check that the buffer is terminated
with a NUL character when the first character read is EOF. Guess that
the function does not work on glibc.
* doc/posix-functions/getdelim.texi: Mention the bug.
---
 ChangeLog                         |  8 +++++
 doc/posix-functions/getdelim.texi |  5 ++++
 m4/getdelim.m4                    | 50 +++++++++++++++++--------------
 3 files changed, 41 insertions(+), 22 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index ec2d9651a5..d5d2799955 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2025-10-09  Collin Funk  <[email protected]>
+
+	getdelim: Work around a glibc bug.
+	* m4/getdelim.m4 (gl_FUNC_GETDELIM): Check that the buffer is terminated
+	with a NUL character when the first character read is EOF. Guess that
+	the function does not work on glibc.
+	* doc/posix-functions/getdelim.texi: Mention the bug.
+
 2025-10-08  Bruno Haible  <[email protected]>
 
 	stdcountof-h tests: Test more kinds of string literals.
diff --git a/doc/posix-functions/getdelim.texi b/doc/posix-functions/getdelim.texi
index d166e61e3b..cb16922259 100644
--- a/doc/posix-functions/getdelim.texi
+++ b/doc/posix-functions/getdelim.texi
@@ -19,6 +19,11 @@ @node getdelim
 This function crashes when passed a pointer to a NULL buffer together with a
 pointer to a non-zero buffer size on some platforms:
 FreeBSD 8.0.
+@item
+This function does not null terminate the buffer when the first
+character read is EOF on some platforms:
+@c https://sourceware.org/PR28038
+glibc 2.42.
 @end itemize
 
 Portability problems not fixed by Gnulib:
diff --git a/m4/getdelim.m4 b/m4/getdelim.m4
index 63d8830649..35734aca84 100644
--- a/m4/getdelim.m4
+++ b/m4/getdelim.m4
@@ -1,5 +1,5 @@
 # getdelim.m4
-# serial 19
+# serial 20
 
 dnl Copyright (C) 2005-2007, 2009-2025 Free Software Foundation, Inc.
 dnl
@@ -37,6 +37,7 @@ AC_DEFUN([gl_FUNC_GETDELIM]
            gl_cv_func_working_getdelim=no ;;
          *)
            echo fooNbarN | tr -d '\012' | tr N '\012' > conftest.data
+           touch conftest.empty
            AC_RUN_IFELSE([AC_LANG_SOURCE([[
 #    include <stdio.h>
 #    include <stdlib.h>
@@ -44,6 +45,7 @@ AC_DEFUN([gl_FUNC_GETDELIM]
     int main ()
     {
       FILE *in = fopen ("./conftest.data", "r");
+      int result = 0;
       if (!in)
         return 1;
       {
@@ -53,7 +55,7 @@ AC_DEFUN([gl_FUNC_GETDELIM]
         size_t siz = 0;
         int len = getdelim (&line, &siz, '\n', in);
         if (!(len == 4 && line && strcmp (line, "foo\n") == 0))
-          { free (line); fclose (in); return 2; }
+          result |= 2;
         free (line);
       }
       {
@@ -62,36 +64,40 @@ AC_DEFUN([gl_FUNC_GETDELIM]
         char *line = NULL;
         size_t siz = (size_t)(~0) / 4;
         if (getdelim (&line, &siz, '\n', in) == -1)
-          { fclose (in); return 3; }
+          result |= 4;
         free (line);
       }
       fclose (in);
-      return 0;
+      {
+        /* Test that reading EOF as the first character sets the first byte
+           in the buffer to NUL.  This fails on glibc 2.42 and earlier.  */
+        in = fopen ("./conftest.empty", "r");
+        if (!in)
+          return 1;
+        char *line = malloc (1);
+        line[0] = 'A';
+        size_t siz = 1;
+        if (getdelim (&line, &siz, '\n', in) != -1 || line[0] != '\0')
+          result |= 8;
+        free (line);
+      }
+      fclose (in);
+      return result;
     }
     ]])],
              [gl_cv_func_working_getdelim=yes],
              [gl_cv_func_working_getdelim=no],
-             [dnl We're cross compiling.
-              dnl Guess it works on glibc2 systems and musl systems.
-              AC_EGREP_CPP([Lucky GNU user],
-                [
-#include <features.h>
-#ifdef __GNU_LIBRARY__
- #if (__GLIBC__ >= 2) && !defined __UCLIBC__
-  Lucky GNU user
- #endif
-#endif
-                ],
-                [gl_cv_func_working_getdelim="guessing yes"],
-                [case "$host_os" in
-                   *-musl* | midipix*) gl_cv_func_working_getdelim="guessing yes" ;;
-                   *)                  gl_cv_func_working_getdelim="$gl_cross_guess_normal" ;;
-                 esac
-                ])
+             [case "$host_os" in
+                                    # Guess yes on musl.
+                *-musl* | midipix*) gl_cv_func_working_getdelim="guessing yes" ;;
+                                    # Guess no on glibc.
+                *-gnu* | gnu*)      gl_cv_func_working_getdelim="guessing no" ;;
+                *)                  gl_cv_func_working_getdelim="$gl_cross_guess_normal" ;;
+              esac
              ])
            ;;
        esac
-      ])
+       rm -f conftest.data conftest.empty])
     case "$gl_cv_func_working_getdelim" in
       *yes) ;;
       *) REPLACE_GETDELIM=1 ;;
-- 
2.51.0

>From 3a51be8d9ef54457a09e9f48fdb8460eb5890bf7 Mon Sep 17 00:00:00 2001
Message-ID: <3a51be8d9ef54457a09e9f48fdb8460eb5890bf7.1760068419.git.collin.fu...@gmail.com>
In-Reply-To: <8510e0f28f65d42f8d4d68efd3447125aecd1d26.1760068419.git.collin.fu...@gmail.com>
References: <8510e0f28f65d42f8d4d68efd3447125aecd1d26.1760068419.git.collin.fu...@gmail.com>
From: Collin Funk <[email protected]>
Date: Thu, 9 Oct 2025 20:40:05 -0700
Subject: [PATCH 2/4] getdelim tests: Add a test for the glibc bug.

* tests/test-getdelim.c (main): Check that the buffer is terminated with
a NUL character when the first character read is EOF.
---
 ChangeLog             | 4 ++++
 tests/test-getdelim.c | 4 ++++
 2 files changed, 8 insertions(+)

diff --git a/ChangeLog b/ChangeLog
index d5d2799955..301976a136 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2025-10-09  Collin Funk  <[email protected]>
 
+	getdelim tests: Add a test for the glibc bug.
+	* tests/test-getdelim.c (main): Check that the buffer is terminated with
+	a NUL character when the first character read is EOF.
+
 	getdelim: Work around a glibc bug.
 	* m4/getdelim.m4 (gl_FUNC_GETDELIM): Check that the buffer is terminated
 	with a NUL character when the first character read is EOF. Guess that
diff --git a/tests/test-getdelim.c b/tests/test-getdelim.c
index b7911464e3..3696d0f5ab 100644
--- a/tests/test-getdelim.c
+++ b/tests/test-getdelim.c
@@ -84,7 +84,11 @@ main (void)
   ASSERT (memcmp (line, "d\0f", 4) == 0);
   ASSERT (3 < len);
 
+  /* Test that reading an EOF will terminate the buffer with a NUL
+     character.  */
   result = getdelim (&line, &len, 'n', f);
+  ASSERT (0 < len);
+  ASSERT (line[0] == '\0');
   ASSERT (result == -1);
 
   free (line);
-- 
2.51.0

>From 2822c9a80c47a39856d95abd29dd113053600411 Mon Sep 17 00:00:00 2001
Message-ID: <2822c9a80c47a39856d95abd29dd113053600411.1760068419.git.collin.fu...@gmail.com>
In-Reply-To: <8510e0f28f65d42f8d4d68efd3447125aecd1d26.1760068419.git.collin.fu...@gmail.com>
References: <8510e0f28f65d42f8d4d68efd3447125aecd1d26.1760068419.git.collin.fu...@gmail.com>
From: Collin Funk <[email protected]>
Date: Thu, 9 Oct 2025 20:41:09 -0700
Subject: [PATCH 3/4] getline: Work around a glibc bug.

* m4/getline.m4 (gl_FUNC_GETLINE): Check that the buffer is terminated
with a NUL character when the first character read is EOF. Guess that
the function does not work on glibc.
* doc/posix-functions/getline.texi: Mention the bug.
---
 ChangeLog                        |  6 ++++
 doc/posix-functions/getline.texi |  5 ++++
 m4/getline.m4                    | 50 ++++++++++++++++++--------------
 3 files changed, 39 insertions(+), 22 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 301976a136..204b8f2de9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2025-10-09  Collin Funk  <[email protected]>
 
+	getline: Work around a glibc bug.
+	* m4/getline.m4 (gl_FUNC_GETLINE): Check that the buffer is terminated
+	with a NUL character when the first character read is EOF. Guess that
+	the function does not work on glibc.
+	* doc/posix-functions/getline.texi: Mention the bug.
+
 	getdelim tests: Add a test for the glibc bug.
 	* tests/test-getdelim.c (main): Check that the buffer is terminated with
 	a NUL character when the first character read is EOF.
diff --git a/doc/posix-functions/getline.texi b/doc/posix-functions/getline.texi
index 6edc3a56ef..ed67385694 100644
--- a/doc/posix-functions/getline.texi
+++ b/doc/posix-functions/getline.texi
@@ -22,6 +22,11 @@ @node getline
 This function crashes when passed a pointer to a NULL buffer together with a
 pointer to a non-zero buffer size on some platforms:
 FreeBSD 8.0.
+@item
+This function does not null terminate the buffer when the first
+character read is EOF on some platforms:
+@c https://sourceware.org/PR28038
+glibc 2.42.
 @end itemize
 
 Portability problems not fixed by Gnulib:
diff --git a/m4/getline.m4 b/m4/getline.m4
index b97b801124..9ef0810c5f 100644
--- a/m4/getline.m4
+++ b/m4/getline.m4
@@ -1,5 +1,5 @@
 # getline.m4
-# serial 33
+# serial 34
 
 dnl Copyright (C) 1998-2003, 2005-2007, 2009-2025 Free Software Foundation,
 dnl Inc.
@@ -31,6 +31,7 @@ AC_DEFUN([gl_FUNC_GETLINE]
     AC_CACHE_CHECK([for working getline function],
       [am_cv_func_working_getline],
       [echo fooNbarN | tr -d '\012' | tr N '\012' > conftest.data
+       touch conftest.empty
        AC_RUN_IFELSE([AC_LANG_SOURCE([[
 #    include <stdio.h>
 #    include <stdlib.h>
@@ -38,6 +39,7 @@ AC_DEFUN([gl_FUNC_GETLINE]
     int main ()
     {
       FILE *in = fopen ("./conftest.data", "r");
+      int result = 0;
       if (!in)
         return 1;
       {
@@ -47,7 +49,7 @@ AC_DEFUN([gl_FUNC_GETLINE]
         size_t siz = 0;
         int len = getline (&line, &siz, in);
         if (!(len == 4 && line && strcmp (line, "foo\n") == 0))
-          { free (line); fclose (in); return 2; }
+          result |= 2;
         free (line);
       }
       {
@@ -56,34 +58,38 @@ AC_DEFUN([gl_FUNC_GETLINE]
         char *line = NULL;
         size_t siz = (size_t)(~0) / 4;
         if (getline (&line, &siz, in) == -1)
-          { fclose (in); return 3; }
+          result |= 4;
         free (line);
       }
       fclose (in);
-      return 0;
+      {
+        /* Test that reading EOF as the first character sets the first byte
+           in the buffer to NUL.  This fails on glibc 2.42 and earlier.  */
+        in = fopen ("./conftest.empty", "r");
+        if (!in)
+          return 1;
+        char *line = malloc (1);
+        line[0] = 'A';
+        size_t siz = 1;
+        if (getline (&line, &siz, in) != -1 || line[0] != '\0')
+          result |= 8;
+        free (line);
+      }
+      fclose (in);
+      return result;
     }
     ]])],
          [am_cv_func_working_getline=yes],
          [am_cv_func_working_getline=no],
-         [dnl We're cross compiling.
-          dnl Guess it works on glibc2 systems and musl systems.
-          AC_EGREP_CPP([Lucky GNU user],
-            [
-#include <features.h>
-#ifdef __GNU_LIBRARY__
- #if (__GLIBC__ >= 2) && !defined __UCLIBC__
-  Lucky GNU user
- #endif
-#endif
-            ],
-            [am_cv_func_working_getline="guessing yes"],
-            [case "$host_os" in
-               *-musl* | midipix*) am_cv_func_working_getline="guessing yes" ;;
-               *)                  am_cv_func_working_getline="$gl_cross_guess_normal" ;;
-             esac
-            ])
+         [case "$host_os" in
+                                # Guess yes on musl.
+            *-musl* | midipix*) am_cv_func_working_getline="guessing yes" ;;
+                                # Guess no on glibc.
+            *-gnu* | gnu*)      am_cv_func_working_getline="guessing no" ;;
+            *)                  am_cv_func_working_getline="$gl_cross_guess_normal" ;;
+          esac
          ])
-      ])
+      rm -f conftest.data conftest.empty])
   else
     am_cv_func_working_getline=no
     case "$gl_cv_onwards_func_getline" in
-- 
2.51.0

>From fc1a9c75e2ed1589ec94d752b275b1f301c31864 Mon Sep 17 00:00:00 2001
Message-ID: <fc1a9c75e2ed1589ec94d752b275b1f301c31864.1760068419.git.collin.fu...@gmail.com>
In-Reply-To: <8510e0f28f65d42f8d4d68efd3447125aecd1d26.1760068419.git.collin.fu...@gmail.com>
References: <8510e0f28f65d42f8d4d68efd3447125aecd1d26.1760068419.git.collin.fu...@gmail.com>
From: Collin Funk <[email protected]>
Date: Thu, 9 Oct 2025 20:42:05 -0700
Subject: [PATCH 4/4] getline tests: Add a test for the glibc bug.

* tests/test-getline.c (main): Check that the buffer is terminated with
a NUL character when the first character read is EOF.
---
 ChangeLog            | 4 ++++
 tests/test-getline.c | 4 ++++
 2 files changed, 8 insertions(+)

diff --git a/ChangeLog b/ChangeLog
index 204b8f2de9..63fd83a1e9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2025-10-09  Collin Funk  <[email protected]>
 
+	getline tests: Add a test for the glibc bug.
+	* tests/test-getline.c (main): Check that the buffer is terminated with
+	a NUL character when the first character read is EOF.
+
 	getline: Work around a glibc bug.
 	* m4/getline.m4 (gl_FUNC_GETLINE): Check that the buffer is terminated
 	with a NUL character when the first character read is EOF. Guess that
diff --git a/tests/test-getline.c b/tests/test-getline.c
index 66c44d5dc9..bc559c6dc5 100644
--- a/tests/test-getline.c
+++ b/tests/test-getline.c
@@ -84,7 +84,11 @@ main (void)
   ASSERT (memcmp (line, "d\0f", 4) == 0);
   ASSERT (3 < len);
 
+  /* Test that reading an EOF will terminate the buffer with a NUL
+     character.  */
   result = getline (&line, &len, f);
+  ASSERT (0 < len);
+  ASSERT (line[0] == '\0');
   ASSERT (result == -1);
 
   free (line);
-- 
2.51.0

Reply via email to