1
0
mirror of https://sourceware.org/git/glibc.git synced 2025-07-30 22:43:12 +03:00

stdlib: Sync canonicalize with gnulib [BZ #10635] [BZ #26592] [BZ #26341] [BZ #24970]

It sync with gnulib version ae9fb3d66.  The testcase for BZ#23741
(stdlib/test-bz22786.c) is adjusted to check also for ENOMEM.

The patch fixes multiple realpath issues:

  - Portability fixes for errno clobbering on free (BZ#10635).  The
    function does not call free directly anymore, although it might be
    done through scratch_buffer_free.  The free errno clobbering is
    being tracked by BZ#17924.

  - Pointer arithmetic overflows in realpath (BZ#26592).

  - Realpath cyclically call __alloca(path_max) to consume too much
    stack space (BZ#26341).

  - Realpath mishandles EOVERFLOW; stat not needed anyway (BZ#24970).
    The check is done through faccessat now.

Checked on x86_64-linux-gnu and i686-linux-gnu.
This commit is contained in:
Adhemerval Zanella
2020-12-29 11:37:34 -03:00
parent 448a256359
commit c6e0b0b5b0
3 changed files with 379 additions and 161 deletions

View File

@ -16,43 +16,199 @@
License along with the GNU C Library; if not, see License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */ <https://www.gnu.org/licenses/>. */
#include <assert.h> #ifndef _LIBC
/* Don't use __attribute__ __nonnull__ in this compilation unit. Otherwise gcc
optimizes away the name == NULL test below. */
# define _GL_ARG_NONNULL(params)
# define _GL_USE_STDLIB_ALLOC 1
# include <libc-config.h>
#endif
/* Specification. */
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#include <errno.h> #include <errno.h>
#include <stddef.h> #include <fcntl.h>
#include <limits.h>
#include <stdbool.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <eloop-threshold.h> #include <eloop-threshold.h>
#include <shlib-compat.h> #include <filename.h>
#include <idx.h>
#include <intprops.h>
#include <scratch_buffer.h>
/* Return the canonical absolute name of file NAME. A canonical name #ifdef _LIBC
does not contain any `.', `..' components nor any repeated path # include <shlib-compat.h>
separators ('/') or symlinks. All path components must exist. If # define GCC_LINT 1
RESOLVED is null, the result is malloc'd; otherwise, if the # define _GL_ATTRIBUTE_PURE __attribute__ ((__pure__))
canonical name is PATH_MAX chars or more, returns null with `errno' #else
set to ENAMETOOLONG; if the name fits in fewer than PATH_MAX chars, # define __canonicalize_file_name canonicalize_file_name
returns the name in RESOLVED. If the name cannot be resolved and # define __realpath realpath
RESOLVED is non-NULL, it contains the path of the first component # include "pathmax.h"
that cannot be resolved. If the path can be resolved, RESOLVED # define __faccessat faccessat
holds the same value as the value returned. */ # if defined _WIN32 && !defined __CYGWIN__
# define __getcwd _getcwd
# elif HAVE_GETCWD
# if IN_RELOCWRAPPER
/* When building the relocatable program wrapper, use the system's getcwd
function, not the gnulib override, otherwise we would get a link error.
*/
# undef getcwd
# endif
# if defined VMS && !defined getcwd
/* We want the directory in Unix syntax, not in VMS syntax.
The gnulib override of 'getcwd' takes 2 arguments; the original VMS
'getcwd' takes 3 arguments. */
# define __getcwd(buf, max) getcwd (buf, max, 0)
# else
# define __getcwd getcwd
# endif
# else
# define __getcwd(buf, max) getwd (buf)
# endif
# define __mempcpy mempcpy
# define __pathconf pathconf
# define __rawmemchr rawmemchr
# define __readlink readlink
# define __stat stat
#endif
char * /* Suppress bogus GCC -Wmaybe-uninitialized warnings. */
__realpath (const char *name, char *resolved) #if defined GCC_LINT || defined lint
# define IF_LINT(Code) Code
#else
# define IF_LINT(Code) /* empty */
#endif
#ifndef DOUBLE_SLASH_IS_DISTINCT_ROOT
# define DOUBLE_SLASH_IS_DISTINCT_ROOT false
#endif
#if defined _LIBC || !FUNC_REALPATH_WORKS
/* Return true if FILE's existence can be shown, false (setting errno)
otherwise. Follow symbolic links. */
static bool
file_accessible (char const *file)
{ {
char *rpath, *dest, *extra_buf = NULL; # if defined _LIBC || HAVE_FACCESSAT
const char *start, *end, *rpath_limit; return __faccessat (AT_FDCWD, file, F_OK, AT_EACCESS) == 0;
long int path_max; # else
struct stat st;
return __stat (file, &st) == 0 || errno == EOVERFLOW;
# endif
}
/* True if concatenating END as a suffix to a file name means that the
code needs to check that the file name is that of a searchable
directory, since the canonicalize_filename_mode_stk code won't
check this later anyway when it checks an ordinary file name
component within END. END must either be empty, or start with a
slash. */
static bool _GL_ATTRIBUTE_PURE
suffix_requires_dir_check (char const *end)
{
/* If END does not start with a slash, the suffix is OK. */
while (ISSLASH (*end))
{
/* Two or more slashes act like a single slash. */
do
end++;
while (ISSLASH (*end));
switch (*end++)
{
default: return false; /* An ordinary file name component is OK. */
case '\0': return true; /* Trailing "/" is trouble. */
case '.': break; /* Possibly "." or "..". */
}
/* Trailing "/.", or "/.." even if not trailing, is trouble. */
if (!*end || (*end == '.' && (!end[1] || ISSLASH (end[1]))))
return true;
}
return false;
}
/* Append this to a file name to test whether it is a searchable directory.
On POSIX platforms "/" suffices, but "/./" is sometimes needed on
macOS 10.13 <https://bugs.gnu.org/30350>, and should also work on
platforms like AIX 7.2 that need at least "/.". */
#if defined _LIBC || defined LSTAT_FOLLOWS_SLASHED_SYMLINK
static char const dir_suffix[] = "/";
#else
static char const dir_suffix[] = "/./";
#endif
/* Return true if DIR is a searchable dir, false (setting errno) otherwise.
DIREND points to the NUL byte at the end of the DIR string.
Store garbage into DIREND[0 .. strlen (dir_suffix)]. */
static bool
dir_check (char *dir, char *dirend)
{
strcpy (dirend, dir_suffix);
return file_accessible (dir);
}
static idx_t
get_path_max (void)
{
# ifdef PATH_MAX
long int path_max = PATH_MAX;
# else
/* The caller invoked realpath with a null RESOLVED, even though
PATH_MAX is not defined as a constant. The glibc manual says
programs should not do this, and POSIX says the behavior is undefined.
Historically, glibc here used the result of pathconf, or 1024 if that
failed; stay consistent with this (dubious) historical practice. */
int err = errno;
long int path_max = __pathconf ("/", _PC_PATH_MAX);
__set_errno (err);
# endif
return path_max < 0 ? 1024 : path_max <= IDX_MAX ? path_max : IDX_MAX;
}
/* Act like __realpath (see below), with an additional argument
rname_buf that can be used as temporary storage.
If GCC_LINT is defined, do not inline this function with GCC 10.1
and later, to avoid creating a pointer to the stack that GCC
-Wreturn-local-addr incorrectly complains about. See:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93644
Although the noinline attribute can hurt performance a bit, no better way
to pacify GCC is known; even an explicit #pragma does not pacify GCC.
When the GCC bug is fixed this workaround should be limited to the
broken GCC versions. */
#if __GNUC_PREREQ (10, 1)
# if defined GCC_LINT || defined lint
__attribute__ ((__noinline__))
# elif __OPTIMIZE__ && !__NO_INLINE__
# define GCC_BOGUS_WRETURN_LOCAL_ADDR
# endif
#endif
static char *
realpath_stk (const char *name, char *resolved,
struct scratch_buffer *rname_buf)
{
char *dest;
char const *start;
char const *end;
int num_links = 0; int num_links = 0;
if (name == NULL) if (name == NULL)
{ {
/* As per Single Unix Specification V2 we must return an error if /* As per Single Unix Specification V2 we must return an error if
either parameter is a null pointer. We extend this to allow either parameter is a null pointer. We extend this to allow
the RESOLVED parameter to be NULL in case the we are expected to the RESOLVED parameter to be NULL in case the we are expected to
allocate the room for the return value. */ allocate the room for the return value. */
__set_errno (EINVAL); __set_errno (EINVAL);
return NULL; return NULL;
} }
@ -60,166 +216,230 @@ __realpath (const char *name, char *resolved)
if (name[0] == '\0') if (name[0] == '\0')
{ {
/* As per Single Unix Specification V2 we must return an error if /* As per Single Unix Specification V2 we must return an error if
the name argument points to an empty string. */ the name argument points to an empty string. */
__set_errno (ENOENT); __set_errno (ENOENT);
return NULL; return NULL;
} }
#ifdef PATH_MAX struct scratch_buffer extra_buffer, link_buffer;
path_max = PATH_MAX; scratch_buffer_init (&extra_buffer);
#else scratch_buffer_init (&link_buffer);
path_max = __pathconf (name, _PC_PATH_MAX); scratch_buffer_init (rname_buf);
if (path_max <= 0) char *rname_on_stack = rname_buf->data;
path_max = 1024; char *rname = rname_on_stack;
#endif bool end_in_extra_buffer = false;
bool failed = true;
if (resolved == NULL) /* This is always zero for Posix hosts, but can be 2 for MS-Windows
{ and MS-DOS X:/foo/bar file names. */
rpath = malloc (path_max); idx_t prefix_len = FILE_SYSTEM_PREFIX_LEN (name);
if (rpath == NULL)
return NULL;
}
else
rpath = resolved;
rpath_limit = rpath + path_max;
if (name[0] != '/') if (!IS_ABSOLUTE_FILE_NAME (name))
{ {
if (!__getcwd (rpath, path_max)) while (!__getcwd (rname, rname_buf->length))
{ {
rpath[0] = '\0'; if (errno != ERANGE)
goto error; {
} dest = rname;
dest = __rawmemchr (rpath, '\0'); goto error;
}
if (!scratch_buffer_grow (rname_buf))
goto error_nomem;
rname = rname_buf->data;
}
dest = __rawmemchr (rname, '\0');
start = name;
prefix_len = FILE_SYSTEM_PREFIX_LEN (rname);
} }
else else
{ {
rpath[0] = '/'; dest = __mempcpy (rname, name, prefix_len);
dest = rpath + 1; *dest++ = '/';
if (DOUBLE_SLASH_IS_DISTINCT_ROOT)
{
if (prefix_len == 0 /* implies ISSLASH (name[0]) */
&& ISSLASH (name[1]) && !ISSLASH (name[2]))
*dest++ = '/';
*dest = '\0';
}
start = name + prefix_len;
} }
for (start = end = name; *start; start = end) for ( ; *start; start = end)
{ {
struct stat64 st; /* Skip sequence of multiple file name separators. */
int n; while (ISSLASH (*start))
++start;
/* Skip sequence of multiple path-separators. */ /* Find end of component. */
while (*start == '/') for (end = start; *end && !ISSLASH (*end); ++end)
++start; /* Nothing. */;
/* Find end of path component. */ /* Length of this file name component; it can be zero if a file
for (end = start; *end && *end != '/'; ++end) name ends in '/'. */
/* Nothing. */; idx_t startlen = end - start;
if (end - start == 0) if (startlen == 0)
break; break;
else if (end - start == 1 && start[0] == '.') else if (startlen == 1 && start[0] == '.')
/* nothing */; /* nothing */;
else if (end - start == 2 && start[0] == '.' && start[1] == '.') else if (startlen == 2 && start[0] == '.' && start[1] == '.')
{ {
/* Back up to previous component, ignore if at root already. */ /* Back up to previous component, ignore if at root already. */
if (dest > rpath + 1) if (dest > rname + prefix_len + 1)
while ((--dest)[-1] != '/'); for (--dest; dest > rname && !ISSLASH (dest[-1]); --dest)
} continue;
if (DOUBLE_SLASH_IS_DISTINCT_ROOT
&& dest == rname + 1 && !prefix_len
&& ISSLASH (*dest) && !ISSLASH (dest[1]))
dest++;
}
else else
{ {
size_t new_size; if (!ISSLASH (dest[-1]))
*dest++ = '/';
if (dest[-1] != '/') while (rname + rname_buf->length - dest
*dest++ = '/'; < startlen + sizeof dir_suffix)
{
idx_t dest_offset = dest - rname;
if (!scratch_buffer_grow_preserve (rname_buf))
goto error_nomem;
rname = rname_buf->data;
dest = rname + dest_offset;
}
if (dest + (end - start) >= rpath_limit) dest = __mempcpy (dest, start, startlen);
{ *dest = '\0';
ptrdiff_t dest_offset = dest - rpath;
char *new_rpath;
if (resolved) char *buf;
{ ssize_t n;
__set_errno (ENAMETOOLONG); while (true)
if (dest > rpath + 1) {
dest--; buf = link_buffer.data;
*dest = '\0'; idx_t bufsize = link_buffer.length;
goto error; n = __readlink (rname, buf, bufsize - 1);
} if (n < bufsize - 1)
new_size = rpath_limit - rpath; break;
if (end - start + 1 > path_max) if (!scratch_buffer_grow (&link_buffer))
new_size += end - start + 1; goto error_nomem;
else }
new_size += path_max; if (0 <= n)
new_rpath = (char *) realloc (rpath, new_size); {
if (new_rpath == NULL) if (++num_links > __eloop_threshold ())
goto error; {
rpath = new_rpath; __set_errno (ELOOP);
rpath_limit = rpath + new_size; goto error;
}
dest = rpath + dest_offset; buf[n] = '\0';
}
dest = __mempcpy (dest, start, end - start); char *extra_buf = extra_buffer.data;
*dest = '\0'; idx_t end_idx IF_LINT (= 0);
if (end_in_extra_buffer)
end_idx = end - extra_buf;
size_t len = strlen (end);
if (INT_ADD_OVERFLOW (len, n))
{
__set_errno (ENOMEM);
goto error_nomem;
}
while (extra_buffer.length <= len + n)
{
if (!scratch_buffer_grow_preserve (&extra_buffer))
goto error_nomem;
extra_buf = extra_buffer.data;
}
if (end_in_extra_buffer)
end = extra_buf + end_idx;
if (__lstat64 (rpath, &st) < 0) /* Careful here, end may be a pointer into extra_buf... */
goto error; memmove (&extra_buf[n], end, len + 1);
name = end = memcpy (extra_buf, buf, n);
end_in_extra_buffer = true;
if (S_ISLNK (st.st_mode)) if (IS_ABSOLUTE_FILE_NAME (buf))
{ {
char *buf = __alloca (path_max); idx_t pfxlen = FILE_SYSTEM_PREFIX_LEN (buf);
size_t len;
if (++num_links > __eloop_threshold ()) dest = __mempcpy (rname, buf, pfxlen);
{ *dest++ = '/'; /* It's an absolute symlink */
__set_errno (ELOOP); if (DOUBLE_SLASH_IS_DISTINCT_ROOT)
goto error; {
} if (ISSLASH (buf[1]) && !ISSLASH (buf[2]) && !pfxlen)
*dest++ = '/';
n = __readlink (rpath, buf, path_max - 1); *dest = '\0';
if (n < 0) }
goto error; /* Install the new prefix to be in effect hereafter. */
buf[n] = '\0'; prefix_len = pfxlen;
}
if (!extra_buf) else
extra_buf = __alloca (path_max); {
/* Back up to previous component, ignore if at root
len = strlen (end); already: */
if (path_max - n <= len) if (dest > rname + prefix_len + 1)
{ for (--dest; dest > rname && !ISSLASH (dest[-1]); --dest)
__set_errno (ENAMETOOLONG); continue;
goto error; if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1
} && ISSLASH (*dest) && !ISSLASH (dest[1]) && !prefix_len)
dest++;
/* Careful here, end may be a pointer into extra_buf... */ }
memmove (&extra_buf[n], end, len + 1); }
name = end = memcpy (extra_buf, buf, n); else if (! (suffix_requires_dir_check (end)
? dir_check (rname, dest)
if (buf[0] == '/') : errno == EINVAL))
dest = rpath + 1; /* It's an absolute symlink */ goto error;
else }
/* Back up to previous component, ignore if at root already: */
if (dest > rpath + 1)
while ((--dest)[-1] != '/');
}
else if (!S_ISDIR (st.st_mode) && *end != '\0')
{
__set_errno (ENOTDIR);
goto error;
}
}
} }
if (dest > rpath + 1 && dest[-1] == '/') if (dest > rname + prefix_len + 1 && ISSLASH (dest[-1]))
--dest; --dest;
*dest = '\0'; if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1 && !prefix_len
&& ISSLASH (*dest) && !ISSLASH (dest[1]))
assert (resolved == NULL || resolved == rpath); dest++;
return rpath; failed = false;
error: error:
assert (resolved == NULL || resolved == rpath); *dest++ = '\0';
if (resolved == NULL) if (resolved != NULL && dest - rname <= get_path_max ())
free (rpath); rname = strcpy (resolved, rname);
return NULL;
error_nomem:
scratch_buffer_free (&extra_buffer);
scratch_buffer_free (&link_buffer);
if (failed || rname == resolved)
{
scratch_buffer_free (rname_buf);
return failed ? NULL : resolved;
}
return scratch_buffer_dupfree (rname_buf, dest - rname);
}
/* Return the canonical absolute name of file NAME. A canonical name
does not contain any ".", ".." components nor any repeated file name
separators ('/') or symlinks. All file name components must exist. If
RESOLVED is null, the result is malloc'd; otherwise, if the
canonical name is PATH_MAX chars or more, returns null with 'errno'
set to ENAMETOOLONG; if the name fits in fewer than PATH_MAX chars,
returns the name in RESOLVED. If the name cannot be resolved and
RESOLVED is non-NULL, it contains the name of the first component
that cannot be resolved. If the name can be resolved, RESOLVED
holds the same value as the value returned. */
char *
__realpath (const char *name, char *resolved)
{
#ifdef GCC_BOGUS_WRETURN_LOCAL_ADDR
#warning "GCC might issue a bogus -Wreturn-local-addr warning here."
#warning "See <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93644>."
#endif
struct scratch_buffer rname_buffer;
return realpath_stk (name, resolved, &rname_buffer);
} }
libc_hidden_def (__realpath) libc_hidden_def (__realpath)
versioned_symbol (libc, __realpath, realpath, GLIBC_2_3); versioned_symbol (libc, __realpath, realpath, GLIBC_2_3);
#endif /* !FUNC_REALPATH_WORKS || defined _LIBC */
#if SHLIB_COMPAT(libc, GLIBC_2_0, GLIBC_2_3) #if SHLIB_COMPAT(libc, GLIBC_2_0, GLIBC_2_3)

View File

@ -62,12 +62,10 @@ do_test (void)
/* This call crashes before the fix for bz22786 on 32-bit platforms. */ /* This call crashes before the fix for bz22786 on 32-bit platforms. */
p = realpath (path, NULL); p = realpath (path, NULL);
TEST_VERIFY (p == NULL);
if (p != NULL || errno != ENAMETOOLONG) /* For 64-bit platforms readlink return ENAMETOOLONG, while for 32-bit
{ realpath will try to allocate a buffer larger than PTRDIFF_MAX. */
printf ("realpath: %s (%m)", p); TEST_VERIFY (errno == ENOMEM || errno == ENAMETOOLONG);
return EXIT_FAILURE;
}
/* Cleanup. */ /* Cleanup. */
unlink (lnk); unlink (lnk);
@ -78,5 +76,4 @@ do_test (void)
return 0; return 0;
} }
#define TEST_FUNCTION do_test
#include <support/test-driver.c> #include <support/test-driver.c>

View File

@ -24,7 +24,7 @@
int int
faccessat (int fd, const char *file, int mode, int flag) __faccessat (int fd, const char *file, int mode, int flag)
{ {
int ret = INLINE_SYSCALL_CALL (faccessat2, fd, file, mode, flag); int ret = INLINE_SYSCALL_CALL (faccessat2, fd, file, mode, flag);
#if __ASSUME_FACCESSAT2 #if __ASSUME_FACCESSAT2
@ -73,3 +73,4 @@ faccessat (int fd, const char *file, int mode, int flag)
return INLINE_SYSCALL_ERROR_RETURN_VALUE (EACCES); return INLINE_SYSCALL_ERROR_RETURN_VALUE (EACCES);
#endif /* !__ASSUME_FACCESSAT2 */ #endif /* !__ASSUME_FACCESSAT2 */
} }
weak_alias (__faccessat, faccessat)