diff options
Diffstat (limited to 'sysdeps/unix/sysv/linux/getdents64.c')
-rw-r--r-- | sysdeps/unix/sysv/linux/getdents64.c | 89 |
1 files changed, 64 insertions, 25 deletions
diff --git a/sysdeps/unix/sysv/linux/getdents64.c b/sysdeps/unix/sysv/linux/getdents64.c index 3bde0cf4f0..bc140b5a7f 100644 --- a/sysdeps/unix/sysv/linux/getdents64.c +++ b/sysdeps/unix/sysv/linux/getdents64.c @@ -33,41 +33,80 @@ strong_alias (__getdents64, __getdents) # include <shlib-compat.h> # if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_2) -# include <olddirent.h> +# include <olddirent.h> +# include <unistd.h> -/* kernel definition of as of 3.2. */ -struct compat_linux_dirent +static ssize_t +handle_overflow (int fd, __off64_t offset, ssize_t count) { - /* Both d_ino and d_off are compat_ulong_t which are defined in all - architectures as 'u32'. */ - uint32_t d_ino; - uint32_t d_off; - unsigned short d_reclen; - char d_name[1]; -}; + /* If this is the first entry in the buffer, we can report the + error. */ + if (count == 0) + { + __set_errno (EOVERFLOW); + return -1; + } + + /* Otherwise, seek to the overflowing entry, so that the next call + will report the error, and return the data read so far.. */ + if (__lseek64 (fd, offset, SEEK_SET) != 0) + return -1; + return count; +} ssize_t __old_getdents64 (int fd, char *buf, size_t nbytes) { - ssize_t retval = INLINE_SYSCALL_CALL (getdents, fd, buf, nbytes); + /* We do not move the individual directory entries. This is only + possible if the target type (struct __old_dirent64) is smaller + than the source type. */ + _Static_assert (offsetof (struct __old_dirent64, d_name) + <= offsetof (struct dirent64, d_name), + "__old_dirent64 is larger than dirent64"); + _Static_assert (__alignof__ (struct __old_dirent64) + <= __alignof__ (struct dirent64), + "alignment of __old_dirent64 is larger than dirent64"); - /* The kernel added the d_type value after the name. Change this now. */ - if (retval != -1) + ssize_t retval = INLINE_SYSCALL_CALL (getdents64, fd, buf, nbytes); + if (retval > 0) { - union - { - struct compat_linux_dirent k; - struct dirent u; - } *kbuf = (void *) buf; - - while ((char *) kbuf < buf + retval) + char *p = buf; + char *end = buf + retval; + while (p < end) { - char d_type = *((char *) kbuf + kbuf->k.d_reclen - 1); - memmove (kbuf->u.d_name, kbuf->k.d_name, - strlen (kbuf->k.d_name) + 1); - kbuf->u.d_type = d_type; + struct dirent64 *source = (struct dirent64 *) p; + + /* Copy out the fixed-size data. */ + __ino_t ino = source->d_ino; + __off64_t offset = source->d_off; + unsigned int reclen = source->d_reclen; + unsigned char type = source->d_type; + + /* Check for ino_t overflow. */ + if (__glibc_unlikely (ino != source->d_ino)) + return handle_overflow (fd, offset, p - buf); + + /* Convert to the target layout. Use a separate struct and + memcpy to side-step aliasing issues. */ + struct __old_dirent64 result; + result.d_ino = ino; + result.d_off = offset; + result.d_reclen = reclen; + result.d_type = type; + + /* Write the fixed-sized part of the result to the + buffer. */ + size_t result_name_offset = offsetof (struct __old_dirent64, d_name); + memcpy (p, &result, result_name_offset); + + /* Adjust the position of the name if necessary. Copy + everything until the end of the record, including the + terminating NUL byte. */ + if (result_name_offset != offsetof (struct dirent64, d_name)) + memmove (p + result_name_offset, source->d_name, + reclen - offsetof (struct dirent64, d_name)); - kbuf = (void *) ((char *) kbuf + kbuf->k.d_reclen); + p += reclen; } } return retval; |