Distinguish ELOOP diagnosis threshold from SYMLOOP_MAX.
[platform/upstream/glibc.git] / elf / chroot_canon.c
1 /* Return the canonical absolute name of a given file inside chroot.
2    Copyright (C) 1996-2012 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published
7    by the Free Software Foundation; version 2 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
17
18 #include <stdlib.h>
19 #include <string.h>
20 #include <unistd.h>
21 #include <limits.h>
22 #include <sys/stat.h>
23 #include <errno.h>
24 #include <stddef.h>
25 #include <stdint.h>
26
27 #include <eloop-threshold.h>
28 #include <ldconfig.h>
29
30 #ifndef PATH_MAX
31 #define PATH_MAX 1024
32 #endif
33
34 /* Return the canonical absolute name of file NAME as if chroot(CHROOT) was
35    done first.  A canonical name does not contain any `.', `..' components
36    nor any repeated path separators ('/') or symlinks.  All path components
37    must exist and NAME must be absolute filename.  The result is malloc'd.
38    The returned name includes the CHROOT prefix.  */
39
40 char *
41 chroot_canon (const char *chroot, const char *name)
42 {
43   char *rpath;
44   char *dest;
45   char *extra_buf = NULL;
46   char *rpath_root;
47   const char *start;
48   const char *end;
49   const char *rpath_limit;
50   int num_links = 0;
51   size_t chroot_len = strlen (chroot);
52
53   if (chroot_len < 1)
54     {
55       __set_errno (EINVAL);
56       return NULL;
57     }
58
59   rpath = xmalloc (chroot_len + PATH_MAX);
60
61   rpath_limit = rpath + chroot_len + PATH_MAX;
62
63   rpath_root = (char *) mempcpy (rpath, chroot, chroot_len) - 1;
64   if (*rpath_root != '/')
65     *++rpath_root = '/';
66   dest = rpath_root + 1;
67
68   for (start = end = name; *start; start = end)
69     {
70       struct stat64 st;
71
72       /* Skip sequence of multiple path-separators.  */
73       while (*start == '/')
74         ++start;
75
76       /* Find end of path component.  */
77       for (end = start; *end && *end != '/'; ++end)
78         /* Nothing.  */;
79
80       if (end - start == 0)
81         break;
82       else if (end - start == 1 && start[0] == '.')
83         /* nothing */;
84       else if (end - start == 2 && start[0] == '.' && start[1] == '.')
85         {
86           /* Back up to previous component, ignore if at root already.  */
87           if (dest > rpath_root + 1)
88             while ((--dest)[-1] != '/');
89         }
90       else
91         {
92           size_t new_size;
93
94           if (dest[-1] != '/')
95             *dest++ = '/';
96
97           if (dest + (end - start) >= rpath_limit)
98             {
99               ptrdiff_t dest_offset = dest - rpath;
100               char *new_rpath;
101
102               new_size = rpath_limit - rpath;
103               if (end - start + 1 > PATH_MAX)
104                 new_size += end - start + 1;
105               else
106                 new_size += PATH_MAX;
107               new_rpath = (char *) xrealloc (rpath, new_size);
108               rpath = new_rpath;
109               rpath_limit = rpath + new_size;
110
111               dest = rpath + dest_offset;
112             }
113
114           dest = mempcpy (dest, start, end - start);
115           *dest = '\0';
116
117           if (lstat64 (rpath, &st) < 0)
118             {
119               if (*end == '\0')
120                 goto done;
121               goto error;
122             }
123
124           if (S_ISLNK (st.st_mode))
125             {
126               char *buf = alloca (PATH_MAX);
127               size_t len;
128
129               if (++num_links > __eloop_threshold ())
130                 {
131                   __set_errno (ELOOP);
132                   goto error;
133                 }
134
135               ssize_t n = readlink (rpath, buf, PATH_MAX - 1);
136               if (n < 0)
137                 {
138                   if (*end == '\0')
139                     goto done;
140                   goto error;
141                 }
142               buf[n] = '\0';
143
144               if (!extra_buf)
145                 extra_buf = alloca (PATH_MAX);
146
147               len = strlen (end);
148               if (len >= PATH_MAX - n)
149                 {
150                   __set_errno (ENAMETOOLONG);
151                   goto error;
152                 }
153
154               /* Careful here, end may be a pointer into extra_buf... */
155               memmove (&extra_buf[n], end, len + 1);
156               name = end = memcpy (extra_buf, buf, n);
157
158               if (buf[0] == '/')
159                 dest = rpath_root + 1;  /* It's an absolute symlink */
160               else
161                 /* Back up to previous component, ignore if at root already: */
162                 if (dest > rpath_root + 1)
163                   while ((--dest)[-1] != '/');
164             }
165         }
166     }
167  done:
168   if (dest > rpath_root + 1 && dest[-1] == '/')
169     --dest;
170   *dest = '\0';
171
172   return rpath;
173
174  error:
175   free (rpath);
176   return NULL;
177 }