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.
This commit is contained in:
Florian Weimer 2013-08-16 09:38:52 +02:00
parent ca0a6bc4c5
commit 91ce40854d
10 changed files with 124 additions and 35 deletions

View File

@ -1,3 +1,25 @@
2013-08-16 Florian Weimer <fweimer@redhat.com>
[BZ #14699]
CVE-2013-4237
* 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.
2013-08-13 Andreas Schwab <schwab@suse.de> 2013-08-13 Andreas Schwab <schwab@suse.de>
[BZ #15749] [BZ #15749]

7
NEWS
View File

@ -9,7 +9,12 @@ Version 2.19
* The following bugs are resolved with this release: * The following bugs are resolved with this release:
15749 14699, 15749
* CVE-2013-4237 The readdir_r function could write more than NAME_MAX bytes
to the d_name member of struct dirent, or omit the terminating NUL
character. (Bugzilla #14699).
Version 2.18 Version 2.18

View File

@ -1149,6 +1149,9 @@ typed ahead as input. @xref{I/O Queues}.
@deftypevr Macro int NAME_MAX @deftypevr Macro int NAME_MAX
The uniform system limit (if any) for the length of a file name component, not The uniform system limit (if any) for the length of a file name component, not
including the terminating null character. including the terminating null character.
@strong{Portability Note:} On some systems, @theglibc{} defines
@code{NAME_MAX}, but does not actually enforce this limit.
@end deftypevr @end deftypevr
@comment limits.h @comment limits.h
@ -1157,6 +1160,9 @@ including the terminating null character.
The uniform system limit (if any) for the length of an entire file name (that The uniform system limit (if any) for the length of an entire file name (that
is, the argument given to system calls such as @code{open}), including the is, the argument given to system calls such as @code{open}), including the
terminating null character. terminating null character.
@strong{Portability Note:} @Theglibc{} does not enforce this limit
even if @code{PATH_MAX} is defined.
@end deftypevr @end deftypevr
@cindex limits, pipe buffer size @cindex limits, pipe buffer size
@ -1476,6 +1482,9 @@ Inquire about the value of @code{POSIX_REC_MIN_XFER_SIZE}.
Inquire about the value of @code{POSIX_REC_XFER_ALIGN}. Inquire about the value of @code{POSIX_REC_XFER_ALIGN}.
@end table @end table
@strong{Portability Note:} On some systems, @theglibc{} does not
enforce @code{_PC_NAME_MAX} or @code{_PC_PATH_MAX} limits.
@node Utility Limits @node Utility Limits
@section Utility Program Capacity Limits @section Utility Program Capacity Limits

View File

@ -444,9 +444,9 @@ symbols are declared in the header file @file{dirent.h}.
@comment POSIX.1 @comment POSIX.1
@deftypefun {struct dirent *} readdir (DIR *@var{dirstream}) @deftypefun {struct dirent *} readdir (DIR *@var{dirstream})
This function reads the next entry from the directory. It normally This function reads the next entry from the directory. It normally
returns a pointer to a structure containing information about the file. returns a pointer to a structure containing information about the
This structure is statically allocated and can be rewritten by a file. This structure is associated with the @var{dirstream} handle
subsequent call. and can be rewritten by a subsequent call.
@strong{Portability Note:} On some systems @code{readdir} may not @strong{Portability Note:} On some systems @code{readdir} may not
return entries for @file{.} and @file{..}, even though these are always return entries for @file{.} and @file{..}, even though these are always
@ -461,19 +461,61 @@ conditions are defined for this function:
The @var{dirstream} argument is not valid. The @var{dirstream} argument is not valid.
@end table @end table
@code{readdir} is not thread safe. Multiple threads using To distinguish between an end-of-directory condition or an error, you
@code{readdir} on the same @var{dirstream} may overwrite the return must set @code{errno} to zero before calling @code{readdir}. To avoid
value. Use @code{readdir_r} when this is critical. entering an infinite loop, you should stop reading from the directory
after the first error.
In POSIX.1-2008, @code{readdir} is not thread-safe. In @theglibc{}
implementation, it is safe to call @code{readdir} concurrently on
different @var{dirstream}s, but multiple threads accessing the same
@var{dirstream} result in undefined behavior. @code{readdir_r} is a
fully thread-safe alternative, but suffers from poor portability (see
below). It is recommended that you use @code{readdir}, with external
locking if multiple threads access the same @var{dirstream}.
@end deftypefun @end deftypefun
@comment dirent.h @comment dirent.h
@comment GNU @comment GNU
@deftypefun int readdir_r (DIR *@var{dirstream}, struct dirent *@var{entry}, struct dirent **@var{result}) @deftypefun int readdir_r (DIR *@var{dirstream}, struct dirent *@var{entry}, struct dirent **@var{result})
This function is the reentrant version of @code{readdir}. Like This function is a version of @code{readdir} which performs internal
@code{readdir} it returns the next entry from the directory. But to locking. Like @code{readdir} it returns the next entry from the
prevent conflicts between simultaneously running threads the result is directory. To prevent conflicts between simultaneously running
not stored in statically allocated memory. Instead the argument threads the result is stored inside the @var{entry} object.
@var{entry} points to a place to store the result.
@strong{Portability Note:} It is recommended to use @code{readdir}
instead of @code{readdir_r} for the following reasons:
@itemize @bullet
@item
On systems which do not define @code{NAME_MAX}, it may not be possible
to use @code{readdir_r} safely because the caller does not specify the
length of the buffer for the directory entry.
@item
On some systems, @code{readdir_r} cannot read directory entries with
very long names. If such a name is encountered, @theglibc{}
implementation of @code{readdir_r} returns with an error code of
@code{ENAMETOOLONG} after the final directory entry has been read. On
other systems, @code{readdir_r} may return successfully, but the
@code{d_name} member may not be NUL-terminated or may be truncated.
@item
POSIX-1.2008 does not guarantee that @code{readdir} is thread-safe,
even when access to the same @var{dirstream} is serialized. But in
current implementations (including @theglibc{}), it is safe to call
@code{readdir} concurrently on different @var{dirstream}s, so there is
no need to use @code{readdir_r} in most multi-threaded programs. In
the rare case that multiple threads need to read from the same
@var{dirstream}, it is still better to use @code{readdir} and external
synchronization.
@item
It is expected that future versions of POSIX will obsolete
@code{readdir_r} and mandate the level of thread safety for
@code{readdir} which is provided by @theglibc{} and other
implementations today.
@end itemize
Normally @code{readdir_r} returns zero and sets @code{*@var{result}} Normally @code{readdir_r} returns zero and sets @code{*@var{result}}
to @var{entry}. If there are no more entries in the directory or an to @var{entry}. If there are no more entries in the directory or an
@ -481,15 +523,6 @@ error is detected, @code{readdir_r} sets @code{*@var{result}} to a
null pointer and returns a nonzero error code, also stored in null pointer and returns a nonzero error code, also stored in
@code{errno}, as described for @code{readdir}. @code{errno}, as described for @code{readdir}.
@strong{Portability Note:} On some systems @code{readdir_r} may not
return a NUL terminated string for the file name, even when there is no
@code{d_reclen} field in @code{struct dirent} and the file
name is the maximum allowed size. Modern systems all have the
@code{d_reclen} field, and on old systems multi-threading is not
critical. In any case there is no such problem with the @code{readdir}
function, so that even on systems without the @code{d_reclen} member one
could use multiple threads by using external locking.
It is also important to look at the definition of the @code{struct It is also important to look at the definition of the @code{struct
dirent} type. Simply passing a pointer to an object of this type for dirent} type. Simply passing a pointer to an object of this type for
the second parameter of @code{readdir_r} might not be enough. Some the second parameter of @code{readdir_r} might not be enough. Some

View File

@ -39,6 +39,8 @@ struct __dirstream
off_t filepos; /* Position of next entry to read. */ off_t filepos; /* Position of next entry to read. */
int errcode; /* Delayed error code. */
/* Directory block. */ /* Directory block. */
char data[0] __attribute__ ((aligned (__alignof__ (void*)))); char data[0] __attribute__ ((aligned (__alignof__ (void*))));
}; };

View File

@ -231,6 +231,7 @@ __alloc_dir (int fd, bool close_fd, int flags, const struct stat64 *statp)
dirp->size = 0; dirp->size = 0;
dirp->offset = 0; dirp->offset = 0;
dirp->filepos = 0; dirp->filepos = 0;
dirp->errcode = 0;
return dirp; return dirp;
} }

