1 /*        $NetBSD: paths.c,v 1.43 2025/05/02 23:04:56 riastradh Exp $  */
2 
3 /*
4  * Copyright 1996 Matt Thomas <matt@3am-software.com>
5  * Copyright 2002 Charles M. Hannum <root@ihack.net>
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. The name of the author may not be used to endorse or promote products
17  *    derived from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include <sys/cdefs.h>
32 #ifndef lint
33 __RCSID("$NetBSD: paths.c,v 1.43 2025/05/02 23:04:56 riastradh Exp $");
34 #endif /* not lint */
35 
36 #include <err.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <stdarg.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <sys/types.h>
45 #include <sys/param.h>
46 #include <sys/sysctl.h>
47 #include <sys/mman.h>
48 #include <sys/stat.h>
49 #include <sys/gmon.h>
50 #include <sys/socket.h>
51 #include <sys/mount.h>
52 #include <sys/resource.h>
53 #include <machine/cpu.h>
54 
55 #include "debug.h"
56 #include "rtld.h"
57 
58 #include "../../lib/libc/include/__sysctl.h" /* __sysctl syscall stub */
59 
60 static Search_Path *_rtld_find_path(Search_Path *, const char *, size_t);
61 static Search_Path **_rtld_append_path(Search_Path **, Search_Path **,
62     const char *, const char *, const char *);
63 static void _rtld_process_mapping(Library_Xform **, const char *,
64     const char *);
65 static char *exstrdup(const char *, const char *);
66 static const char *getstr(const char **, const char *, const char *);
67 static const char *getcstr(const char **, const char *, const char *);
68 static const char *getword(const char **, const char *, const char *);
69 static int matchstr(const char *, const char *, const char *);
70 
71 static const char WS[] = " \t\n";
72 
73 /*
74  * Like xstrdup(), but takes end of string as a argument.
75  */
76 static char *
exstrdup(const char * bp,const char * ep)77 exstrdup(const char *bp, const char *ep)
78 {
79           char *cp;
80           size_t len = ep - bp;
81 
82           cp = xmalloc(len + 1);
83           memcpy(cp, bp, len);
84           cp[len] = '\0';
85           return (cp);
86 }
87 
88 /*
89  * Like strsep(), but takes end of string and doesn't put any NUL.  To
90  * detect empty string, compare `*p' and return value.
91  */
92 static const char *
getstr(const char ** p,const char * ep,const char * delim)93 getstr(const char **p, const char *ep, const char *delim)
94 {
95           const char *cp = *p, *q, *r;
96 
97           if (ep < cp)
98                     /* End of string */
99                     return (NULL);
100 
101           for (q = cp; q < ep; q++)
102                     for (r = delim; *r != 0; r++)
103                               if (*r == *q)
104                                         goto done;
105 
106 done:
107           *p = q;
108           return (cp);
109 }
110 
111 /*
112  * Like getstr() above, but delim[] is complemented.
113  */
114 static const char *
getcstr(const char ** p,const char * ep,const char * delim)115 getcstr(const char **p, const char *ep, const char *delim)
116 {
117           const char *cp = *p, *q, *r;
118 
119           if (ep < cp)
120                     /* End of string */
121                     return (NULL);
122 
123           for (q = cp; q < ep; q++)
124                     for (r = delim; *r != *q; r++)
125                               if (*r == 0)
126                                         goto done;
127 
128 done:
129           *p = q;
130           return (cp);
131 }
132 
133 static const char *
getword(const char ** p,const char * ep,const char * delim)134 getword(const char **p, const char *ep, const char *delim)
135 {
136 
137           (void)getcstr(p, ep, delim);
138 
139           /*
140            * Now, we're looking non-delim, or end of string.
141            */
142 
143           return (getstr(p, ep, delim));
144 }
145 
146 /*
147  * Match `bp' against NUL terminated string pointed by `p'.
148  */
149 static int
matchstr(const char * p,const char * bp,const char * ep)150 matchstr(const char *p, const char *bp, const char *ep)
151 {
152           int c;
153 
154           while (bp < ep)
155                     if ((c = *p++) == 0 || c != *bp++)
156                               return (0);
157 
158           return (*p == 0);
159 }
160 
161 static Search_Path *
_rtld_find_path(Search_Path * path,const char * pathstr,size_t pathlen)162 _rtld_find_path(Search_Path *path, const char *pathstr, size_t pathlen)
163 {
164 
165           for (; path != NULL; path = path->sp_next) {
166                     if (pathlen == path->sp_pathlen &&
167                         memcmp(path->sp_path, pathstr, pathlen) == 0)
168                               return path;
169           }
170           return NULL;
171 }
172 
173 static Search_Path **
_rtld_append_path(Search_Path ** head_p,Search_Path ** path_p,const char * execname,const char * bp,const char * ep)174 _rtld_append_path(Search_Path **head_p, Search_Path **path_p,
175     const char *execname, const char *bp, const char *ep)
176 {
177           Search_Path *path;
178           char epath[MAXPATHLEN];
179           size_t len;
180 
181           len = _rtld_expand_path(epath, sizeof(epath), execname, bp, ep);
182           if (len == 0)
183                     return path_p;
184 
185           if (_rtld_find_path(*head_p, bp, ep - bp) != NULL)
186                     return path_p;
187 
188           path = NEW(Search_Path);
189           path->sp_pathlen = len;
190           path->sp_path = exstrdup(epath, epath + len);
191           path->sp_next = (*path_p);
192           (*path_p) = path;
193           path_p = &path->sp_next;
194 
195           dbg((" added path \"%s\"", path->sp_path));
196           return path_p;
197 }
198 
199 void
_rtld_add_paths(const char * execname,Search_Path ** path_p,const char * pathstr)200 _rtld_add_paths(const char *execname, Search_Path **path_p, const char *pathstr)
201 {
202           Search_Path **head_p = path_p;
203 
204           if (pathstr == NULL)
205                     return;
206 
207           if (pathstr[0] == ':') {
208                     /*
209                      * Leading colon means append to current path
210                      */
211                     while ((*path_p) != NULL)
212                               path_p = &(*path_p)->sp_next;
213                     pathstr++;
214           }
215 
216           for (;;) {
217                     const char *bp = pathstr;
218                     const char *ep = strchr(bp, ':');
219                     if (ep == NULL)
220                               ep = &pathstr[strlen(pathstr)];
221 
222                     path_p = _rtld_append_path(head_p, path_p, execname, bp, ep);
223 
224                     if (ep[0] == '\0')
225                               break;
226                     pathstr = ep + 1;
227           }
228 }
229 
230 /*
231  * Process library mappings of the form:
232  *        <library_name>      <machdep_variable> <value,...:library_name,...> ...
233  */
234 static void
_rtld_process_mapping(Library_Xform ** lib_p,const char * bp,const char * ep)235 _rtld_process_mapping(Library_Xform **lib_p, const char *bp, const char *ep)
236 {
237           Library_Xform *hwptr = NULL;
238           const char *ptr, *key, *ekey, *lib, *elib, *l;
239           int i, j;
240 
241           dbg((" processing mapping \"%.*s\"", (int)(ep - bp), bp));
242 
243           if ((ptr = getword(&bp, ep, WS)) == NULL || ptr == bp)
244                     return;
245 
246           dbg((" library \"%.*s\"", (int)(bp - ptr), ptr));
247 
248           hwptr = xmalloc(sizeof(*hwptr));
249           memset(hwptr, 0, sizeof(*hwptr));
250           hwptr->name = exstrdup(ptr, bp);
251 
252           bp++;
253 
254           if ((ptr = getword(&bp, ep, WS)) == NULL || ptr == bp) {
255                     xwarnx("missing sysctl variable name");
256                     goto cleanup;
257           }
258 
259           dbg((" sysctl \"%.*s\"", (int)(bp - ptr), ptr));
260 
261           hwptr->ctlname = exstrdup(ptr, bp);
262 
263           for (i = 0; bp++, (ptr = getword(&bp, ep, WS)) != NULL;) {
264                     dbg((" ptr = %.*s", (int)(bp - ptr), ptr));
265                     if (ptr == bp)
266                               continue;
267 
268                     if (i == RTLD_MAX_ENTRY) {
269 no_more:
270                               xwarnx("maximum library entries exceeded `%s'",
271                                   hwptr->name);
272                               goto cleanup;
273                     }
274                     if ((key = getstr(&ptr, bp, ":")) == NULL) {
275                               xwarnx("missing sysctl variable value for `%s'",
276                                   hwptr->name);
277                               goto cleanup;
278                     }
279                     ekey = ptr++;
280                     if ((lib = getstr(&ptr, bp, ":")) == NULL) {
281                               xwarnx("missing sysctl library list for `%s'",
282                                   hwptr->name);
283                               goto cleanup;
284                     }
285                     elib = ptr;                   /* No need to advance */
286                     for (j = 0; (l = getstr(&lib, elib, ",")) != NULL;
287                         j++, lib++) {
288                               if (j == RTLD_MAX_LIBRARY) {
289                                         xwarnx("maximum library entries exceeded `%s'",
290                                             hwptr->name);
291                                         goto cleanup;
292                               }
293                               dbg((" library \"%.*s\"", (int)(lib - l), l));
294                               hwptr->entry[i].library[j] = exstrdup(l, lib);
295                     }
296                     if (j == 0) {
297                               xwarnx("No library map entries for `%s/%.*s'",
298                                   hwptr->name, (int)(bp - ptr), ptr);
299                               goto cleanup;
300                     }
301                     j = i;
302                     for (; (l = getstr(&key, ekey, ",")) != NULL; i++, key++) {
303                               /*
304                                * Allow empty key (it is valid as string
305                                * value).  Thus, we loop at least once and
306                                * `i' is incremented.
307                                */
308 
309                               dbg((" key \"%.*s\"", (int)(key - l), l));
310                               if (i == RTLD_MAX_ENTRY)
311                                         goto no_more;
312                               if (i != j)
313                                         (void)memcpy(hwptr->entry[i].library,
314                                             hwptr->entry[j].library,
315                                             sizeof(hwptr->entry[j].library));
316                               hwptr->entry[i].value = exstrdup(l, key);
317                     }
318           }
319 
320           if (i == 0) {
321                     xwarnx("No library entries for `%s'", hwptr->name);
322                     goto cleanup;
323           }
324 
325           hwptr->next = *lib_p;
326           *lib_p = hwptr;
327 
328           return;
329 
330 cleanup:
331           if (hwptr->name)
332                     xfree(hwptr->name);
333           xfree(hwptr);
334 }
335 
336 void
_rtld_process_hints(const char * execname,Search_Path ** path_p,Library_Xform ** lib_p,const char * fname)337 _rtld_process_hints(const char *execname, Search_Path **path_p,
338     Library_Xform **lib_p, const char *fname)
339 {
340           int fd;
341           char *buf, small[128];
342           const char *b, *ep, *ptr;
343           struct stat st;
344           ssize_t sz;
345           Search_Path **head_p = path_p;
346 
347           if ((fd = open(fname, O_RDONLY)) == -1) {
348                     /* Don't complain */
349                     return;
350           }
351 
352           /* Try to avoid mmap/stat on the file. */
353           buf = small;
354           buf[0] = '\0';
355           sz = read(fd, buf, sizeof(small));
356           if (sz == -1) {
357                     xwarn("read: %s", fname);
358                     (void)close(fd);
359                     return;
360           }
361           if (sz >= (ssize_t)sizeof(small)) {
362                     if (fstat(fd, &st) == -1) {
363                               /* Complain */
364                               xwarn("fstat: %s", fname);
365                               (void)close(fd);
366                               return;
367                     }
368 
369                     sz = (ssize_t) st.st_size;
370 
371                     buf = mmap(0, sz, PROT_READ, MAP_SHARED|MAP_FILE, fd, 0);
372                     if (buf == MAP_FAILED) {
373                               xwarn("mmap: %s", fname);
374                               (void)close(fd);
375                               return;
376                     }
377           }
378           (void)close(fd);
379 
380           while ((*path_p) != NULL)
381                     path_p = &(*path_p)->sp_next;
382 
383           for (b = buf, ep = buf + sz; b < ep; b++) {
384                     (void)getcstr(&b, ep, WS);
385                     if (b == ep)
386                               break;
387 
388                     ptr = getstr(&b, ep, "\n#");
389                     if (*ptr == '/') {
390                               /*
391                                * Since '/' != '\n' and != '#', we know ptr <
392                                * b.  And we will stop when b[-1] == '/'.
393                                */
394                               while (b[-1] == ' ' || b[-1] == '\t')
395                                         b--;
396                               path_p = _rtld_append_path(head_p, path_p, execname,
397                                   ptr, b);
398                     } else
399                               _rtld_process_mapping(lib_p, ptr, b);
400 
401                     /*
402                      * b points one of ' ', \t, \n, # or equal to ep.  So,
403                      * make sure we are at newline or end of string.
404                      */
405                     (void)getstr(&b, ep, "\n");
406           }
407 
408           if (buf != small)
409                     (void)munmap(buf, sz);
410 }
411 
412 /* Basic name -> sysctl MIB translation */
413 int
_rtld_sysctl(const char * name,void * oldp,size_t * oldlen)414 _rtld_sysctl(const char *name, void *oldp, size_t *oldlen)
415 {
416           const char *node, *ep;
417           struct sysctlnode query, *result, *newresult;
418           int mib[CTL_MAXNAME], r;
419           size_t res_size, n, i;
420           u_int miblen = 0;
421 
422           /* Start with 16 entries, will grow it up as needed. */
423           res_size = 16 * sizeof(struct sysctlnode);
424           result = xmalloc(res_size);
425           if (result == NULL)
426                     return (-1);
427 
428           ep = name + strlen(name);
429           do {
430                     i = ~0ul;
431                     while (*name == '/' || *name == '.')
432                               name++;
433                     if (name >= ep)
434                               break;
435 
436                     mib[miblen] = CTL_QUERY;
437                     memset(&query, 0, sizeof(query));
438                     query.sysctl_flags = SYSCTL_VERSION;
439 
440                     n = res_size;
441                     if (__sysctl(mib, miblen + 1, result, &n, &query,
442                         sizeof(query)) == -1) {
443                               if (errno != ENOMEM)
444                                         goto bad;
445                               /* Grow up result */
446                               res_size = n;
447                               newresult = xrealloc(result, res_size);
448                               if (newresult == NULL)
449                                         goto bad;
450                               result = newresult;
451                               if (__sysctl(mib, miblen + 1, result, &n, &query,
452                                   sizeof(query)) == -1)
453                                         goto bad;
454                     }
455                     n /= sizeof(struct sysctlnode);
456 
457                     node = getstr(&name, ep, "./");
458 
459                     for (i = 0; i < n; i++)
460                               if (matchstr(result[i].sysctl_name, node, name)) {
461                                         mib[miblen] = result[i].sysctl_num;
462                                         miblen++;
463                                         break;
464                               }
465           } while (name < ep && miblen <= CTL_MAXNAME);
466 
467           if (name < ep || i == ~0ul)
468                     goto bad;
469           r = SYSCTL_TYPE(result[i].sysctl_flags);
470 
471           xfree(result);
472           if (__sysctl(mib, miblen, oldp, oldlen, NULL, 0) == -1)
473                     return (-1);
474           return r;
475 
476 bad:
477           xfree(result);
478           return (-1);
479 }
480