1 /* hairy bits of Hurd file name lookup
2    Copyright (C) 1992-2022 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4 
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9 
10    The GNU C Library 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 GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, see
17    <https://www.gnu.org/licenses/>.  */
18 
19 #include <hurd.h>
20 #include <hurd/lookup.h>
21 #include <hurd/term.h>
22 #include <hurd/paths.h>
23 #include <limits.h>
24 #include <fcntl.h>
25 #include <string.h>
26 #include <_itoa.h>
27 #include <eloop-threshold.h>
28 #include <unistd.h>
29 
30 /* Translate the error from dir_lookup into the error the user sees.  */
31 static inline error_t
lookup_error(error_t error)32 lookup_error (error_t error)
33 {
34   switch (error)
35     {
36     case EOPNOTSUPP:
37     case MIG_BAD_ID:
38       /* These indicate that the server does not understand dir_lookup
39 	 at all.  If it were a directory, it would, by definition.  */
40       return ENOTDIR;
41     default:
42       return error;
43     }
44 }
45 
46 error_t
__hurd_file_name_lookup_retry(error_t (* use_init_port)(int which,error_t (* operate)(file_t)),file_t (* get_dtable_port)(int fd),error_t (* lookup)(file_t dir,const char * name,int flags,mode_t mode,retry_type * do_retry,string_t retry_name,mach_port_t * result),enum retry_type doretry,char retryname[1024],int flags,mode_t mode,file_t * result)47 __hurd_file_name_lookup_retry (error_t (*use_init_port)
48 				 (int which, error_t (*operate) (file_t)),
49 			       file_t (*get_dtable_port) (int fd),
50 			       error_t (*lookup)
51 				 (file_t dir, const char *name,
52 				  int flags, mode_t mode,
53 				  retry_type *do_retry, string_t retry_name,
54 				  mach_port_t *result),
55 			       enum retry_type doretry,
56 			       char retryname[1024],
57 			       int flags, mode_t mode,
58 			       file_t *result)
59 {
60   error_t err;
61   char *file_name;
62   int nloops;
63   file_t lastdir = MACH_PORT_NULL;
64 
65   error_t lookup_op (file_t startdir)
66     {
67       if (file_name[0] == '/' && file_name[1] != '\0')
68 	{
69 	  while (file_name[1] == '/')
70 	    /* Remove double leading slash.  */
71 	    file_name++;
72 	  if (file_name[1] != '\0')
73 	    /* Remove leading slash when we have more than the slash.  */
74 	    file_name++;
75 	}
76 
77       return lookup_error ((*lookup) (startdir, file_name, flags, mode,
78 				      &doretry, retryname, result));
79     }
80   error_t reauthenticate (file_t unauth)
81     {
82       error_t err;
83       mach_port_t ref = __mach_reply_port ();
84       error_t reauth (auth_t auth)
85 	{
86 	  return __auth_user_authenticate (auth, ref,
87 					   MACH_MSG_TYPE_MAKE_SEND,
88 					   result);
89 	}
90       err = __io_reauthenticate (unauth, ref, MACH_MSG_TYPE_MAKE_SEND);
91       if (! err)
92 	err = (*use_init_port) (INIT_PORT_AUTH, &reauth);
93       __mach_port_destroy (__mach_task_self (), ref);
94       __mach_port_deallocate (__mach_task_self (), unauth);
95       return err;
96     }
97 
98   if (! lookup)
99     lookup = __dir_lookup;
100 
101   nloops = 0;
102   err = 0;
103   do
104     {
105       file_t startdir = MACH_PORT_NULL;
106       int dirport = INIT_PORT_CWDIR;
107 
108       switch (doretry)
109 	{
110 	case FS_RETRY_REAUTH:
111 	  if (err = reauthenticate (*result))
112 	    goto out;
113 	  /* Fall through.  */
114 
115 	case FS_RETRY_NORMAL:
116 	  if (nloops++ >= __eloop_threshold ())
117 	    {
118 	      __mach_port_deallocate (__mach_task_self (), *result);
119 	      err = ELOOP;
120 	      goto out;
121 	    }
122 
123 	  /* An empty RETRYNAME indicates we have the final port.  */
124 	  if (retryname[0] == '\0'
125 	      /* If reauth'd, we must do one more retry on "" to give the new
126 		 translator a chance to make a new port for us.  */
127 	      && doretry == FS_RETRY_NORMAL)
128 	    {
129 	      if (flags & O_NOFOLLOW)
130 		{
131 		  /* In Linux, O_NOFOLLOW means to reject symlinks.  If we
132 		     did an O_NOLINK lookup above and io_stat here to check
133 		     for S_IFLNK only, a translator like firmlink could easily
134 		     spoof this check by not showing S_IFLNK, but in fact
135 		     redirecting the lookup to some other name
136 		     (i.e. opening the very same holes a symlink would).
137 
138 		     Instead we do an O_NOTRANS lookup above, and stat the
139 		     underlying node: if it has a translator set, and its
140 		     owner is not root (st_uid 0) then we reject it.
141 		     Since the motivation for this feature is security, and
142 		     that security presumes we trust the containing
143 		     directory, this check approximates the security of
144 		     refusing symlinks while accepting mount points.
145 		     Note that we actually permit something Linux doesn't:
146 		     we follow root-owned symlinks; if that is deemed
147 		     undesireable, we can add a final check for that
148 		     one exception to our general translator-based rule.  */
149 		  struct stat64 st;
150 		  err = __io_stat (*result, &st);
151 		  if (!err)
152 		    {
153 		      if (flags & O_DIRECTORY && !S_ISDIR (st.st_mode))
154 			err = ENOTDIR;
155 		      if (S_ISLNK (st.st_mode))
156 			err = ELOOP;
157 		      else if (st.st_mode & (S_IPTRANS|S_IATRANS))
158 			{
159 			  if (st.st_uid != 0)
160 			    err = ELOOP;
161 			  else if (st.st_mode & S_IPTRANS)
162 			    {
163 			      char buf[1024];
164 			      char *trans = buf;
165 			      size_t translen = sizeof buf;
166 			      err = __file_get_translator (*result,
167 							   &trans, &translen);
168 			      if (!err
169 				  && translen > sizeof _HURD_SYMLINK
170 				  && !memcmp (trans,
171 					      _HURD_SYMLINK, sizeof _HURD_SYMLINK))
172 				err = ELOOP;
173 			    }
174 			}
175 		    }
176 		}
177 
178 	      /* We got a successful translation.  Now apply any open-time
179 		 action flags we were passed.  */
180 
181 	      if (!err && (flags & O_TRUNC))
182 		{
183 		  /* Asked to truncate the file.  */
184 		  err = __file_set_size (*result, 0);
185 		  if (!err)
186 		    {
187 		      struct timespec atime = { 0, UTIME_OMIT };
188 		      struct timespec mtime = { 0, UTIME_NOW };
189 		      __file_utimens (*result, atime, mtime);
190 		    }
191 		}
192 
193 	      if (err)
194 		__mach_port_deallocate (__mach_task_self (), *result);
195 	      goto out;
196 	    }
197 
198 	  startdir = *result;
199 	  file_name = retryname;
200 	  break;
201 
202 	case FS_RETRY_MAGICAL:
203 	  switch (retryname[0])
204 	    {
205 	    case '/':
206 	      dirport = INIT_PORT_CRDIR;
207 	      if (*result != MACH_PORT_NULL)
208 		__mach_port_deallocate (__mach_task_self (), *result);
209 	      if (nloops++ >= __eloop_threshold ())
210 		{
211 		  err = ELOOP;
212 		  goto out;
213 		}
214 	      file_name = &retryname[1];
215 	      break;
216 
217 	    case 'f':
218 	      if (retryname[1] == 'd' && retryname[2] == '/')
219 		{
220 		  int fd;
221 		  char *end;
222 		  int save = errno;
223 		  errno = 0;
224 		  fd = (int) __strtoul_internal (&retryname[3], &end, 10, 0);
225 		  if (end == NULL || errno /* Malformed number.  */
226 		      /* Check for excess text after the number.  A slash
227 			 is valid; it ends the component.  Anything else
228 			 does not name a numeric file descriptor.  */
229 		      || (*end != '/' && *end != '\0'))
230 		    {
231 		      errno = save;
232 		      err = ENOENT;
233 		      goto out;
234 		    }
235 		  if (! get_dtable_port)
236 		    err = EGRATUITOUS;
237 		  else
238 		    {
239 		      *result = (*get_dtable_port) (fd);
240 		      if (*result == MACH_PORT_NULL)
241 			{
242 			  /* If the name was a proper number, but the file
243 			     descriptor does not exist, we return EBADF instead
244 			     of ENOENT.  */
245 			  err = errno;
246 			  errno = save;
247 			}
248 		    }
249 		  errno = save;
250 		  if (err)
251 		    goto out;
252 		  if (*end == '\0')
253 		    {
254 		      err = 0;
255 		      goto out;
256 		    }
257 		  else
258 		    {
259 		      /* Do a normal retry on the remaining components.  */
260 		      startdir = *result;
261 		      file_name = end + 1; /* Skip the slash.  */
262 		      break;
263 		    }
264 		}
265 	      else
266 		goto bad_magic;
267 	      break;
268 
269 	    case 'm':
270 	      if (retryname[1] == 'a' && retryname[2] == 'c'
271 		  && retryname[3] == 'h' && retryname[4] == 't'
272 		  && retryname[5] == 'y' && retryname[6] == 'p'
273 		  && retryname[7] == 'e')
274 		{
275 		  error_t err;
276 		  struct host_basic_info hostinfo;
277 		  mach_msg_type_number_t hostinfocnt = HOST_BASIC_INFO_COUNT;
278 		  char *p;
279 		  /* XXX want client's host */
280 		  if (err = __host_info (__mach_host_self (), HOST_BASIC_INFO,
281 					 (integer_t *) &hostinfo,
282 					 &hostinfocnt))
283 		    goto out;
284 		  if (hostinfocnt != HOST_BASIC_INFO_COUNT)
285 		    {
286 		      err = EGRATUITOUS;
287 		      goto out;
288 		    }
289 		  p = _itoa (hostinfo.cpu_subtype, &retryname[8], 10, 0);
290 		  *--p = '/';
291 		  p = _itoa (hostinfo.cpu_type, &retryname[8], 10, 0);
292 		  if (p < retryname)
293 		    abort ();	/* XXX write this right if this ever happens */
294 		  if (p > retryname)
295 		    memmove (retryname, p, strlen(p) + 1);
296 		  startdir = *result;
297 		}
298 	      else
299 		goto bad_magic;
300 	      break;
301 
302 	    case 't':
303 	      if (retryname[1] == 't' && retryname[2] == 'y')
304 		switch (retryname[3])
305 		  {
306 		    error_t opentty (file_t *result)
307 		      {
308 			error_t err;
309 			error_t ctty_open (file_t port)
310 			  {
311 			    if (port == MACH_PORT_NULL)
312 			      return ENXIO; /* No controlling terminal.  */
313 			    return __termctty_open_terminal (port,
314 							     flags,
315 							     result);
316 			  }
317 			err = (*use_init_port) (INIT_PORT_CTTYID, &ctty_open);
318 			if (! err)
319 			  err = reauthenticate (*result);
320 			return err;
321 		      }
322 
323 		  case '\0':
324 		    err = opentty (result);
325 		    goto out;
326 		  case '/':
327 		    if (err = opentty (&startdir))
328 		      goto out;
329 		    memmove (retryname, &retryname[4], strlen(retryname + 4) + 1);
330 		    break;
331 		  default:
332 		    goto bad_magic;
333 		  }
334 	      else
335 		goto bad_magic;
336 	      break;
337 
338 	    case 'p':
339 	      if (retryname[1] == 'i' && retryname[2] == 'd'
340 		  && (retryname[3] == '/' || retryname[3] == 0))
341 		{
342 		  char *p, buf[1024];  /* XXX */
343 		  size_t len;
344 		  p = _itoa (__getpid (), &buf[sizeof buf], 10, 0);
345 		  len = &buf[sizeof buf] - p;
346 		  memcpy (buf, p, len);
347 		  strncpy (buf + len, &retryname[3], sizeof buf - len - 1);
348 		  buf[sizeof buf - 1] = '\0';
349 		  strcpy (retryname, buf);
350 
351 		  /* Do a normal retry on the remaining components.  */
352 		  __mach_port_mod_refs (__mach_task_self (), lastdir,
353 					MACH_PORT_RIGHT_SEND, 1);
354 		  startdir = lastdir;
355 		  file_name = retryname;
356 		}
357 	      else
358 		goto bad_magic;
359 	      break;
360 
361 	    default:
362 	    bad_magic:
363 	      err = EGRATUITOUS;
364 	      goto out;
365 	    }
366 	  break;
367 
368 	default:
369 	  err = EGRATUITOUS;
370 	  goto out;
371 	}
372 
373       if (MACH_PORT_VALID (*result) && *result != lastdir)
374 	{
375 	  if (MACH_PORT_VALID (lastdir))
376 	    __mach_port_deallocate (__mach_task_self (), lastdir);
377 
378 	  lastdir = *result;
379 	  __mach_port_mod_refs (__mach_task_self (), lastdir,
380 				MACH_PORT_RIGHT_SEND, 1);
381 	}
382 
383       if (startdir != MACH_PORT_NULL)
384 	{
385 	  err = lookup_op (startdir);
386 	  __mach_port_deallocate (__mach_task_self (), startdir);
387 	  startdir = MACH_PORT_NULL;
388 	}
389       else
390 	err = (*use_init_port) (dirport, &lookup_op);
391     } while (! err);
392 
393 out:
394   if (MACH_PORT_VALID (lastdir))
395     __mach_port_deallocate (__mach_task_self (), lastdir);
396 
397   return err;
398 }
399 weak_alias (__hurd_file_name_lookup_retry, hurd_file_name_lookup_retry)
400