Branch data Line data Source code
1 : : /*-
2 : : * Copyright (c) 1998 John D. Polstra
3 : : * Copyright (c) 2012 Matthew Seaman <matthew@FreeBSD.org>
4 : : * All rights reserved.
5 : : *
6 : : * Redistribution and use in source and binary forms, with or without
7 : : * modification, are permitted provided that the following conditions
8 : : * are met:
9 : : * 1. Redistributions of source code must retain the above copyright
10 : : * notice, this list of conditions and the following disclaimer.
11 : : * 2. Redistributions in binary form must reproduce the above copyright
12 : : * notice, this list of conditions and the following disclaimer in the
13 : : * documentation and/or other materials provided with the distribution.
14 : : *
15 : : * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 : : * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 : : * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 : : * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 : : * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 : : * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 : : * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 : : * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 : : * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 : : * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 : : * SUCH DAMAGE.
26 : : *
27 : : * $FreeBSD: stable/8/sbin/ldconfig/elfhints.c 76224 2001-05-02 23:56:21Z obrien $
28 : : */
29 : :
30 : : #include <bsd_compat.h>
31 : : #include <sys/mman.h>
32 : : #include <sys/stat.h>
33 : :
34 : : #include <assert.h>
35 : : #include <ctype.h>
36 : : #include <dirent.h>
37 : : #include <err.h>
38 : : #include <errno.h>
39 : : #include <fcntl.h>
40 : : #include <stdio.h>
41 : : #include <stdlib.h>
42 : : #include <string.h>
43 : : #include <unistd.h>
44 : :
45 : : #include "pkg.h"
46 : : #include "private/pkg.h"
47 : : #include "private/ldconfig.h"
48 : :
49 : : #define MAXDIRS 1024 /* Maximum directories in path */
50 : : #define MAXFILESIZE (16*1024) /* Maximum hints file size */
51 : :
52 : : struct shlib {
53 : : const char *name;
54 : : char path[];
55 : : };
56 : :
57 : : static int shlib_list_add(pkghash **shlib_list,
58 : : const char *dir, const char *shlib_file);
59 : : static int scan_dirs_for_shlibs(pkghash **shlib_list,
60 : : int numdirs, const char **dirlist,
61 : : bool strictnames);
62 : : static void add_dir(const char *, const char *, int);
63 : : static void read_dirs_from_file(const char *, const char *);
64 : : static void read_elf_hints(const char *, int);
65 : : static void write_elf_hints(const char *);
66 : :
67 : : static const char *dirs[MAXDIRS];
68 : : static int ndirs;
69 : : int insecure;
70 : :
71 : : /* Known shlibs on the standard system search path. Persistent,
72 : : common to all applications */
73 : : static pkghash *shlibs = NULL;
74 : :
75 : : /* Known shlibs on the specific RPATH or RUNPATH of one binary.
76 : : Evanescent. */
77 : : static pkghash *rpath = NULL;
78 : :
79 : : void
80 : 1331 : shlib_list_init(void)
81 : : {
82 [ + - ]: 1331 : assert(pkghash_count(shlibs) == 0);
83 : 1331 : }
84 : :
85 : : void
86 : 48 : rpath_list_init(void)
87 : : {
88 [ + - ]: 48 : assert(pkghash_count(rpath) == 0);
89 : 48 : }
90 : :
91 : : static int
92 : 5249536 : shlib_list_add(pkghash **shlib_list, const char *dir,
93 : : const char *shlib_file)
94 : : {
95 : : struct shlib *sl;
96 : : size_t path_len, dir_len;
97 : :
98 : : /* If shlib_file is already in the shlib_list table, don't try
99 : : * and add it again */
100 [ + + ]: 5249536 : if (pkghash_get(*shlib_list, shlib_file) != NULL)
101 : 27951 : return (EPKG_OK);
102 : :
103 : 5221585 : path_len = strlen(dir) + strlen(shlib_file) + 2;
104 : :
105 : 5221585 : sl = xcalloc(1, sizeof(struct shlib) + path_len);
106 : :
107 : 5221585 : strlcpy(sl->path, dir, path_len);
108 : 5221585 : dir_len = strlcat(sl->path, "/", path_len);
109 : 5221585 : strlcat(sl->path, shlib_file, path_len);
110 : :
111 : 5221585 : sl->name = sl->path + dir_len;
112 : :
113 [ + + - + ]: 10441823 : pkghash_safe_add(*shlib_list, sl->name, sl, free);
114 : :
115 : 5221585 : return (EPKG_OK);
116 : 5249536 : }
117 : :
118 : : const char *
119 : 64 : shlib_list_find_by_name(const char *shlib_file)
120 : : {
121 : : struct shlib *sl;
122 : :
123 : 64 : sl = pkghash_get_value(rpath, shlib_file);
124 [ + + ]: 64 : if (sl != NULL)
125 : 16 : return (sl->path);
126 : :
127 : 48 : sl = pkghash_get_value(shlibs, shlib_file);
128 [ - + ]: 48 : if (sl != NULL)
129 : 48 : return (sl->path);
130 : :
131 : 0 : return (NULL);
132 : 64 : }
133 : :
134 : : void
135 : 1331 : shlib_list_free(void)
136 : : {
137 : :
138 : 1331 : pkghash_destroy(shlibs);
139 : 1331 : shlibs = NULL;
140 : 1331 : }
141 : :
142 : : void
143 : 280 : rpath_list_free(void)
144 : : {
145 : :
146 : 280 : pkghash_destroy(rpath);
147 : 280 : rpath = NULL;
148 : 280 : }
149 : :
150 : : static void
151 : 19965 : add_dir(const char *hintsfile, const char *name, int trusted)
152 : : {
153 : : struct stat stbuf;
154 : : int i;
155 : :
156 : : /* Do some security checks */
157 [ - + # # ]: 19965 : if (!trusted && !insecure) {
158 [ # # ]: 0 : if (stat(name, &stbuf) == -1) {
159 : 0 : warn("%s", name);
160 : 0 : return;
161 : : }
162 [ # # ]: 0 : if (stbuf.st_uid != 0) {
163 : 0 : warnx("%s: ignoring directory not owned by root", name);
164 : 0 : return;
165 : : }
166 [ # # ]: 0 : if ((stbuf.st_mode & S_IWOTH) != 0) {
167 : 0 : warnx("%s: ignoring world-writable directory", name);
168 : 0 : return;
169 : : }
170 [ # # ]: 0 : if ((stbuf.st_mode & S_IWGRP) != 0) {
171 : 0 : warnx("%s: ignoring group-writable directory", name);
172 : 0 : return;
173 : : }
174 : 0 : }
175 : :
176 [ + + ]: 159720 : for (i = 0; i < ndirs; i++)
177 [ + - ]: 139755 : if (strcmp(dirs[i], name) == 0)
178 : 0 : return;
179 [ - + ]: 19965 : if (ndirs >= MAXDIRS)
180 : 0 : errx(1, "\"%s\": Too many directories in path", hintsfile);
181 : 19965 : dirs[ndirs++] = name;
182 : 19965 : }
183 : :
184 : : static int
185 : 1347 : scan_dirs_for_shlibs(pkghash **shlib_list, int numdirs,
186 : : const char **dirlist, bool strictnames)
187 : : {
188 : : int i;
189 : :
190 : : /* Expect shlibs to follow the name pattern libfoo.so.N if
191 : : strictnames is true -- ie. when searching the default
192 : : library search path.
193 : :
194 : : Otherwise, allow any name ending in .so or .so.N --
195 : : ie. when searching RPATH or RUNPATH and assuming it
196 : : contains private shared libraries which can follow just
197 : : about any naming convention */
198 : :
199 [ + + ]: 21328 : for (i = 0; i < numdirs; i++) {
200 : : DIR *dirp;
201 : : struct dirent *dp;
202 : :
203 [ + - ]: 19981 : if ((dirp = opendir(dirlist[i])) == NULL)
204 : 0 : continue;
205 [ + + ]: 7624228 : while ((dp = readdir(dirp)) != NULL) {
206 : : int len;
207 : : int ret;
208 : : const char *vers;
209 : :
210 : : /* Only regular files and sym-links. On some
211 : : filesystems d_type is not set, on these the d_type
212 : : field will be DT_UNKNOWN. */
213 [ + + + + : 7604247 : if (dp->d_type != DT_REG && dp->d_type != DT_LNK &&
+ - ]
214 : 302185 : dp->d_type != DT_UNKNOWN)
215 : 302185 : continue;
216 : :
217 : 7302062 : len = strlen(dp->d_name);
218 [ + + ]: 7302062 : if (strictnames) {
219 : : /* Name can't be shorter than "libx.so" */
220 [ + + + + ]: 7301866 : if (len < 7 ||
221 : 7268591 : strncmp(dp->d_name, "lib", 3) != 0)
222 : 286165 : continue;
223 : 7015701 : }
224 : :
225 : 7015897 : vers = dp->d_name + len;
226 [ - + + + ]: 27709198 : while (vers > dp->d_name &&
227 [ + + ]: 20693301 : (isdigit(*(vers-1)) || *(vers-1) == '.'))
228 : 13677404 : vers--;
229 [ + + ]: 7015897 : if (vers == dp->d_name + len) {
230 [ + + ]: 3712331 : if (strncmp(vers - 3, ".so", 3) != 0)
231 : 1766361 : continue;
232 [ + - - + ]: 5249536 : } else if (vers < dp->d_name + 3 ||
233 : 3303566 : strncmp(vers - 3, ".so.", 4) != 0)
234 : 0 : continue;
235 : :
236 : : /* We have a valid shared library name. */
237 : 10499072 : ret = shlib_list_add(shlib_list, dirlist[i],
238 : 5249536 : dp->d_name);
239 [ - + ]: 5249536 : if (ret != EPKG_OK) {
240 : 0 : closedir(dirp);
241 : 0 : return ret;
242 : : }
243 : : }
244 : 19981 : closedir(dirp);
245 : 19981 : }
246 : 1347 : return 0;
247 : 1347 : }
248 : :
249 : : #define ORIGIN "$ORIGIN"
250 : :
251 : 16 : int shlib_list_from_rpath(const char *rpath_str, const char *dirpath)
252 : : {
253 : : const char **dirlist;
254 : : char *buf;
255 : : size_t buflen;
256 : : int i, numdirs;
257 : : int ret;
258 : : const char *c, *cstart;
259 : :
260 : : /* The special token $ORIGIN should be replaced by the
261 : : dirpath: adjust buflen calculation to account for this */
262 : :
263 : 16 : numdirs = 1;
264 [ + + ]: 416 : for (c = rpath_str; *c != '\0'; c++)
265 [ + - ]: 400 : if (*c == ':')
266 : 0 : numdirs++;
267 : 16 : buflen = numdirs * sizeof(char *) + strlen(rpath_str) + 1;
268 : 16 : i = strlen(dirpath) - strlen(ORIGIN);
269 [ - + ]: 16 : if (i > 0)
270 : 16 : buflen += i * numdirs;
271 : :
272 : 16 : dirlist = xcalloc(1, buflen);
273 : 16 : buf = (char *)dirlist + numdirs * sizeof(char *);
274 : :
275 : 16 : buf[0] = '\0';
276 : 16 : cstart = rpath_str;
277 [ - + ]: 16 : while ( (c = strstr(cstart, ORIGIN)) != NULL ) {
278 : 0 : strncat(buf, cstart, c - cstart);
279 : 0 : strlcat(buf, dirpath, buflen);
280 : 0 : cstart = c + strlen(ORIGIN);
281 : : }
282 : 16 : strlcat(buf, cstart, buflen);
283 : :
284 : 16 : i = 0;
285 [ + + ]: 32 : while ((c = strsep(&buf, ":")) != NULL) {
286 [ - + ]: 16 : if (strlen(c) > 0)
287 : 16 : dirlist[i++] = c;
288 : : }
289 : :
290 [ + - ]: 16 : assert(i <= numdirs);
291 : :
292 : 16 : ret = scan_dirs_for_shlibs(&rpath, i, dirlist, false);
293 : :
294 : 16 : free(dirlist);
295 : :
296 : 16 : return (ret);
297 : : }
298 : :
299 : : int
300 : 1331 : shlib_list_from_elf_hints(const char *hintsfile)
301 : : {
302 : : #if defined __FreeBSD__ || defined __DragonFly__
303 : 1331 : read_elf_hints(hintsfile, 1);
304 : : #endif
305 : :
306 : 1331 : return (scan_dirs_for_shlibs(&shlibs, ndirs, dirs, true));
307 : : }
308 : :
309 : : static const char *stage_dirs[] = {
310 : : "/lib",
311 : : "/usr/lib",
312 : : };
313 : :
314 : : void
315 : 0 : shlib_list_from_stage(const char *stage)
316 : : {
317 : : int i;
318 : : char *dir;
319 : :
320 [ # # ]: 0 : if (stage == NULL)
321 : 0 : return;
322 : :
323 [ # # ]: 0 : for (i = 0; i < NELEM(stage_dirs); i++) {
324 : 0 : xasprintf(&dir, "%s%s", stage, stage_dirs[i]);
325 : 0 : scan_dirs_for_shlibs(&shlibs, 1, (const char **)&dir, true);
326 : 0 : free(dir);
327 : 0 : }
328 : 0 : }
329 : :
330 : : void
331 : 0 : list_elf_hints(const char *hintsfile)
332 : : {
333 : : int i;
334 : : int nlibs;
335 : :
336 : 0 : read_elf_hints(hintsfile, 1);
337 : 0 : printf("%s:\n", hintsfile);
338 : 0 : printf("\tsearch directories:");
339 [ # # ]: 0 : for (i = 0; i < ndirs; i++)
340 : 0 : printf("%c%s", i == 0 ? ' ' : ':', dirs[i]);
341 : 0 : printf("\n");
342 : :
343 : 0 : nlibs = 0;
344 [ # # ]: 0 : for (i = 0; i < ndirs; i++) {
345 : : DIR *dirp;
346 : : struct dirent *dp;
347 : :
348 [ # # ]: 0 : if ((dirp = opendir(dirs[i])) == NULL)
349 : 0 : continue;
350 [ # # ]: 0 : while ((dp = readdir(dirp)) != NULL) {
351 : : int len;
352 : : int namelen;
353 : : const char *name;
354 : : const char *vers;
355 : :
356 : : /* Name can't be shorter than "libx.so.0" */
357 [ # # # # ]: 0 : if ((len = strlen(dp->d_name)) < 9 ||
358 : 0 : strncmp(dp->d_name, "lib", 3) != 0)
359 : 0 : continue;
360 : 0 : name = dp->d_name + 3;
361 : 0 : vers = dp->d_name + len;
362 [ # # # # ]: 0 : while (vers > dp->d_name && isdigit(*(vers-1)))
363 : 0 : vers--;
364 [ # # ]: 0 : if (vers == dp->d_name + len)
365 : 0 : continue;
366 [ # # # # ]: 0 : if (vers < dp->d_name + 4 ||
367 : 0 : strncmp(vers - 4, ".so.", 4) != 0)
368 : 0 : continue;
369 : :
370 : : /* We have a valid shared library name. */
371 : 0 : namelen = (vers - 4) - name;
372 : 0 : printf("\t%d:-l%.*s.%s => %s/%s\n", nlibs,
373 : 0 : namelen, name, vers, dirs[i], dp->d_name);
374 : 0 : nlibs++;
375 : : }
376 : 0 : closedir(dirp);
377 : 0 : }
378 : 0 : }
379 : :
380 : : static void
381 : 0 : read_dirs_from_file(const char *hintsfile, const char *listfile)
382 : : {
383 : : FILE *fp;
384 : : char buf[MAXPATHLEN];
385 : : int linenum;
386 : :
387 [ # # ]: 0 : if ((fp = fopen(listfile, "re")) == NULL)
388 : 0 : err(1, "%s", listfile);
389 : :
390 : 0 : linenum = 0;
391 [ # # ]: 0 : while (fgets(buf, sizeof buf, fp) != NULL) {
392 : : char *cp, *sp;
393 : :
394 : 0 : linenum++;
395 : 0 : cp = buf;
396 : : /* Skip leading white space. */
397 [ # # ]: 0 : while (isspace(*cp))
398 : 0 : cp++;
399 [ # # # # ]: 0 : if (*cp == '#' || *cp == '\0')
400 : 0 : continue;
401 : 0 : sp = cp;
402 : : /* Advance over the directory name. */
403 [ # # # # ]: 0 : while (!isspace(*cp) && *cp != '\0')
404 : 0 : cp++;
405 : : /* Terminate the string and skip trailing white space. */
406 [ # # ]: 0 : if (*cp != '\0') {
407 : 0 : *cp++ = '\0';
408 [ # # ]: 0 : while (isspace(*cp))
409 : 0 : cp++;
410 : 0 : }
411 : : /* Now we had better be at the end of the line. */
412 [ # # ]: 0 : if (*cp != '\0')
413 : 0 : warnx("%s:%d: trailing characters ignored",
414 : 0 : listfile, linenum);
415 : :
416 : 0 : sp = xstrdup(sp);
417 : 0 : add_dir(hintsfile, sp, 0);
418 : : }
419 : :
420 : 0 : fclose(fp);
421 : 0 : }
422 : :
423 : : static void
424 : 1331 : read_elf_hints(const char *hintsfile, int must_exist)
425 : : {
426 : : int fd;
427 : : struct stat s;
428 : : void *mapbase;
429 : : struct elfhints_hdr *hdr;
430 : : char *strtab;
431 : : char *dirlist;
432 : : char *p;
433 : :
434 [ + - ]: 1331 : if ((fd = open(hintsfile, O_RDONLY)) == -1) {
435 [ # # ]: 0 : if (errno == ENOENT && !must_exist)
436 : 0 : return;
437 : 0 : err(1, "Cannot open \"%s\"", hintsfile);
438 : : }
439 [ + - ]: 1331 : if (fstat(fd, &s) == -1)
440 : 0 : err(1, "Cannot stat \"%s\"", hintsfile);
441 [ + - ]: 1331 : if (s.st_size > MAXFILESIZE)
442 : 0 : errx(1, "\"%s\" is unreasonably large", hintsfile);
443 : : /*
444 : : * We use a read-write, private mapping so that we can null-terminate
445 : : * some strings in it without affecting the underlying file.
446 : : */
447 : 2662 : mapbase = mmap(NULL, s.st_size, PROT_READ|PROT_WRITE,
448 : 1331 : MAP_PRIVATE, fd, 0);
449 [ + - ]: 1331 : if (mapbase == MAP_FAILED)
450 : 0 : err(1, "Cannot mmap \"%s\"", hintsfile);
451 : 1331 : close(fd);
452 : :
453 : 1331 : hdr = (struct elfhints_hdr *)mapbase;
454 [ + - ]: 1331 : if (hdr->magic != ELFHINTS_MAGIC)
455 : 0 : errx(1, "\"%s\": invalid file format", hintsfile);
456 [ - + ]: 1331 : if (hdr->version != 1)
457 : 0 : errx(1, "\"%s\": unrecognized file version (%d)", hintsfile,
458 : 0 : hdr->version);
459 : :
460 : 1331 : strtab = (char *)mapbase + hdr->strtab;
461 : 1331 : dirlist = strtab + hdr->dirlist;
462 : :
463 [ - + ]: 1331 : if (*dirlist != '\0')
464 [ + + ]: 21296 : while ((p = strsep(&dirlist, ":")) != NULL)
465 : 19965 : add_dir(hintsfile, p, 1);
466 : 1331 : }
467 : :
468 : : void
469 : 0 : update_elf_hints(const char *hintsfile, int argc, char **argv, int merge)
470 : : {
471 : : int i;
472 : :
473 [ # # ]: 0 : if (merge)
474 : 0 : read_elf_hints(hintsfile, 0);
475 [ # # ]: 0 : for (i = 0; i < argc; i++) {
476 : : struct stat s;
477 : :
478 [ # # ]: 0 : if (stat(argv[i], &s) == -1)
479 : 0 : warn("warning: %s", argv[i]);
480 [ # # ]: 0 : else if (S_ISREG(s.st_mode))
481 : 0 : read_dirs_from_file(hintsfile, argv[i]);
482 : : else
483 : 0 : add_dir(hintsfile, argv[i], 0);
484 : 0 : }
485 : 0 : write_elf_hints(hintsfile);
486 : 0 : }
487 : :
488 : : static void
489 : 0 : write_elf_hints(const char *hintsfile)
490 : : {
491 : : struct elfhints_hdr hdr;
492 : : char *tempname;
493 : : int fd;
494 : : FILE *fp;
495 : : int i;
496 : :
497 : 0 : xasprintf(&tempname, "%s.XXXXXX", hintsfile);
498 [ # # ]: 0 : if ((fd = mkstemp(tempname)) == -1)
499 : 0 : err(1, "mkstemp(%s)", tempname);
500 [ # # ]: 0 : if (fchmod(fd, 0444) == -1)
501 : 0 : err(1, "fchmod(%s)", tempname);
502 [ # # ]: 0 : if ((fp = fdopen(fd, "wb")) == NULL)
503 : 0 : err(1, "fdopen(%s)", tempname);
504 : :
505 : 0 : hdr.magic = ELFHINTS_MAGIC;
506 : 0 : hdr.version = 1;
507 : 0 : hdr.strtab = sizeof hdr;
508 : 0 : hdr.strsize = 0;
509 : 0 : hdr.dirlist = 0;
510 : 0 : memset(hdr.spare, 0, sizeof hdr.spare);
511 : :
512 : : /* Count up the size of the string table. */
513 [ # # ]: 0 : if (ndirs > 0) {
514 : 0 : hdr.strsize += strlen(dirs[0]);
515 [ # # ]: 0 : for (i = 1; i < ndirs; i++)
516 : 0 : hdr.strsize += 1 + strlen(dirs[i]);
517 : 0 : }
518 : 0 : hdr.dirlistlen = hdr.strsize;
519 : 0 : hdr.strsize++; /* For the null terminator */
520 : :
521 : : /* Write the header. */
522 [ # # ]: 0 : if (fwrite(&hdr, 1, sizeof hdr, fp) != sizeof hdr)
523 : 0 : err(1, "%s: write error", tempname);
524 : : /* Write the strings. */
525 [ # # ]: 0 : if (ndirs > 0) {
526 [ # # ]: 0 : if (fputs(dirs[0], fp) == EOF)
527 : 0 : err(1, "%s: write error", tempname);
528 [ # # ]: 0 : for (i = 1; i < ndirs; i++)
529 [ # # ]: 0 : if (fprintf(fp, ":%s", dirs[i]) < 0)
530 : 0 : err(1, "%s: write error", tempname);
531 : 0 : }
532 [ # # # # ]: 0 : if (putc('\0', fp) == EOF || fclose(fp) == EOF)
533 : 0 : err(1, "%s: write error", tempname);
534 : :
535 [ # # ]: 0 : if (rename(tempname, hintsfile) == -1)
536 : 0 : err(1, "rename %s to %s", tempname, hintsfile);
537 : 0 : free(tempname);
538 : 0 : }
|