mirror of
				https://sourceware.org/git/glibc.git
				synced 2025-10-30 10:45:40 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			325 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* hairy bits of Hurd file name lookup
 | |
|    Copyright (C) 1992-2012 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
 | |
|    <http://www.gnu.org/licenses/>.  */
 | |
| 
 | |
| #include <hurd.h>
 | |
| #include <hurd/lookup.h>
 | |
| #include <hurd/term.h>
 | |
| #include <hurd/paths.h>
 | |
| #include <limits.h>
 | |
| #include <fcntl.h>
 | |
| #include <string.h>
 | |
| #include <_itoa.h>
 | |
| #include <eloop-threshold.h>
 | |
| 
 | |
| /* Translate the error from dir_lookup into the error the user sees.  */
 | |
| static inline error_t
 | |
| lookup_error (error_t error)
 | |
| {
 | |
|   switch (error)
 | |
|     {
 | |
|     case EOPNOTSUPP:
 | |
|     case MIG_BAD_ID:
 | |
|       /* These indicate that the server does not understand dir_lookup
 | |
| 	 at all.  If it were a directory, it would, by definition.  */
 | |
|       return ENOTDIR;
 | |
|     default:
 | |
|       return error;
 | |
|     }
 | |
| }
 | |
| 
 | |
| 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, 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)
 | |
