diff options
author | Paul Eggert <eggert@cs.ucla.edu> | 2017-10-09 10:30:40 -0700 |
---|---|---|
committer | Paul Eggert <eggert@cs.ucla.edu> | 2017-10-09 10:48:55 -0700 |
commit | 934f08f3de7929e54e9da3271a34bb49a8854f2c (patch) | |
tree | 99f898f4f6242d35d697749957ca41e813054ca7 /src/sysdep.c | |
parent | 6c2b1e89efe90e1b55bc0f0296e68efa52dbcbc1 (diff) | |
download | emacs-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.c | 57 |
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 |