mirror of git://sourceware.org/git/glibc.git
libio: null terminate the buffer upon initial allocation in getdelim
Commit 33eff78c8b caused issues in nbdkit
which had code similar to this to get the last line of the file:
while (getline (&line, &len, fp) != -1)
;
/* Process LINE. */
After that commit, line[0] would be equal to '\0' instead of containing
the last line of the file like before that commit. A recent POSIX issue
clarified that the behavior before and after that commit are allowed,
since the contents of LINE are unspecified after -1 is returned
[1]. However, some programs rely on the previous behavior.
This patch null terminates the buffer upon getdelim/getline's initial
allocation. This is compatible with previous glibc versions, while also
protecting the caller from reading uninitialized memory if the file is
empty, as long as getline/getdelim does the initial allocation.
[1] https://www.austingroupbugs.net/bug_view_page.php?bug_id=1953
Suggested-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
This commit is contained in:
parent
e2b00d59eb
commit
866fa41ef8
|
|
@ -63,7 +63,11 @@ __getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp)
|
||||||
{
|
{
|
||||||
*n = 120;
|
*n = 120;
|
||||||
*lineptr = (char *) malloc (*n);
|
*lineptr = (char *) malloc (*n);
|
||||||
if (*lineptr == NULL)
|
/* Null terminate the buffer upon allocation otherwise it will not be
|
||||||
|
null-terminated upon reading from an empty file. */
|
||||||
|
if (*lineptr != NULL)
|
||||||
|
(*lineptr)[0] = '\0';
|
||||||
|
else
|
||||||
{
|
{
|
||||||
fseterr_unlocked (fp);
|
fseterr_unlocked (fp);
|
||||||
result = -1;
|
result = -1;
|
||||||
|
|
@ -77,7 +81,6 @@ __getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp)
|
||||||
if (__underflow (fp) == EOF)
|
if (__underflow (fp) == EOF)
|
||||||
{
|
{
|
||||||
result = -1;
|
result = -1;
|
||||||
(*lineptr)[0] = '\0';
|
|
||||||
goto unlock_return;
|
goto unlock_return;
|
||||||
}
|
}
|
||||||
len = fp->_IO_read_end - fp->_IO_read_ptr;
|
len = fp->_IO_read_end - fp->_IO_read_ptr;
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#include <support/check.h>
|
#include <support/check.h>
|
||||||
#include <support/support.h>
|
#include <support/support.h>
|
||||||
|
|
@ -51,20 +52,53 @@ do_test (void)
|
||||||
xfclose (memstream);
|
xfclose (memstream);
|
||||||
free (lineptr);
|
free (lineptr);
|
||||||
|
|
||||||
/* Test that getdelim NUL terminates upon reading an EOF from an empty
|
/* Test that we null-terminate the buffer upon allocating it (BZ #28038). */
|
||||||
file (BZ #28038). This test fails on glibc 2.42 and earlier. */
|
lineptr = NULL;
|
||||||
lineptr = xmalloc (1);
|
linelen = 0;
|
||||||
lineptr[0] = 'A';
|
|
||||||
linelen = 1;
|
|
||||||
char *file_name;
|
char *file_name;
|
||||||
TEST_VERIFY_EXIT (create_temp_file ("tst-getdelim.", &file_name) != -1);
|
TEST_VERIFY_EXIT (create_temp_file ("tst-getdelim.", &file_name) != -1);
|
||||||
FILE *fp = fopen (file_name, "r");
|
FILE *fp = fopen (file_name, "w+");
|
||||||
|
free (file_name);
|
||||||
TEST_VERIFY_EXIT (fp != NULL);
|
TEST_VERIFY_EXIT (fp != NULL);
|
||||||
TEST_VERIFY (getdelim (&lineptr, &linelen, '\n', fp) == -1);
|
TEST_VERIFY (getdelim (&lineptr, &linelen, '\n', fp) == -1);
|
||||||
|
TEST_VERIFY (feof (fp));
|
||||||
TEST_VERIFY (linelen > 0);
|
TEST_VERIFY (linelen > 0);
|
||||||
TEST_VERIFY (lineptr[0] == '\0');
|
TEST_VERIFY (lineptr[0] == '\0');
|
||||||
|
free (lineptr);
|
||||||
|
|
||||||
|
/* Test that we can read until -1 is returned and then access the last line
|
||||||
|
of the file. This behavior was broken by commit
|
||||||
|
33eff78c8b28adc4963987880e10d96761f2a167 and later fixed. */
|
||||||
|
lineptr = NULL;
|
||||||
|
linelen = 0;
|
||||||
|
char input[] = "a\nb\nc\n";
|
||||||
|
TEST_VERIFY_EXIT (fwrite (input, 1, sizeof input - 1, fp)
|
||||||
|
== sizeof input - 1);
|
||||||
|
TEST_VERIFY_EXIT (fseeko (fp, 0, SEEK_SET) == 0);
|
||||||
|
char expect[] = { 'a' - 1, '\n', '\0' };
|
||||||
|
for (int i = 0; i < 3; ++i)
|
||||||
|
{
|
||||||
|
++expect[0];
|
||||||
|
TEST_VERIFY (getdelim (&lineptr, &linelen, '\n', fp) == 2);
|
||||||
|
TEST_VERIFY (linelen > 2);
|
||||||
|
TEST_VERIFY (strcmp (lineptr, expect) == 0);
|
||||||
|
}
|
||||||
|
TEST_VERIFY (getdelim (&lineptr, &linelen, '\n', fp) == -1);
|
||||||
|
TEST_VERIFY (feof (fp));
|
||||||
|
TEST_VERIFY (linelen > 2);
|
||||||
|
TEST_VERIFY (strcmp (lineptr, expect) == 0);
|
||||||
|
|
||||||
|
/* Test the same thing without a newline. */
|
||||||
|
TEST_VERIFY_EXIT (fwrite ("d", 1, 1, fp) == 1);
|
||||||
|
TEST_VERIFY_EXIT (fseeko (fp, -1, SEEK_CUR) == 0);
|
||||||
|
TEST_VERIFY (getdelim (&lineptr, &linelen, '\n', fp) == 1);
|
||||||
|
TEST_VERIFY (linelen > 2);
|
||||||
|
TEST_VERIFY (strcmp (lineptr, "d") == 0);
|
||||||
|
TEST_VERIFY (getdelim (&lineptr, &linelen, '\n', fp) == -1);
|
||||||
|
TEST_VERIFY (feof (fp));
|
||||||
|
TEST_VERIFY (linelen > 2);
|
||||||
|
TEST_VERIFY (strcmp (lineptr, "d") == 0);
|
||||||
fclose (fp);
|
fclose (fp);
|
||||||
free (file_name);
|
|
||||||
free (lineptr);
|
free (lineptr);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -1279,7 +1279,12 @@ This function was originally a GNU extension, but was added in
|
||||||
POSIX.1-2008.
|
POSIX.1-2008.
|
||||||
|
|
||||||
If an error occurs or end of file is reached without any bytes read,
|
If an error occurs or end of file is reached without any bytes read,
|
||||||
@code{getline} returns @code{-1}.
|
@code{getline} returns @code{-1}. POSIX leaves the contents of
|
||||||
|
@code{*@var{lineptr}} undefined when @code{getline} returns @code{-1}.
|
||||||
|
If the @glibcadj{} implementation of @code{getline} allocates the
|
||||||
|
initial buffer it will null terminate it to prevent the caller from
|
||||||
|
reading uninitialized memory if no characters can be read from
|
||||||
|
@code{stream}.
|
||||||
@end deftypefun
|
@end deftypefun
|
||||||
|
|
||||||
@deftypefun ssize_t getdelim (char **restrict @var{lineptr}, size_t *restrict @var{n}, int @var{delimiter}, FILE *restrict @var{stream})
|
@deftypefun ssize_t getdelim (char **restrict @var{lineptr}, size_t *restrict @var{n}, int @var{delimiter}, FILE *restrict @var{stream})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue