Blame hurd/lookup-retry.c

Packit Service 82fcde
/* hairy bits of Hurd file name lookup
Packit Service 82fcde
   Copyright (C) 1992-2018 Free Software Foundation, Inc.
Packit Service 82fcde
   This file is part of the GNU C Library.
Packit Service 82fcde
Packit Service 82fcde
   The GNU C Library is free software; you can redistribute it and/or
Packit Service 82fcde
   modify it under the terms of the GNU Lesser General Public
Packit Service 82fcde
   License as published by the Free Software Foundation; either
Packit Service 82fcde
   version 2.1 of the License, or (at your option) any later version.
Packit Service 82fcde
Packit Service 82fcde
   The GNU C Library is distributed in the hope that it will be useful,
Packit Service 82fcde
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 82fcde
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit Service 82fcde
   Lesser General Public License for more details.
Packit Service 82fcde
Packit Service 82fcde
   You should have received a copy of the GNU Lesser General Public
Packit Service 82fcde
   License along with the GNU C Library; if not, see
Packit Service 82fcde
   <http://www.gnu.org/licenses/>.  */
Packit Service 82fcde
Packit Service 82fcde
#include <hurd.h>
Packit Service 82fcde
#include <hurd/lookup.h>
Packit Service 82fcde
#include <hurd/term.h>
Packit Service 82fcde
#include <hurd/paths.h>
Packit Service 82fcde
#include <limits.h>
Packit Service 82fcde
#include <fcntl.h>
Packit Service 82fcde
#include <string.h>
Packit Service 82fcde
#include <_itoa.h>
Packit Service 82fcde
#include <eloop-threshold.h>
Packit Service 82fcde
Packit Service 82fcde
/* Translate the error from dir_lookup into the error the user sees.  */
Packit Service 82fcde
static inline error_t
Packit Service 82fcde
lookup_error (error_t error)
Packit Service 82fcde
{
Packit Service 82fcde
  switch (error)
Packit Service 82fcde
    {
Packit Service 82fcde
    case EOPNOTSUPP:
Packit Service 82fcde
    case MIG_BAD_ID:
Packit Service 82fcde
      /* These indicate that the server does not understand dir_lookup
Packit Service 82fcde
	 at all.  If it were a directory, it would, by definition.  */
Packit Service 82fcde
      return ENOTDIR;
Packit Service 82fcde
    default:
Packit Service 82fcde
      return error;
Packit Service 82fcde
    }
Packit Service 82fcde
}
Packit Service 82fcde
Packit Service 82fcde
error_t
Packit Service 82fcde
__hurd_file_name_lookup_retry (error_t (*use_init_port)
Packit Service 82fcde
				 (int which, error_t (*operate) (file_t)),
Packit Service 82fcde
			       file_t (*get_dtable_port) (int fd),
Packit Service 82fcde
			       error_t (*lookup)
Packit Service 82fcde
				 (file_t dir, const char *name,
Packit Service 82fcde
				  int flags, mode_t mode,
Packit Service 82fcde
				  retry_type *do_retry, string_t retry_name,
Packit Service 82fcde
				  mach_port_t *result),
Packit Service 82fcde
			       enum retry_type doretry,
Packit Service 82fcde
			       char retryname[1024],
Packit Service 82fcde
			       int flags, mode_t mode,
Packit Service 82fcde
			       file_t *result)
