From 91ce40854d0b7f865cf5024ef95a8026b76096f3 Mon Sep 17 00:00:00 2001 From: Florian Weimer Date: Fri, 16 Aug 2013 09:38:52 +0200 Subject: CVE-2013-4237, BZ #14699: Buffer overflow in readdir_r * sysdeps/posix/dirstream.h (struct __dirstream): Add errcode member. * sysdeps/posix/opendir.c (__alloc_dir): Initialize errcode member. * sysdeps/posix/rewinddir.c (rewinddir): Reset errcode member. * sysdeps/posix/readdir_r.c (__READDIR_R): Enforce NAME_MAX limit. Return delayed error code. Remove GETDENTS_64BIT_ALIGNED conditional. * sysdeps/unix/sysv/linux/wordsize-64/readdir_r.c: Do not define GETDENTS_64BIT_ALIGNED. * sysdeps/unix/sysv/linux/i386/readdir64_r.c: Likewise. * manual/filesys.texi (Reading/Closing Directory): Document ENAMETOOLONG return value of readdir_r. Recommend readdir more strongly. * manual/conf.texi (Limits for Files): Add portability note to NAME_MAX, PATH_MAX. (Pathconf): Add portability note for _PC_NAME_MAX, _PC_PATH_MAX. --- sysdeps/posix/dirstream.h | 2 ++ sysdeps/posix/opendir.c | 1 + sysdeps/posix/readdir_r.c | 42 ++++++++++++++++++++++++++++++------------ sysdeps/posix/rewinddir.c | 1 + 4 files changed, 34 insertions(+), 12 deletions(-) (limited to 'sysdeps/posix') diff --git a/sysdeps/posix/dirstream.h b/sysdeps/posix/dirstream.h index a7a074d..8e8570d 100644 --- a/sysdeps/posix/dirstream.h +++ b/sysdeps/posix/dirstream.h @@ -39,6 +39,8 @@ struct __dirstream off_t filepos; /* Position of next entry to read. */ + int errcode; /* Delayed error code. */ + /* Directory block. */ char data[0] __attribute__ ((aligned (__alignof__ (void*)))); }; diff --git a/sysdeps/posix/opendir.c b/sysdeps/posix/opendir.c index ddfc3a7..fc05b0f 100644 --- a/sysdeps/posix/opendir.c +++ b/sysdeps/posix/opendir.c @@ -231,6 +231,7 @@ __alloc_dir (int fd, bool close_fd, int flags, const struct stat64 *statp) dirp->size = 0; dirp->offset = 0; dirp->filepos = 0; + dirp->errcode = 0; return dirp; } diff --git a/sysdeps/posix/readdir_r.c b/sysdeps/posix/readdir_r.c index b5a8e2e..8ed5c3f 100644 --- a/sysdeps/posix/readdir_r.c +++ b/sysdeps/posix/readdir_r.c @@ -40,6 +40,7 @@ __READDIR_R (DIR *dirp, DIRENT_TYPE *entry, DIRENT_TYPE **result) DIRENT_TYPE *dp; size_t reclen; const int saved_errno = errno; + int ret; __libc_lock_lock (dirp->lock); @@ -70,10 +71,10 @@ __READDIR_R (DIR *dirp, DIRENT_TYPE *entry, DIRENT_TYPE **result) bytes = 0; __set_errno (saved_errno); } + if (bytes < 0) + dirp->errcode = errno; dp = NULL; - /* Reclen != 0 signals that an error occurred. */ - reclen = bytes != 0; break; } dirp->size = (size_t) bytes; @@ -106,29 +107,46 @@ __READDIR_R (DIR *dirp, DIRENT_TYPE *entry, DIRENT_TYPE **result) dirp->filepos += reclen; #endif - /* Skip deleted files. */ +#ifdef NAME_MAX + if (reclen > offsetof (DIRENT_TYPE, d_name) + NAME_MAX + 1) + { + /* The record is very long. It could still fit into the + caller-supplied buffer if we can skip padding at the + end. */ + size_t namelen = _D_EXACT_NAMLEN (dp); + if (namelen <= NAME_MAX) + reclen = offsetof (DIRENT_TYPE, d_name) + namelen + 1; + else + { + /* The name is too long. Ignore this file. */ + dirp->errcode = ENAMETOOLONG; + dp->d_ino = 0; + continue; + } + } +#endif + + /* Skip deleted and ignored files. */ } while (dp->d_ino == 0); if (dp != NULL) { -#ifdef GETDENTS_64BIT_ALIGNED - /* The d_reclen value might include padding which is not part of - the DIRENT_TYPE data structure. */ - reclen = MIN (reclen, - offsetof (DIRENT_TYPE, d_name) + sizeof (dp->d_name)); -#endif *result = memcpy (entry, dp, reclen); -#ifdef GETDENTS_64BIT_ALIGNED +#ifdef _DIRENT_HAVE_D_RECLEN entry->d_reclen = reclen; #endif + ret = 0; } else - *result = NULL; + { + *result = NULL; + ret = dirp->errcode; + } __libc_lock_unlock (dirp->lock); - return dp != NULL ? 0 : reclen ? errno : 0; + return ret; } #ifdef __READDIR_R_ALIAS diff --git a/sysdeps/posix/rewinddir.c b/sysdeps/posix/rewinddir.c index 2935a8e..d4991ad 100644 --- a/sysdeps/posix/rewinddir.c +++ b/sysdeps/posix/rewinddir.c @@ -33,6 +33,7 @@ rewinddir (dirp) dirp->filepos = 0; dirp->offset = 0; dirp->size = 0; + dirp->errcode = 0; #ifndef NOT_IN_libc __libc_lock_unlock (dirp->lock); #endif -- cgit v1.1