| {
 | |
|   error_t err;
 | |
|   char *file_name;
 | |
|   int nloops;
 | |
| 
 | |
|   error_t lookup_op (file_t startdir)
 | |
|     {
 | |
|       while (file_name[0] == '/')
 | |
| 	file_name++;
 | |
| 
 | |
|       return lookup_error ((*lookup) (startdir, file_name, flags, mode,
 | |
| 				      &doretry, retryname, result));
 | |
|     }
 | |
|   error_t reauthenticate (file_t unauth)
 | |
|     {
 | |
|       error_t err;
 | |
|       mach_port_t ref = __mach_reply_port ();
 | |
|       error_t reauth (auth_t auth)
 | |
| 	{
 | |
| 	  return __auth_user_authenticate (auth, ref,
 | |
| 					   MACH_MSG_TYPE_MAKE_SEND,
 | |
| 					   result);
 | |
| 	}
 | |
|       err = __io_reauthenticate (unauth, ref, MACH_MSG_TYPE_MAKE_SEND);
 | |
|       if (! err)
 | |
| 	err = (*use_init_port) (INIT_PORT_AUTH, &reauth);
 | |
|       __mach_port_destroy (__mach_task_self (), ref);
 | |
|       __mach_port_deallocate (__mach_task_self (), unauth);
 | |
|       return err;
 | |
|     }
 | |
| 
 | |
|   if (! lookup)
 | |
|     lookup = __dir_lookup;
 | |
| 
 | |
|   nloops = 0;
 | |
|   err = 0;
 | |
|   do
 | |
|     {
 | |
|       file_t startdir = MACH_PORT_NULL;
 | |
|       int dirport = INIT_PORT_CWDIR;
 | |
| 
 | |
|       switch (doretry)
 | |
| 	{
 | |
| 	case FS_RETRY_REAUTH:
 | |
| 	  if (err = reauthenticate (*result))
 | |
| 	    return err;
 | |
| 	  /* Fall through.  */
 | |
| 
 | |
| 	case FS_RETRY_NORMAL:
 | |
| 	  if (nloops++ >= __eloop_threshold ())
 | |
| 	    {
 | |
| 	      __mach_port_deallocate (__mach_task_self (), *result);
 | |
| 	      return ELOOP;
 | |
| 	    }
 | |
| 
 | |
| 	  /* An empty RETRYNAME indicates we have the final port.  */
 | |
| 	  if (retryname[0] == '\0' &&
 | |
| 	      /* If reauth'd, we must do one more retry on "" to give the new
 | |
| 		 translator a chance to make a new port for us.  */
 | |
| 	      doretry == FS_RETRY_NORMAL)
 | |
| 	    {
 | |
| 	      if (flags & O_NOFOLLOW)
 | |
| 		{
 | |
| 		  /* In Linux, O_NOFOLLOW means to reject symlinks.  If we
 | |
| 		     did an O_NOLINK lookup above and io_stat here to check
 | |
| 		     for S_IFLNK, a translator like firmlink could easily
 | |
| 		     spoof this check by not showing S_IFLNK, but in fact
 | |
| 		     redirecting the lookup to some other name
 | |
| 		     (i.e. opening the very same holes a symlink would).
 | |
| 
 | |
| 		     Instead we do an O_NOTRANS lookup above, and stat the
 | |
| 		     underlying node: if it has a translator set, and its
 | |
| 		     owner is not root (st_uid 0) then we reject it.
 | |
| 		     Since the motivation for this feature is security, and
 | |
| 		     that security presumes we trust the containing
 | |
| 		     directory, this check approximates the security of
 | |
| 		     refusing symlinks while accepting mount points.
 | |
| 		     Note that we actually permit something Linux doesn't:
 | |
| 		     we follow root-owned symlinks; if that is deemed
 | |
| 		     undesireable, we can add a final check for that
 | |
| 		     one exception to our general translator-based rule.  */
 | |
| 		  struct stat64 st;
 | |
| 		  err = __io_stat (*result, &st);
 | |
| 		  if (!err
 | |
| 		      && (st.st_mode & (S_IPTRANS|S_IATRANS)))
 | |
| 		    {
 | |
| 		      if (st.st_uid != 0)
 | |
| 			err = ENOENT;
 | |
| 		      else if (st.st_mode & S_IPTRANS)
 | |
| 			{
 | |
| 			  char buf[1024];
 | |
| 			  char *trans = buf;
 | |
| 			  size_t translen = sizeof buf;
 | |
| 			  err = __file_get_translator (*result,
 | |
| 						       &trans, &translen);
 | |
| 			  if (!err
 | |
| 			      && translen > sizeof _HURD_SYMLINK
 | |
| 			      && !memcmp (trans,
 | |
| 					  _HURD_SYMLINK, sizeof _HURD_SYMLINK))
 | |
| 			    err = ENOENT;
 | |
| 			}
 | |
| 		    }
 | |
| 		}
 | |
| 
 | |
| 	      /* We got a successful translation.  Now apply any open-time
 | |
| 		 action flags we were passed.  */
 | |
| 
 | |
| 	      if (!err && (flags & O_TRUNC)) /* Asked to truncate the file.  */
 | |
| 		err = __file_set_size (*result, 0);
 | |
| 
 | |
| 	      if (err)
 | |
| 		__mach_port_deallocate (__mach_task_self (), *result);
 | |
| 	      return err;
 | |
| 	    }
 | |
| 
 | |
| 	  startdir = *result;
 | |
| 	  file_name = retryname;
 | |
| 	  break;
 | |
| 
 | |
| 	case FS_RETRY_MAGICAL:
 | |
| 	  switch (retryname[0])
 | |
| 	    {
 | |
| 	    case '/':
 | |
| 	      dirport = INIT_PORT_CRDIR;
 | |
| 	      if (*result != MACH_PORT_NULL)
 | |
| 		__mach_port_deallocate (__mach_task_self (), *result);
 | |
| 	      if (nloops++ >= __eloop_threshold ())
 | |
| 		return ELOOP;
 | |
| 	      file_name = &retryname[1];
 | |
| 	      break;
 | |
| 
 | |
| 	    case 'f':
 | |
| 	      if (retryname[1] == 'd' && retryname[2] == '/')
 | |
| 		{
 | |
| 		  int fd;
 | |
| 		  char *end;
 | |
| 		  int save = errno;
 | |
| 		  errno = 0;
 | |
| 		  fd = (int) __strtoul_internal (&retryname[3], &end, 10, 0);
 | |
| 		  if (end == NULL || errno || /* Malformed number.  */
 | |
| 		      /* Check for excess text after the number.  A slash
 | |
| 			 is valid; it ends the component.  Anything else
 | |
| 			 does not name a numeric file descriptor.  */
 | |
| 		      (*end != '/' && *end != '\0'))
 | |
| 		    {
 | |
| 		      errno = save;
 | |
| 		      return ENOENT;
 | |
| 		    }
 | |
| 		  if (! get_dtable_port)
 | |
| 		    err = EGRATUITOUS;
 | |
| 		  else
 | |
| 		    {
 | |
| 		      *result = (*get_dtable_port) (fd);
 | |
| 		      if (*result == MACH_PORT_NULL)
 | |
| 			{
 | |
| 			  /* If the name was a proper number, but the file
 | |
| 			     descriptor does not exist, we return EBADF instead
 | |
| 			     of ENOENT.  */
 | |
| 			  err = errno;
 | |
| 			  errno = save;
 | |
| 			}
 | |
| 		    }
 | |
| 		  errno = save;
 | |
| 		  if (err)
 | |
| 		    return err;
 | |
| 		  if (*end == '\0')
 | |
| 		    return 0;
 | |
| 		  else
 | |
| 		    {
 | |
| 		      /* Do a normal retry on the remaining components.  */
 | |
| 		      startdir = *result;
 | |
| 		      file_name = end + 1; /* Skip the slash.  */
 | |
| 		      break;
 | |
| 		    }
 | |
| 		}
 | |
| 	      else
 | |
| 		goto bad_magic;
 | |
| 	      break;
 | |
| 
 | |
| 	    case 'm':
 | |
| 	      if (retryname[1] == 'a' && retryname[2] == 'c' &&
 | |
| 		  retryname[3] == 'h' && retryname[4] == 't' &&
 | |
| 		  retryname[5] == 'y' && retryname[6] == 'p' &&
 | |
| 		  retryname[7] == 'e')
 | |
| 		{
 | |
| 		  error_t err;
 | |
| 		  struct host_basic_info hostinfo;
 | |
| 		  mach_msg_type_number_t hostinfocnt = HOST_BASIC_INFO_COUNT;
 | |
| 		  char *p;
 | |
| 		  /* XXX want client's host */
 | |
| 		  if (err = __host_info (__mach_host_self (), HOST_BASIC_INFO,
 | |
| 					 (integer_t *) &hostinfo,
 | |
| 					 &hostinfocnt))
 | |
| 		    return err;
 | |
| 		  if (hostinfocnt != HOST_BASIC_INFO_COUNT)
 | |
| 		    return EGRATUITOUS;
 | |
| 		  p = _itoa (hostinfo.cpu_subtype, &retryname[8], 10, 0);
 | |
| 		  *--p = '/';
 | |
| 		  p = _itoa (hostinfo.cpu_type, &retryname[8], 10, 0);
 | |
| 		  if (p < retryname)
 | |
| 		    abort ();	/* XXX write this right if this ever happens */
 | |
| 		  if (p > retryname)
 | |
| 		    strcpy (retryname, p);
 | |
| 		  startdir = *result;
 | |
| 		}
 | |
| 	      else
 | |
| 		goto bad_magic;
 | |
| 	      break;
 | |
| 
 | |
| 	    case 't':
 | |
| 	      if (retryname[1] == 't' && retryname[2] == 'y')
 | |
| 		switch (retryname[3])
 | |
| 		  {
 | |
| 		    error_t opentty (file_t *result)
 | |
| 		      {
 | |
| 			error_t err;
 | |
| 			error_t ctty_open (file_t port)
 | |
| 			  {
 | |
| 			    if (port == MACH_PORT_NULL)
 | |
| 			      return ENXIO; /* No controlling terminal.  */
 | |
| 			    return __termctty_open_terminal (port,
 | |
| 							     flags,
 | |
| 							     result);
 | |
| 			  }
 | |
| 			err = (*use_init_port) (INIT_PORT_CTTYID, &ctty_open);
 | |
| 			if (! err)
 | |
| 			  err = reauthenticate (*result);
 | |
| 			return err;
 | |
| 		      }
 | |
| 
 | |
| 		  case '\0':
 | |
| 		    return opentty (result);
 | |
| 		  case '/':
 | |
| 		    if (err = opentty (&startdir))
 | |
| 		      return err;
 | |
| 		    strcpy (retryname, &retryname[4]);
 | |
| 		    break;
 | |
| 		  default:
 | |
| 		    goto bad_magic;
 | |
| 		  }
 | |
| 	      else
 | |
| 		goto bad_magic;
 | |
| 	      break;
 | |
| 
 | |
| 	    default:
 | |
| 	    bad_magic:
 | |
| 	      return EGRATUITOUS;
 | |
| 	    }
 | |
| 	  break;
 | |
| 
 | |
| 	default:
 | |
| 	  return EGRATUITOUS;
 | |
| 	}
 | |
| 
 | |
|       if (startdir != MACH_PORT_NULL)
 | |
| 	{
 | |
| 	  err = lookup_op (startdir);
 | |
| 	  __mach_port_deallocate (__mach_task_self (), startdir);
 | |
| 	  startdir = MACH_PORT_NULL;
 | |
| 	}
 | |
|       else
 | |
| 	err = (*use_init_port) (dirport, &lookup_op);
 | |
|     } while (! err);
 | |
| 
 | |
|   return err;
 | |
| }
 | |
| weak_alias (__hurd_file_name_lookup_retry, hurd_file_name_lookup_retry)
 |