summaryrefslogtreecommitdiff
path: root/src/sysdep.c
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2017-10-09 10:30:40 -0700
committerPaul Eggert <eggert@cs.ucla.edu>2017-10-09 10:48:55 -0700
commit934f08f3de7929e54e9da3271a34bb49a8854f2c (patch)
tree99f898f4f6242d35d697749957ca41e813054ca7 /src/sysdep.c
parent6c2b1e89efe90e1b55bc0f0296e68efa52dbcbc1 (diff)
downloademacs-934f08f3de7929e54e9da3271a34bb49a8854f2c.tar.gz
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.
Diffstat (limited to 'src/sysdep.c')
-rw-r--r--src/sysdep.c57
1 files changed, 39 insertions, 18 deletions
diff --git a/src/sysdep.c b/src/sysdep.c
index c3484920d0c..6d24b7fa2b1 100644
--- a/src/sysdep.c
+++ b/src/sysdep.c
@@ -228,6 +228,22 @@ init_standard_fds (void)
static char *
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
# ifdef HYBRID_MALLOC
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
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
- char *buf;
- char *pwd = getenv ("PWD");
+ size_t pwdlen;
struct stat dotstat, pwdstat;
+ pwd = getenv ("PWD");
+
/* 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
parent directory is searchable but not readable. */
if (pwd
&& (IS_DIRECTORY_SEP (*pwd) || (*pwd && IS_DEVICE_SEP (pwd[1])))
+ && (pwdlen = strlen (pwd)) < bufsize_max
&& stat (pwd, &pwdstat) == 0
&& stat (".", &dotstat) == 0
&& dotstat.st_ino == pwdstat.st_ino
- && dotstat.st_dev == pwdstat.st_dev
-#ifdef MAXPATHLEN
- && strlen (pwd) < MAXPATHLEN
-#endif
- )
+ && dotstat.st_dev == pwdstat.st_dev)
{
- buf = malloc (strlen (pwd) + 1);
+ char *buf = malloc (pwdlen + 1);
if (!buf)
return NULL;
- strcpy (buf, pwd);
+ return memcpy (buf, pwd, pwdlen + 1);
}
else
{
- size_t buf_size = 1024;
- buf = malloc (buf_size);
+ ptrdiff_t buf_size = min (bufsize_max, 1024);
+ char *buf = malloc (buf_size);
if (!buf)
return NULL;
for (;;)
{
if (getcwd (buf, buf_size) == buf)
- break;
- if (errno != ERANGE)
+ return buf;
+ int getcwd_errno = errno;
+ if (getcwd_errno != ERANGE || buf_size == bufsize_max)
{
- int tmp_errno = errno;
free (buf);
- errno = tmp_errno;
+ errno = getcwd_errno;
return NULL;
}
- buf_size *= 2;
+ buf_size = buf_size <= bufsize_max / 2 ? 2 * buf_size : bufsize_max;
buf = realloc (buf, buf_size);
if (!buf)
return NULL;
}
}
- return buf;
}
/* Return the current working directory. The result should be freed