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