Blame hurd/lookup-retry.c

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