View File

@ -40,6 +40,7 @@ __READDIR_R (DIR *dirp, DIRENT_TYPE *entry, DIRENT_TYPE **result)
DIRENT_TYPE *dp; DIRENT_TYPE *dp;
size_t reclen; size_t reclen;
const int saved_errno = errno; const int saved_errno = errno;
int ret;
__libc_lock_lock (dirp->lock); __libc_lock_lock (dirp->lock);
@ -70,10 +71,10 @@ __READDIR_R (DIR *dirp, DIRENT_TYPE *entry, DIRENT_TYPE **result)
bytes = 0; bytes = 0;
__set_errno (saved_errno); __set_errno (saved_errno);
} }
if (bytes < 0)
dirp->errcode = errno;
dp = NULL; dp = NULL;
/* Reclen != 0 signals that an error occurred. */
reclen = bytes != 0;
break; break;
} }
dirp->size = (size_t) bytes; dirp->size = (size_t) bytes;
@ -106,29 +107,46 @@ __READDIR_R (DIR *dirp, DIRENT_TYPE *entry, DIRENT_TYPE **result)
dirp->filepos += reclen; dirp->filepos += reclen;
#endif #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); while (dp->d_ino == 0);
if (dp != NULL) 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); *result = memcpy (entry, dp, reclen);
#ifdef GETDENTS_64BIT_ALIGNED #ifdef _DIRENT_HAVE_D_RECLEN
entry->d_reclen = reclen; entry->d_reclen = reclen;
#endif #endif
ret = 0;
} }
else else
*result = NULL; {
*result = NULL;
ret = dirp->errcode;
}
__libc_lock_unlock (dirp->lock); __libc_lock_unlock (dirp->lock);
return dp != NULL ? 0 : reclen ? errno : 0; return ret;
} }
#ifdef __READDIR_R_ALIAS #ifdef __READDIR_R_ALIAS

