aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2019-06-27 15:08:40 +0200
committerFlorian Weimer <fweimer@redhat.com>2019-06-27 15:08:40 +0200
commita620bd7935c4b2dc94e472e62bd9a5c9434ea7b7 (patch)
tree0741388f6c9acdf7621676f6bf4829f0bb7ac44f
parentd039da1c00e01f8d3c3d74f439a971eb73e3045e (diff)
downloadglibc-a620bd7935c4b2dc94e472e62bd9a5c9434ea7b7.tar.gz
glibc-a620bd7935c4b2dc94e472e62bd9a5c9434ea7b7.tar.xz
glibc-a620bd7935c4b2dc94e472e62bd9a5c9434ea7b7.zip
Linux: Adjust gedents64 buffer size to int range [BZ #24740]
The kernel interface uses type unsigned int, but there is an internal conversion to int, so INT_MAX is the correct limit. Part of the buffer will always be unused, but this is not a problem. Such huge buffers do not occur in practice anyway. Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
-rw-r--r--ChangeLog12
-rw-r--r--sysdeps/unix/sysv/linux/getdents64.c5
-rw-r--r--sysdeps/unix/sysv/linux/mips/mips64/getdents64.c6
-rw-r--r--sysdeps/unix/sysv/linux/tst-getdents64.c44
4 files changed, 67 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
index c10f372784e..306c942a458 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2019-06-27 Florian Weimer <fweimer@redhat.com>
+
+ [BZ #24740]
+ * sysdeps/unix/sysv/linux/getdents64.c (__getdents64): Adjust
+ buffer size if necessary.
+ * sysdeps/unix/sysv/linux/mips/mips64/getdents64.c (__getdents64):
+ Likewise.
+ * sysdeps/unix/sysv/linux/tst-getdents64.c (large_buffer_check):
+ New function.
+ (large_buffer_checks): Likewise.
+ (do_test): Call large_buffer_checks.
+
2019-06-26 H.J. Lu <hongjiu.lu@intel.com>
* sysdeps/i386/dl-lookupcfg.h: Moved to ...
diff --git a/sysdeps/unix/sysv/linux/getdents64.c b/sysdeps/unix/sysv/linux/getdents64.c
index a6dd22106db..5e3ef9994ea 100644
--- a/sysdeps/unix/sysv/linux/getdents64.c
+++ b/sysdeps/unix/sysv/linux/getdents64.c
@@ -19,11 +19,16 @@
#include <string.h>
#include <dirent.h>
#include <errno.h>
+#include <limits.h>
/* The kernel struct linux_dirent64 matches the 'struct dirent64' type. */
ssize_t
__getdents64 (int fd, void *buf, size_t nbytes)
{
+ /* The system call takes an unsigned int argument, and some length
+ checks in the kernel use an int type. */
+ if (nbytes > INT_MAX)
+ nbytes = INT_MAX;
return INLINE_SYSCALL_CALL (getdents64, fd, buf, nbytes);
}
libc_hidden_def (__getdents64)
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/getdents64.c b/sysdeps/unix/sysv/linux/mips/mips64/getdents64.c
index 1e22fa4325b..8bf3abb0e09 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/getdents64.c
+++ b/sysdeps/unix/sysv/linux/mips/mips64/getdents64.c
@@ -23,12 +23,18 @@
#include <sys/param.h>
#include <unistd.h>
#include <scratch_buffer.h>
+#include <limits.h>
ssize_t
__getdents64 (int fd, void *buf0, size_t nbytes)
{
char *buf = buf0;
+ /* The system call takes an unsigned int argument, and some length
+ checks in the kernel use an int type. */
+ if (nbytes > INT_MAX)
+ nbytes = INT_MAX;
+
#ifdef __NR_getdents64
ssize_t ret = INLINE_SYSCALL_CALL (getdents64, fd, buf, nbytes);
if (ret != -1)
diff --git a/sysdeps/unix/sysv/linux/tst-getdents64.c b/sysdeps/unix/sysv/linux/tst-getdents64.c
index c1f7721221e..24e77e04d81 100644
--- a/sysdeps/unix/sysv/linux/tst-getdents64.c
+++ b/sysdeps/unix/sysv/linux/tst-getdents64.c
@@ -19,6 +19,7 @@
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
+#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@@ -28,6 +29,47 @@
#include <support/xunistd.h>
#include <unistd.h>
+/* Called by large_buffer_checks below. */
+static void
+large_buffer_check (int fd, char *large_buffer, size_t large_buffer_size)
+{
+ xlseek (fd, 0, SEEK_SET);
+ ssize_t ret = getdents64 (fd, large_buffer, large_buffer_size);
+ if (ret < 0)
+ FAIL_EXIT1 ("getdents64 for buffer of %zu bytes failed: %m",
+ large_buffer_size);
+ if (ret < offsetof (struct dirent64, d_name))
+ FAIL_EXIT1 ("getdents64 for buffer of %zu returned small value %zd",
+ large_buffer_size, ret);
+}
+
+/* Bug 24740: Make sure that the system call argument is adjusted
+ properly for the int type. A large value should stay a large
+ value, and not wrap around to something small, causing the system
+ call to fail with EINVAL. */
+static void
+large_buffer_checks (int fd)
+{
+ size_t large_buffer_size;
+ if (!__builtin_add_overflow (UINT_MAX, 2, &large_buffer_size))
+ {
+ char *large_buffer = malloc (large_buffer_size);
+ if (large_buffer == NULL)
+ printf ("warning: could not allocate %zu bytes of memory,"
+ " subtests skipped\n", large_buffer_size);
+ else
+ {
+ large_buffer_check (fd, large_buffer, INT_MAX);
+ large_buffer_check (fd, large_buffer, (size_t) INT_MAX + 1);
+ large_buffer_check (fd, large_buffer, (size_t) INT_MAX + 2);
+ large_buffer_check (fd, large_buffer, UINT_MAX);
+ large_buffer_check (fd, large_buffer, (size_t) UINT_MAX + 1);
+ large_buffer_check (fd, large_buffer, (size_t) UINT_MAX + 2);
+ }
+ free (large_buffer);
+ }
+}
+
static int
do_test (void)
{
@@ -105,6 +147,8 @@ do_test (void)
rewinddir (reference);
}
+ large_buffer_checks (fd);
+
xclose (fd);
closedir (reference);
return 0;