(root)/
glibc-2.38/
hurd/
lookup-retry.c
       1  /* hairy bits of Hurd file name lookup
       2     Copyright (C) 1992-2023 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
      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
      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  		     undesirable, 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  			      mach_msg_type_number_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  #if !IS_IN (rtld)
     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  #endif
     193  
     194  	      if (err)
     195  		__mach_port_deallocate (__mach_task_self (), *result);
     196  	      goto out;
     197  	    }
     198  
     199  	  startdir = *result;
     200  	  file_name = retryname;
     201  	  break;
     202  
     203  	case FS_RETRY_MAGICAL:
     204  	  switch (retryname[0])
     205  	    {
     206  	    case '/':
     207  	      dirport = INIT_PORT_CRDIR;
     208  	      if (*result != MACH_PORT_NULL)
     209  		__mach_port_deallocate (__mach_task_self (), *result);
     210  	      if (nloops++ >= __eloop_threshold ())
     211  		{
     212  		  err = ELOOP;
     213  		  goto out;
     214  		}
     215  	      file_name = &retryname[1];
     216  	      break;
     217  
     218  #if !IS_IN (rtld)
     219  	    case 'f':
     220  	      if (retryname[1] == 'd' && retryname[2] == '/')
     221  		{
     222  		  int fd;
     223  		  char *end;
     224  		  int save = errno;
     225  		  errno = 0;
     226  		  fd = (int) __strtoul_internal (&retryname[3], &end, 10, 0);
     227  		  if (end == NULL || errno /* Malformed number.  */
     228  		      /* Check for excess text after the number.  A slash
     229  			 is valid; it ends the component.  Anything else
     230  			 does not name a numeric file descriptor.  */
     231  		      || (*end != '/' && *end != '\0'))
     232  		    {
     233  		      errno = save;
     234  		      err = ENOENT;
     235  		      goto out;
     236  		    }
     237  		  if (! get_dtable_port)
     238  		    err = EGRATUITOUS;
     239  		  else
     240  		    {
     241  		      *result = (*get_dtable_port) (fd);
     242  		      if (*result == MACH_PORT_NULL)
     243  			{
     244  			  /* If the name was a proper number, but the file
     245  			     descriptor does not exist, we return EBADF instead
     246  			     of ENOENT.  */
     247  			  err = errno;
     248  			  errno = save;
     249  			}
     250  		    }
     251  		  errno = save;
     252  		  if (err)
     253  		    goto out;
     254  		  if (*end == '\0')
     255  		    {
     256  		      err = 0;
     257  		      goto out;
     258  		    }
     259  		  else
     260  		    {
     261  		      /* Do a normal retry on the remaining components.  */
     262  		      startdir = *result;
     263  		      file_name = end + 1; /* Skip the slash.  */
     264  		      break;
     265  		    }
     266  		}
     267  	      else
     268  		goto bad_magic;
     269  	      break;
     270  
     271  	    case 'm':
     272  	      if (retryname[1] == 'a' && retryname[2] == 'c'
     273  		  && retryname[3] == 'h' && retryname[4] == 't'
     274  		  && retryname[5] == 'y' && retryname[6] == 'p'
     275  		  && retryname[7] == 'e')
     276  		{
     277  		  error_t err;
     278  		  struct host_basic_info hostinfo;
     279  		  mach_msg_type_number_t hostinfocnt = HOST_BASIC_INFO_COUNT;
     280  		  /* XXX want client's host */
     281  		  if (err = __host_info (__mach_host_self (), HOST_BASIC_INFO,
     282  					 (integer_t *) &hostinfo,
     283  					 &hostinfocnt))
     284  		    goto out;
     285  		  if (hostinfocnt != HOST_BASIC_INFO_COUNT)
     286  		    {
     287  		      err = EGRATUITOUS;
     288  		      goto out;
     289  		    }
     290  		  file_name = _itoa (hostinfo.cpu_subtype, &retryname[8], 10, 0);
     291  		  *--file_name = '/';
     292  		  file_name = _itoa (hostinfo.cpu_type, file_name, 10, 0);
     293  		  if (file_name < retryname)
     294  		    abort ();	/* XXX write this right if this ever happens */
     295  		  startdir = *result;
     296  		}
     297  	      else
     298  		goto bad_magic;
     299  	      break;
     300  
     301  	    case 't':
     302  	      if (retryname[1] == 't' && retryname[2] == 'y')
     303  		switch (retryname[3])
     304  		  {
     305  		    error_t opentty (file_t *result)
     306  		      {
     307  			error_t err;
     308  			error_t ctty_open (file_t port)
     309  			  {
     310  			    if (port == MACH_PORT_NULL)
     311  			      return ENXIO; /* No controlling terminal.  */
     312  			    return __termctty_open_terminal (port,
     313  							     flags,
     314  							     result);
     315  			  }
     316  			err = (*use_init_port) (INIT_PORT_CTTYID, &ctty_open);
     317  			if (! err)
     318  			  err = reauthenticate (*result);
     319  			return err;
     320  		      }
     321  
     322  		  case '\0':
     323  		    err = opentty (result);
     324  		    goto out;
     325  		  case '/':
     326  		    if (err = opentty (&startdir))
     327  		      goto out;
     328  		    memmove (retryname, &retryname[4], strlen(retryname + 4) + 1);
     329  		    break;
     330  		  default:
     331  		    goto bad_magic;
     332  		  }
     333  	      else
     334  		goto bad_magic;
     335  	      break;
     336  
     337  	    case 'p':
     338  	      if (retryname[1] == 'i' && retryname[2] == 'd'
     339  		  && (retryname[3] == '/' || retryname[3] == 0))
     340  		{
     341  		  char *p, buf[1024];  /* XXX */
     342  		  size_t len;
     343  		  p = _itoa (__getpid (), &buf[sizeof buf], 10, 0);
     344  		  len = &buf[sizeof buf] - p;
     345  		  memcpy (buf, p, len);
     346  		  strncpy (buf + len, &retryname[3], sizeof buf - len - 1);
     347  		  buf[sizeof buf - 1] = '\0';
     348  		  strcpy (retryname, buf);
     349  
     350  		  /* Do a normal retry on the remaining components.  */
     351  		  __mach_port_mod_refs (__mach_task_self (), lastdir,
     352  					MACH_PORT_RIGHT_SEND, 1);
     353  		  startdir = lastdir;
     354  		  file_name = retryname;
     355  		}
     356  	      else
     357  		goto bad_magic;
     358  	      break;
     359  
     360  	    bad_magic:
     361  #endif /* !IS_IN (rtld) */
     362  	    default:
     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)