1 /* Return the canonical absolute name of a given file inside chroot.
2    Copyright (C) 1996-2022 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 <https://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 *
chroot_canon(const char * chroot,const char * name)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 stat 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 (lstat (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 }
178