Source: glibc Version: 2.28-2 Severity: important Tags: patch User: debian-68k@lists.debian.org Usertags: m68k
Hi! In 298d0e3129c0b5137f4989275b13fe30d0733c4d ("Consolidate Linux getdents{64} implementation"), upstream made changes to the getdents{64} implementation which breaks glibc on qemu-user. The result of that is that applications like dash behave erratically, such that for example pattern expansion no longer works. This means that "*" is not expanded to filenames: $ ls -l old/ total 148 -rw-r--r-- 1 glaubitz fbedv 16188 Jun 21 2003 chardraw.c -rw-r--r-- 1 glaubitz fbedv 7922 Jun 21 2003 fillpoly.c -rw-r--r-- 1 glaubitz fbedv 948 Jun 21 2003 readme -rw-r--r-- 1 glaubitz fbedv 31860 Jun 21 2003 to_atari.c -rw-r--r-- 1 glaubitz fbedv 13093 Jun 21 2003 to_eps.c -rw-r--r-- 1 glaubitz fbedv 6631 Jun 21 2003 to_mf.c -rw-r--r-- 1 glaubitz fbedv 3261 Jun 21 2003 to_pbm.c -rw-r--r-- 1 glaubitz fbedv 12854 Jun 21 2003 to_pcx.c -rw-r--r-- 1 glaubitz fbedv 10657 Jun 21 2003 to_pdf.c -rw-r--r-- 1 glaubitz fbedv 6697 Jun 21 2003 to_pm.c -rw-r--r-- 1 glaubitz fbedv 9463 Jun 21 2003 to_x11a.c -rw-r--r-- 1 glaubitz fbedv 10405 Jun 21 2003 to_x11.c $ ls -l old/* /bin/ls: cannot access 'old/*': No such file or directory $ dash is just one example. Other affected applications are cmake or qmake. The attached prelimenary patch by James Clarke fixes the problem for me. Thanks, Adrian > [1] https://sourceware.org/bugzilla/show_bug.cgi?id=23960 -- .''`. John Paul Adrian Glaubitz : :' : Debian Developer - glaub...@debian.org `. `' Freie Universitaet Berlin - glaub...@physik.fu-berlin.de `- GPG: 62FF 8A75 84E0 2956 9546 0006 7426 3B37 F5B5 F913
Description: Fix regression of glibc 2.28 on qemu-user The new getdents{64} implementation in glibc 2.28 breaks qemu-user making certain applications like dash fail to work properly. . Author: James Clarke <jrt...@jrtc27.com> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=23960 Last-Update: 2018-12-11 Index: glibc-2.28/sysdeps/unix/sysv/linux/alpha/getdents.c =================================================================== --- glibc-2.28.orig/sysdeps/unix/sysv/linux/alpha/getdents.c +++ glibc-2.28/sysdeps/unix/sysv/linux/alpha/getdents.c @@ -1,11 +1,13 @@ /* Although Alpha defines _DIRENT_MATCHES_DIRENT64, 'struct dirent' and 'struct dirent64' have slight different internal layout with d_ino being a __ino_t on non-LFS version with an extra __pad field which should - be zeroed. */ + be zero. */ #include <dirent.h> #undef _DIRENT_MATCHES_DIRENT64 #define _DIRENT_MATCHES_DIRENT64 0 -#define DIRENT_SET_DP_INO(dp, value) \ - do { (dp)->d_ino = (value); (dp)->__pad = 0; } while (0) +#define DIRENT_CHECK_DP_INO_SIZE(kdp, dp) \ + (sizeof ((kdp)->d_ino) == sizeof ((dp)->d_ino) + sizeof ((dp)->__pad)) +#define DIRENT_CHECK_DP_INO(dp) \ + (dp)->__pad == 0 #include <sysdeps/unix/sysv/linux/getdents.c> Index: glibc-2.28/sysdeps/unix/sysv/linux/getdents.c =================================================================== --- glibc-2.28.orig/sysdeps/unix/sysv/linux/getdents.c +++ glibc-2.28/sysdeps/unix/sysv/linux/getdents.c @@ -24,92 +24,85 @@ # include <string.h> # include <errno.h> -# ifndef DIRENT_SET_DP_INO -# define DIRENT_SET_DP_INO(dp, value) (dp)->d_ino = (value) +/* For Linux we need a special version of this file since the + definition of `struct dirent' is not the same for the kernel and + the libc; the kernel places d_type after the variable-length d_name, + but we place it at a fixed offset just before d_name. */ + +struct kernel_dirent + { + __syscall_ulong_t d_ino; + __syscall_ulong_t d_off; + unsigned short d_reclen; + char d_name[256]; + }; + +# ifndef DIRENT_CHECK_DP_INO_SIZE +# define DIRENT_CHECK_DP_INO_SIZE(kdp, dp) \ + (sizeof ((kdp)->d_ino) == sizeof ((dp)->d_ino)) # endif -/* Pack the dirent64 struct down into 32-bit offset/inode fields, and - ensure that no overflow occurs. */ ssize_t __getdents (int fd, char *buf, size_t nbytes) { - union - { - /* For !_DIRENT_MATCHES_DIRENT64 kernel 'linux_dirent64' has the same - layout of 'struct dirent64'. */ - struct dirent64 k; - struct dirent u; - char b[1]; - } *kbuf = (void *) buf, *outp, *inp; - size_t kbytes = nbytes; - off64_t last_offset = -1; ssize_t retval; +# ifdef DIRENT_CHECK_DP_INO + off64_t last_offset = -1; +# endif -# define size_diff (offsetof (struct dirent64, d_name) \ - - offsetof (struct dirent, d_name)) - char kbuftmp[sizeof (struct dirent) + size_diff]; - if (nbytes <= sizeof (struct dirent)) - kbuf = (void*) kbuftmp; - - retval = INLINE_SYSCALL_CALL (getdents64, fd, kbuf, kbytes); - if (retval == -1) - return -1; - - /* These two pointers might alias the same memory buffer. - Standard C requires that we always use the same type for them, - so we must use the union type. */ - inp = kbuf; - outp = (void *) buf; - - while (&inp->b < &kbuf->b + retval) + /* The d_ino and d_off fields in kernel_dirent and dirent must have + the same sizes and alignments. */ + _Static_assert (DIRENT_CHECK_DP_INO_SIZE ((struct kernel_dirent *) 0, + (struct dirent *) 0), "size and alignment"); + _Static_assert (sizeof (((struct kernel_dirent *) 0)->d_off) + == sizeof (((struct dirent *) 0)->d_off), "size and alignment"); + _Static_assert (offsetof (struct kernel_dirent, d_off) + == offsetof (struct dirent, d_off), "size and alignment"); + _Static_assert (offsetof (struct kernel_dirent, d_reclen) + == offsetof (struct dirent, d_reclen), "size and alignment"); + + retval = INLINE_SYSCALL (getdents, 3, fd, buf, nbytes); + + /* The kernel added the d_type value after the name. Change + this now. Also fix up d_ino as required for e.g. Alpha. */ + if (retval != -1) { - const size_t alignment = _Alignof (struct dirent); - /* Since inp->k.d_reclen is already aligned for the kernel - structure this may compute a value that is bigger - than necessary. */ - size_t old_reclen = inp->k.d_reclen; - size_t new_reclen = ((old_reclen - size_diff + alignment - 1) - & ~(alignment - 1)); - - /* Copy the data out of the old structure into temporary space. - Then copy the name, which may overlap if BUF == KBUF. */ - const uint64_t d_ino = inp->k.d_ino; - const int64_t d_off = inp->k.d_off; - const uint8_t d_type = inp->k.d_type; - - memmove (outp->u.d_name, inp->k.d_name, - old_reclen - offsetof (struct dirent64, d_name)); - - /* Now we have copied the data from INP and access only OUTP. */ - - DIRENT_SET_DP_INO (&outp->u, d_ino); - outp->u.d_off = d_off; - if ((sizeof (outp->u.d_ino) != sizeof (inp->k.d_ino) - && outp->u.d_ino != d_ino) - || (sizeof (outp->u.d_off) != sizeof (inp->k.d_off) - && outp->u.d_off != d_off)) - { - /* Overflow. If there was at least one entry before this one, - return them without error, otherwise signal overflow. */ - if (last_offset != -1) - { - __lseek64 (fd, last_offset, SEEK_SET); - return outp->b - buf; - } - return INLINE_SYSCALL_ERROR_RETURN_VALUE (EOVERFLOW); - } - - last_offset = d_off; - outp->u.d_reclen = new_reclen; - outp->u.d_type = d_type; + union + { + struct kernel_dirent k; + struct dirent u; + } *kbuf = (void *) buf; + + while ((char *) kbuf < buf + retval) + { + 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; + + /* Alpha only has a 32-bit d_ino, followed by a 32-bit __pad field, + * so check we haven't overflowed. */ +# ifdef DIRENT_CHECK_DP_INO + if (DIRENT_CHECK_DP_INO (&kbuf->u)) + { + /* Overflow. If there was at least one entry before this one, + return them without error, otherwise signal overflow. */ + if (last_offset != -1) + { + __lseek64 (fd, last_offset, SEEK_SET); + return (char *) kbuf - buf; + } + return INLINE_SYSCALL_ERROR_RETURN_VALUE (EOVERFLOW); + } - inp = (void *) inp + old_reclen; - outp = (void *) outp + new_reclen; + last_offset = kbuf->u.d_off; +#endif + + kbuf = (void *) ((char *) kbuf + kbuf->k.d_reclen); + } } - return outp->b - buf; + return retval; } -# undef DIRENT_SET_DP_INO - -#endif /* _DIRENT_MATCHES_DIRENT64 */ +#endif /* !_DIRENT_MATCHES_DIRENT64 */