View File

@ -33,6 +33,7 @@ rewinddir (dirp)
dirp->filepos = 0; dirp->filepos = 0;
dirp->offset = 0; dirp->offset = 0;
dirp->size = 0; dirp->size = 0;
dirp->errcode = 0;
#ifndef NOT_IN_libc #ifndef NOT_IN_libc
__libc_lock_unlock (dirp->lock); __libc_lock_unlock (dirp->lock);
#endif #endif

View File

@ -18,7 +18,6 @@
#define __READDIR_R __readdir64_r #define __READDIR_R __readdir64_r
#define __GETDENTS __getdents64 #define __GETDENTS __getdents64
#define DIRENT_TYPE struct dirent64 #define DIRENT_TYPE struct dirent64
#define GETDENTS_64BIT_ALIGNED 1
#include <sysdeps/posix/readdir_r.c> #include <sysdeps/posix/readdir_r.c>

View File

@ -1,5 +1,4 @@
#define readdir64_r __no_readdir64_r_decl #define readdir64_r __no_readdir64_r_decl
#define GETDENTS_64BIT_ALIGNED 1
#include <sysdeps/posix/readdir_r.c> #include <sysdeps/posix/readdir_r.c>
#undef readdir64_r #undef readdir64_r
weak_alias (__readdir_r, readdir64_r) weak_alias (__readdir_r, readdir64_r)