Packit Service 82fcde
{
Packit Service 82fcde
  error_t err;
Packit Service 82fcde
  char *file_name;
Packit Service 82fcde
  int nloops;
Packit Service 82fcde
Packit Service 82fcde
  error_t lookup_op (file_t startdir)
Packit Service 82fcde
    {
Packit Service 82fcde
      if (file_name[0] == '/' && file_name[1] != '\0')
Packit Service 82fcde
	{
Packit Service 82fcde
	  while (file_name[1] == '/')
Packit Service 82fcde
	    /* Remove double leading slash.  */
Packit Service 82fcde
	    file_name++;
Packit Service 82fcde
	  if (file_name[1] != '\0')
Packit Service 82fcde
	    /* Remove leading slash when we have more than the slash.  */
Packit Service 82fcde
	    file_name++;
Packit Service 82fcde
	}
Packit Service 82fcde
Packit Service 82fcde
      return lookup_error ((*lookup) (startdir, file_name, flags, mode,
Packit Service 82fcde
				      &doretry, retryname, result));
Packit Service 82fcde
    }
Packit Service 82fcde
  error_t reauthenticate (file_t unauth)
Packit Service 82fcde
    {
Packit Service 82fcde
      error_t err;
Packit Service 82fcde
      mach_port_t ref = __mach_reply_port ();
Packit Service 82fcde
      error_t reauth (auth_t auth)
Packit Service 82fcde
	{
Packit Service 82fcde
	  return __auth_user_authenticate (auth, ref,
Packit Service 82fcde
					   MACH_MSG_TYPE_MAKE_SEND,
Packit Service 82fcde
					   result);
Packit Service 82fcde
	}
Packit Service 82fcde
      err = __io_reauthenticate (unauth, ref, MACH_MSG_TYPE_MAKE_SEND);
Packit Service 82fcde
      if (! err)
Packit Service 82fcde
	err = (*use_init_port) (INIT_PORT_AUTH, &reauth);
Packit Service 82fcde
      __mach_port_destroy (__mach_task_self (), ref);
Packit Service 82fcde
      __mach_port_deallocate (__mach_task_self (), unauth);
Packit Service 82fcde
      return err;
Packit Service 82fcde
    }
Packit Service 82fcde
Packit Service 82fcde
  if (! lookup)
Packit Service 82fcde
    lookup = __dir_lookup;
Packit Service 82fcde
Packit Service 82fcde
  nloops = 0;
Packit Service 82fcde
  err = 0;
Packit Service 82fcde
  do
Packit Service 82fcde
    {
Packit Service 82fcde
      file_t startdir = MACH_PORT_NULL;
Packit Service 82fcde
      int dirport = INIT_PORT_CWDIR;
Packit Service 82fcde
Packit Service 82fcde
      switch (doretry)
Packit Service 82fcde
	{
Packit Service 82fcde
	case FS_RETRY_REAUTH:
Packit Service 82fcde
	  if (err = reauthenticate (*result))
Packit Service 82fcde
	    return err;
Packit Service 82fcde
	  /* Fall through.  */
Packit Service 82fcde
Packit Service 82fcde
	case FS_RETRY_NORMAL:
Packit Service 82fcde
	  if (nloops++ >= __eloop_threshold ())
Packit Service 82fcde
	    {
Packit Service 82fcde
	      __mach_port_deallocate (__mach_task_self (), *result);
Packit Service 82fcde
	      return ELOOP;
Packit Service 82fcde
	    }
Packit Service 82fcde
Packit Service 82fcde
	  /* An empty RETRYNAME indicates we have the final port.  */
Packit Service 82fcde
	  if (retryname[0] == '\0' &&
Packit Service 82fcde
	      /* If reauth'd, we must do one more retry on "" to give the new
Packit Service 82fcde
		 translator a chance to make a new port for us.  */
Packit Service 82fcde
	      doretry == FS_RETRY_NORMAL)
Packit Service 82fcde
	    {
Packit Service 82fcde
	      if (flags & O_NOFOLLOW)
Packit Service 82fcde
		{
Packit Service 82fcde
		  /* In Linux, O_NOFOLLOW means to reject symlinks.  If we
Packit Service 82fcde
		     did an O_NOLINK lookup above and io_stat here to check
Packit Service 82fcde
		     for S_IFLNK only, a translator like firmlink could easily
Packit Service 82fcde
		     spoof this check by not showing S_IFLNK, but in fact
Packit Service 82fcde
		     redirecting the lookup to some other name
Packit Service 82fcde
		     (i.e. opening the very same holes a symlink would).
Packit Service 82fcde
Packit Service 82fcde
		     Instead we do an O_NOTRANS lookup above, and stat the
Packit Service 82fcde
		     underlying node: if it has a translator set, and its
Packit Service 82fcde
		     owner is not root (st_uid 0) then we reject it.
Packit Service 82fcde
		     Since the motivation for this feature is security, and
Packit Service 82fcde
		     that security presumes we trust the containing
Packit Service 82fcde
		     directory, this check approximates the security of
Packit Service 82fcde
		     refusing symlinks while accepting mount points.
Packit Service 82fcde
		     Note that we actually permit something Linux doesn't:
Packit Service 82fcde
		     we follow root-owned symlinks; if that is deemed
Packit Service 82fcde
		     undesireable, we can add a final check for that
Packit Service 82fcde
		     one exception to our general translator-based rule.  */
Packit Service 82fcde
		  struct stat64 st;
Packit Service 82fcde
		  err = __io_stat (*result, &st);
Packit Service 82fcde
		  if (!err)
Packit Service 82fcde
		    {
Packit Service 82fcde
		      if (flags & O_DIRECTORY && !S_ISDIR (st.st_mode))
Packit Service 82fcde
			err = ENOTDIR;
Packit Service 82fcde
		      if (S_ISLNK (st.st_mode))
Packit Service 82fcde
			err = ELOOP;
Packit Service 82fcde
		      else if (st.st_mode & (S_IPTRANS|S_IATRANS))
Packit Service 82fcde
			{
Packit Service 82fcde
			  if (st.st_uid != 0)
Packit Service 82fcde
			    err = ELOOP;
Packit Service 82fcde
			  else if (st.st_mode & S_IPTRANS)
Packit Service 82fcde
			    {
Packit Service 82fcde
			      char buf[1024];
Packit Service 82fcde
			      char *trans = buf;
Packit Service 82fcde
			      size_t translen = sizeof buf;
Packit Service 82fcde
			      err = __file_get_translator (*result,
Packit Service 82fcde
							   &trans, &translen);
Packit Service 82fcde
			      if (!err
Packit Service 82fcde
				  && translen > sizeof _HURD_SYMLINK
Packit Service 82fcde
				  && !memcmp (trans,
Packit Service 82fcde
					      _HURD_SYMLINK, sizeof _HURD_SYMLINK))
Packit Service 82fcde
				err = ELOOP;
Packit Service 82fcde
			    }
Packit Service 82fcde
			}
Packit Service 82fcde
		    }
Packit Service 82fcde
		}
Packit Service 82fcde
Packit Service 82fcde
	      /* We got a successful translation.  Now apply any open-time
Packit Service 82fcde
		 action flags we were passed.  */
Packit Service 82fcde
Packit Service 82fcde
	      if (!err && (flags & O_TRUNC)) /* Asked to truncate the file.  */
Packit Service 82fcde
		err = __file_set_size (*result, 0);
Packit Service 82fcde
Packit Service 82fcde
	      if (err)
Packit Service 82fcde
		__mach_port_deallocate (__mach_task_self (), *result);
Packit Service 82fcde
	      return err;
Packit Service 82fcde
	    }
Packit Service 82fcde
Packit Service 82fcde
	  startdir = *result;
Packit Service 82fcde
	  file_name = retryname;
Packit Service 82fcde
	  break;
Packit Service 82fcde
Packit Service 82fcde
	case FS_RETRY_MAGICAL:
Packit Service 82fcde
	  switch (retryname[0])
Packit Service 82fcde
	    {
Packit Service 82fcde
	    case '/':
Packit Service 82fcde
	      dirport = INIT_PORT_CRDIR;
Packit Service 82fcde
	      if (*result != MACH_PORT_NULL)
Packit Service 82fcde
		__mach_port_deallocate (__mach_task_self (), *result);
Packit Service 82fcde
	      if (nloops++ >= __eloop_threshold ())
Packit Service 82fcde
		return ELOOP;
Packit Service 82fcde
	      file_name = &retryname[1];
Packit Service 82fcde
	      break;
Packit Service 82fcde
Packit Service 82fcde
	    case 'f':
Packit Service 82fcde
	      if (retryname[1] == 'd' && retryname[2] == '/')
Packit Service 82fcde
		{
Packit Service 82fcde
		  int fd;
Packit Service 82fcde
		  char *end;
Packit Service 82fcde
		  int save = errno;
Packit Service 82fcde
		  errno = 0;
Packit Service 82fcde
		  fd = (int) __strtoul_internal (&retryname[3], &end, 10, 0);
Packit Service 82fcde
		  if (end == NULL || errno || /* Malformed number.  */
Packit Service 82fcde
		      /* Check for excess text after the number.  A slash
Packit Service 82fcde
			 is valid; it ends the component.  Anything else
Packit Service 82fcde
			 does not name a numeric file descriptor.  */
Packit Service 82fcde
		      (*end != '/' && *end != '\0'))
Packit Service 82fcde
		    {
Packit Service 82fcde
		      errno = save;
Packit Service 82fcde
		      return ENOENT;
Packit Service 82fcde
		    }
Packit Service 82fcde
		  if (! get_dtable_port)
Packit Service 82fcde
		    err = EGRATUITOUS;
Packit Service 82fcde
		  else
Packit Service 82fcde
		    {
Packit Service 82fcde
		      *result = (*get_dtable_port) (fd);
Packit Service 82fcde
		      if (*result == MACH_PORT_NULL)
Packit Service 82fcde
			{
Packit Service 82fcde
			  /* If the name was a proper number, but the file
Packit Service 82fcde
			     descriptor does not exist, we return EBADF instead
Packit Service 82fcde
			     of ENOENT.  */
Packit Service 82fcde
			  err = errno;
Packit Service 82fcde
			  errno = save;
Packit Service 82fcde
			}
Packit Service 82fcde
		    }
Packit Service 82fcde
		  errno = save;
Packit Service 82fcde
		  if (err)
Packit Service 82fcde
		    return err;
Packit Service 82fcde
		  if (*end == '\0')
Packit Service 82fcde
		    return 0;
Packit Service 82fcde
		  else
Packit Service 82fcde
		    {
Packit Service 82fcde
		      /* Do a normal retry on the remaining components.  */
Packit Service 82fcde
		      startdir = *result;
Packit Service 82fcde
		      file_name = end + 1; /* Skip the slash.  */
Packit Service 82fcde
		      break;
Packit Service 82fcde
		    }
Packit Service 82fcde
		}
Packit Service 82fcde
	      else
Packit Service 82fcde
		goto bad_magic;
Packit Service 82fcde
	      break;
Packit Service 82fcde
Packit Service 82fcde
	    case 'm':
Packit Service 82fcde
	      if (retryname[1] == 'a' && retryname[2] == 'c' &&
Packit Service 82fcde
		  retryname[3] == 'h' && retryname[4] == 't' &&
Packit Service 82fcde
		  retryname[5] == 'y' && retryname[6] == 'p' &&
Packit Service 82fcde
		  retryname[7] == 'e')
Packit Service 82fcde
		{
Packit Service 82fcde
		  error_t err;
Packit Service 82fcde
		  struct host_basic_info hostinfo;
Packit Service 82fcde
		  mach_msg_type_number_t hostinfocnt = HOST_BASIC_INFO_COUNT;
Packit Service 82fcde
		  char *p;
Packit Service 82fcde
		  /* XXX want client's host */
Packit Service 82fcde
		  if (err = __host_info (__mach_host_self (), HOST_BASIC_INFO,
Packit Service 82fcde
					 (integer_t *) &hostinfo,
Packit Service 82fcde
					 &hostinfocnt))
Packit Service 82fcde
		    return err;
Packit Service 82fcde
		  if (hostinfocnt != HOST_BASIC_INFO_COUNT)
Packit Service 82fcde
		    return EGRATUITOUS;
Packit Service 82fcde
		  p = _itoa (hostinfo.cpu_subtype, &retryname[8], 10, 0);
Packit Service 82fcde
		  *--p = '/';
Packit Service 82fcde
		  p = _itoa (hostinfo.cpu_type, &retryname[8], 10, 0);
Packit Service 82fcde
		  if (p < retryname)
Packit Service 82fcde
		    abort ();	/* XXX write this right if this ever happens */
Packit Service 82fcde
		  if (p > retryname)
Packit Service 82fcde
		    strcpy (retryname, p);
Packit Service 82fcde
		  startdir = *result;
Packit Service 82fcde
		}
Packit Service 82fcde
	      else
Packit Service 82fcde
		goto bad_magic;
Packit Service 82fcde
	      break;
Packit Service 82fcde
Packit Service 82fcde
	    case 't':
Packit Service 82fcde
	      if (retryname[1] == 't' && retryname[2] == 'y')
Packit Service 82fcde
		switch (retryname[3])
Packit Service 82fcde
		  {
Packit Service 82fcde
		    error_t opentty (file_t *result)
Packit Service 82fcde
		      {
Packit Service 82fcde
			error_t err;
Packit Service 82fcde
			error_t ctty_open (file_t port)
Packit Service 82fcde
			  {
Packit Service 82fcde
			    if (port == MACH_PORT_NULL)
Packit Service 82fcde
			      return ENXIO; /* No controlling terminal.  */
Packit Service 82fcde
			    return __termctty_open_terminal (port,
Packit Service 82fcde
							     flags,
Packit Service 82fcde
							     result);
Packit Service 82fcde
			  }
Packit Service 82fcde
			err = (*use_init_port) (INIT_PORT_CTTYID, &ctty_open);
Packit Service 82fcde
			if (! err)
Packit Service 82fcde
			  err = reauthenticate (*result);
Packit Service 82fcde
			return err;
Packit Service 82fcde
		      }
Packit Service 82fcde
Packit Service 82fcde
		  case '\0':
Packit Service 82fcde
		    return opentty (result);
Packit Service 82fcde
		  case '/':
Packit Service 82fcde
		    if (err = opentty (&startdir))
Packit Service 82fcde
		      return err;
Packit Service 82fcde
		    strcpy (retryname, &retryname[4]);
Packit Service 82fcde
		    break;
Packit Service 82fcde
		  default:
Packit Service 82fcde
		    goto bad_magic;
Packit Service 82fcde
		  }
Packit Service 82fcde
	      else
Packit Service 82fcde
		goto bad_magic;
Packit Service 82fcde
	      break;
Packit Service 82fcde
Packit Service 82fcde
	    default:
Packit Service 82fcde
	    bad_magic:
Packit Service 82fcde
	      return EGRATUITOUS;
Packit Service 82fcde
	    }
Packit Service 82fcde
	  break;
Packit Service 82fcde
Packit Service 82fcde
	default:
Packit Service 82fcde
	  return EGRATUITOUS;
Packit Service 82fcde
	}
Packit Service 82fcde
Packit Service 82fcde
      if (startdir != MACH_PORT_NULL)
Packit Service 82fcde
	{
Packit Service 82fcde
	  err = lookup_op (startdir);
Packit Service 82fcde
	  __mach_port_deallocate (__mach_task_self (), startdir);
Packit Service 82fcde
	  startdir = MACH_PORT_NULL;
Packit Service 82fcde
	}
Packit Service 82fcde
      else
Packit Service 82fcde
	err = (*use_init_port) (dirport, &lookup_op);
Packit Service 82fcde
    } while (! err);
Packit Service 82fcde
Packit Service 82fcde
  return err;
Packit Service 82fcde
}
Packit Service 82fcde
weak_alias (__hurd_file_name_lookup_retry, hurd_file_name_lookup_retry)