Fix unlikely overflows with wd length

* src/sysdep.c (get_current_dir_name_or_unreachable):
Avoid integer overflow if working directory name is absurdly long.
When allocating memory for getcwd, do not exceed MAXPATHLEN.
This commit is contained in:
Paul Eggert 2017-10-09 10:30:40 -07:00
parent 6c2b1e89ef
commit 934f08f3de

View file

@ -228,6 +228,22 @@ init_standard_fds (void)
static char * static char *
get_current_dir_name_or_unreachable (void) get_current_dir_name_or_unreachable (void)
{ {
/* Use malloc, not xmalloc, since this function can be called before
the xmalloc exception machinery is available. */
char *pwd;
/* The maximum size of a directory name, including the terminating null.
Leave room so that the caller can append a trailing slash. */
ptrdiff_t dirsize_max = min (PTRDIFF_MAX, SIZE_MAX) - 1;
/* The maximum size of a buffer for a file name, including the
terminating null. This is bounded by MAXPATHLEN, if available. */
ptrdiff_t bufsize_max = dirsize_max;
#ifdef MAXPATHLEN
bufsize_max = min (bufsize_max, MAXPATHLEN);
#endif
# if HAVE_GET_CURRENT_DIR_NAME && !BROKEN_GET_CURRENT_DIR_NAME # if HAVE_GET_CURRENT_DIR_NAME && !BROKEN_GET_CURRENT_DIR_NAME
# ifdef HYBRID_MALLOC # ifdef HYBRID_MALLOC
bool use_libc = bss_sbrk_did_unexec; bool use_libc = bss_sbrk_did_unexec;
@ -238,56 +254,61 @@ get_current_dir_name_or_unreachable (void)
{ {
/* For an unreachable directory, this returns a string that starts /* For an unreachable directory, this returns a string that starts
with "(unreachable)"; see Bug#27871. */ with "(unreachable)"; see Bug#27871. */
return get_current_dir_name (); pwd = get_current_dir_name ();
if (pwd)
{
if (strlen (pwd) < dirsize_max)
return pwd;
free (pwd);
errno = ERANGE;
}
return NULL;
} }
# endif # endif
char *buf; size_t pwdlen;
char *pwd = getenv ("PWD");
struct stat dotstat, pwdstat; struct stat dotstat, pwdstat;
pwd = getenv ("PWD");
/* If PWD is accurate, use it instead of calling getcwd. PWD is /* If PWD is accurate, use it instead of calling getcwd. PWD is
sometimes a nicer name, and using it may avoid a fatal error if a sometimes a nicer name, and using it may avoid a fatal error if a
parent directory is searchable but not readable. */ parent directory is searchable but not readable. */
if (pwd if (pwd
&& (IS_DIRECTORY_SEP (*pwd) || (*pwd && IS_DEVICE_SEP (pwd[1]))) && (IS_DIRECTORY_SEP (*pwd) || (*pwd && IS_DEVICE_SEP (pwd[1])))
&& (pwdlen = strlen (pwd)) < bufsize_max
&& stat (pwd, &pwdstat) == 0 && stat (pwd, &pwdstat) == 0
&& stat (".", &dotstat) == 0 && stat (".", &dotstat) == 0
&& dotstat.st_ino == pwdstat.st_ino && dotstat.st_ino == pwdstat.st_ino
&& dotstat.st_dev == pwdstat.st_dev && dotstat.st_dev == pwdstat.st_dev)
#ifdef MAXPATHLEN
&& strlen (pwd) < MAXPATHLEN
#endif
)
{ {
buf = malloc (strlen (pwd) + 1); char *buf = malloc (pwdlen + 1);
if (!buf) if (!buf)
return NULL; return NULL;
strcpy (buf, pwd); return memcpy (buf, pwd, pwdlen + 1);
} }
else else
{ {
size_t buf_size = 1024; ptrdiff_t buf_size = min (bufsize_max, 1024);
buf = malloc (buf_size); char *buf = malloc (buf_size);
if (!buf) if (!buf)
return NULL; return NULL;
for (;;) for (;;)
{ {
if (getcwd (buf, buf_size) == buf) if (getcwd (buf, buf_size) == buf)
break; return buf;
if (errno != ERANGE) int getcwd_errno = errno;
if (getcwd_errno != ERANGE || buf_size == bufsize_max)
{ {
int tmp_errno = errno;
free (buf); free (buf);
errno = tmp_errno; errno = getcwd_errno;
return NULL; return NULL;
} }
buf_size *= 2; buf_size = buf_size <= bufsize_max / 2 ? 2 * buf_size : bufsize_max;
buf = realloc (buf, buf_size); buf = realloc (buf, buf_size);
if (!buf) if (!buf)
return NULL; return NULL;
} }
} }
return buf;
} }
/* Return the current working directory. The result should be freed /* Return the current working directory. The result should be freed