@@ -87,7 +87,7 @@ tests := tst-strtol tst-strtod testmb testrand testsort testdiv \
tst-makecontext-align test-bz22786 tst-strtod-nan-sign \
tst-swapcontext1 tst-setcontext4 tst-setcontext5 \
tst-setcontext6 tst-setcontext7 tst-setcontext8 \
- tst-setcontext9 tst-bz20544
+ tst-setcontext9 tst-bz20544 tst-canon-bz26341
tests-internal := tst-strtod1i tst-strtod3 tst-strtod4 tst-strtod5i \
tst-tls-atexit tst-tls-atexit-nodelete
@@ -102,6 +102,7 @@ LDLIBS-test-atexit-race = $(shared-thread-library)
LDLIBS-test-at_quick_exit-race = $(shared-thread-library)
LDLIBS-test-cxa_atexit-race = $(shared-thread-library)
LDLIBS-test-on_exit-race = $(shared-thread-library)
+LDLIBS-tst-canon-bz26341 = $(shared-thread-library)
LDLIBS-test-dlclose-exit-race = $(shared-thread-library) $(libdl)
LDFLAGS-test-dlclose-exit-race = $(LDFLAGS-rdynamic)
@@ -25,9 +25,81 @@
#include <errno.h>
#include <stddef.h>
+#include <scratch_buffer.h>
#include <eloop-threshold.h>
#include <shlib-compat.h>
+#ifndef PATH_MAX
+# ifdef MAXPATHLEN
+# define PATH_MAX MAXPATHLEN
+# else
+# define PATH_MAX 1024
+# endif
+#endif
+
+static ssize_t
+resolve_readlink (const char *rpath, struct scratch_buffer *out)
+{
+ do
+ {
+ ssize_t n = __readlink (rpath, out->data, out->length);
+ if (n == -1)
+ return -1;
+ else if (n == out->length)
+ {
+ if (out->length > PATH_MAX)
+ {
+ __set_errno (ENAMETOOLONG);
+ return -1;
+ }
+ if (!scratch_buffer_grow (out))
+ return -1;
+ }
+ else
+ {
+ ((char*)out->data)[n] = '\0';
+ return n;
+ }
+ }
+ while (true);
+}
+
+static bool
+realpath_readlink (const char *rpath, const char *end, size_t path_max,
+ size_t st_size, struct scratch_buffer *out)
+{
+ struct scratch_buffer buf;
+ scratch_buffer_init (&buf);
+
+ if (!scratch_buffer_set_array_size (&buf, st_size + 1, sizeof (char *)))
+ return false;
+
+ bool r = false;
+
+ ssize_t n = resolve_readlink (rpath, &buf);
+ if (n == -1)
+ goto out;
+
+ size_t len = strlen (end);
+ if (path_max - buf.length <= len)
+ {
+ __set_errno (ENAMETOOLONG);
+ goto out;
+ }
+
+ if (! scratch_buffer_set_array_size (out, n + len + 1, sizeof (char *)))
+ goto out;
+
+ memmove (out->data + n, end, len + 1);
+ memcpy (out->data, buf.data, n);
+
+ r = true;
+
+out:
+ scratch_buffer_free (&buf);
+ return r;
+}
+
/* Return the canonical absolute name of file NAME. A canonical name
does not contain any `.', `..' components nor any repeated path
separators ('/') or symlinks. All path components must exist. If
@@ -42,10 +114,13 @@
char *
__realpath (const char *name, char *resolved)
{
- char *rpath, *dest, *extra_buf = NULL;
+ char *rpath, *dest;
const char *start, *end, *rpath_limit;
- long int path_max;
+ const size_t path_max = PATH_MAX;
int num_links = 0;
+ struct scratch_buffer extra_buf;
+
+ scratch_buffer_init (&extra_buf);
if (name == NULL)
{
@@ -65,14 +140,6 @@ __realpath (const char *name, char *resolved)
return NULL;
}
-#ifdef PATH_MAX
- path_max = PATH_MAX;
-#else
- path_max = __pathconf (name, _PC_PATH_MAX);
- if (path_max <= 0)
- path_max = 1024;
-#endif
-
if (resolved == NULL)
{
rpath = malloc (path_max);
@@ -85,7 +152,7 @@ __realpath (const char *name, char *resolved)
if (name[0] != '/')
{
- if (!__getcwd (rpath, path_max))
+ if (__getcwd (rpath, path_max) == NULL)
{
rpath[0] = '\0';
goto error;
@@ -101,7 +168,6 @@ __realpath (const char *name, char *resolved)
for (start = end = name; *start; start = end)
{
struct stat64 st;
- int n;
/* Skip sequence of multiple path-separators. */
while (*start == '/')
@@ -158,40 +224,24 @@ __realpath (const char *name, char *resolved)
dest = __mempcpy (dest, start, end - start);
*dest = '\0';
- if (__lxstat64 (_STAT_VER, rpath, &st) < 0)
+ if (__lstat64 (rpath, &st) < 0)
goto error;
if (S_ISLNK (st.st_mode))
{
- char *buf = __alloca (path_max);
- size_t len;
-
if (++num_links > __eloop_threshold ())
{
__set_errno (ELOOP);
goto error;
}
- n = __readlink (rpath, buf, path_max - 1);
- if (n < 0)
+ if (! realpath_readlink (rpath, end, path_max, st.st_size,
+ &extra_buf))
goto error;
- buf[n] = '\0';
- if (!extra_buf)
- extra_buf = __alloca (path_max);
+ name = end = extra_buf.data;
- len = strlen (end);
- if (path_max - n <= len)
- {
- __set_errno (ENAMETOOLONG);
- goto error;
- }
-
- /* Careful here, end may be a pointer into extra_buf... */
- memmove (&extra_buf[n], end, len + 1);
- name = end = memcpy (extra_buf, buf, n);
-
- if (buf[0] == '/')
+ if (((char *)extra_buf.data)[0] == '/')
dest = rpath + 1; /* It's an absolute symlink */
else
/* Back up to previous component, ignore if at root already: */
@@ -209,6 +259,8 @@ __realpath (const char *name, char *resolved)
--dest;
*dest = '\0';
+ scratch_buffer_free (&extra_buf);
+
assert (resolved == NULL || resolved == rpath);
return rpath;
@@ -216,6 +268,7 @@ error:
assert (resolved == NULL || resolved == rpath);
if (resolved == NULL)
free (rpath);
+ scratch_buffer_free (&extra_buf);
return NULL;
}
libc_hidden_def (__realpath)
new file mode 100644
@@ -0,0 +1,108 @@
+/* Check if realpath does not consume extra stack space based on symlink
+ existance in the path (BZ #26341)
+ Copyright (C) 2020 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+
+#include <support/check.h>
+#include <support/support.h>
+#include <support/temp_file.h>
+#include <support/xunistd.h>
+#include <support/xthread.h>
+
+static char *filename;
+static size_t filenamelen;
+static char *linkname;
+
+static int
+maxsymlinks (void)
+{
+#ifdef MAXSYMLINKS
+ return MAXSYMLINKS;
+#else
+ long int sysconf_symloop_max = sysconf (_SC_SYMLOOP_MAX);
+ return sysconf_symloop_max <= 0
+ ? _POSIX_SYMLOOP_MAX
+ : sysconf_symloop_max;
+#endif
+}
+
+#ifndef PATH_MAX
+# define PATH_MAX 1024
+#endif
+
+static void
+create_link (void)
+{
+ int fd = create_temp_file ("tst-canon-bz26341", &filename);
+ TEST_VERIFY_EXIT (fd != -1);
+ xclose (fd);
+
+ char *prevlink = filename;
+ int maxlinks = maxsymlinks ();
+ for (int i = 0; i < maxlinks; i++)
+ {
+ linkname = xasprintf ("%s%d", filename, i);
+ xsymlink (prevlink, linkname);
+ add_temp_file (linkname);
+ prevlink = linkname;
+ }
+
+ filenamelen = strlen (filename);
+}
+
+static void *
+do_realpath (void *arg)
+{
+ /* Old implementation of realpath allocates a PATH_MAX using alloca
+ for each symlink in the path, leading to MAXSYMLINKS times PATH_MAX
+ maximum stack usage.
+ This stack allocations tries fill the thread allocated stack minus
+ both the thread (plus some slack) and the realpath (plus some slack).
+ If realpath uses more than 2 * PATH_MAX plus some slack it will trigger
+ a stackoverflow. */
+
+ const size_t realpath_usage = 2 * PATH_MAX + 1024;
+ const size_t thread_usage = 1 * PATH_MAX + 1024;
+ size_t stack_size = support_small_thread_stack_size ()
+ - realpath_usage - thread_usage;
+ char stack[stack_size];
+ char *resolved = stack + stack_size - thread_usage + 1024;
+
+ char *p = realpath (linkname, resolved);
+ TEST_VERIFY (p != NULL);
+ TEST_COMPARE_BLOB (resolved, filenamelen, filename, filenamelen);
+
+ return NULL;
+}
+
+static int
+do_test (void)
+{
+ create_link ();
+
+ pthread_t th = xpthread_create (support_small_stack_thread_attribute (),
+ do_realpath, NULL);
+ xpthread_join (th);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
@@ -20,8 +20,8 @@
#include <pthread.h>
#include <support/xthread.h>
-void
-support_set_small_thread_stack_size (pthread_attr_t *attr)
+size_t
+support_small_thread_stack_size (void)
{
/* Some architectures have too small values for PTHREAD_STACK_MIN
which cannot be used for creating threads. Ensure that the stack
@@ -31,5 +31,11 @@ support_set_small_thread_stack_size (pthread_attr_t *attr)
if (stack_size < PTHREAD_STACK_MIN)
stack_size = PTHREAD_STACK_MIN;
#endif
- xpthread_attr_setstacksize (attr, stack_size);
+ return stack_size;
+}
+
+void
+support_set_small_thread_stack_size (pthread_attr_t *attr)
+{
+ xpthread_attr_setstacksize (attr, support_small_thread_stack_size ());
}
@@ -78,6 +78,8 @@ void xpthread_attr_setguardsize (pthread_attr_t *attr,
/* Set the stack size in ATTR to a small value, but still large enough
to cover most internal glibc stack usage. */
void support_set_small_thread_stack_size (pthread_attr_t *attr);
+/* Return the stack size used on support_set_small_thread_stack_size. */
+size_t support_small_thread_stack_size (void);
/* Return a pointer to a thread attribute which requests a small
stack. The caller must not free this pointer. */