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