/ src / checkrt.c
checkrt.c
  1  /* Copyright (c) 2022 <djcj@gmx.de>
  2   *
  3   * Permission is hereby granted, free of charge, to any person obtaining a copy
  4   * of this software and associated documentation files (the "Software"), to deal
  5   * in the Software without restriction, including without limitation the rights
  6   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7   * copies of the Software, and to permit persons to whom the Software is
  8   * furnished to do so, subject to the following conditions:
  9   *
 10   * The above copyright notice and this permission notice shall be included in all
 11   * copies or substantial portions of the Software.
 12   *
 13   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 14   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 15   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 16   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 17   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 18   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 19   * SOFTWARE.
 20   */
 21  
 22  #ifndef _GNU_SOURCE
 23  #define _GNU_SOURCE
 24  #endif
 25  #include <link.h>
 26  #include <dlfcn.h>
 27  #include <elf.h>
 28  #include <fcntl.h>
 29  #include <libgen.h>
 30  #include <stdio.h>
 31  #include <stdlib.h>
 32  #include <string.h>
 33  #include <sys/stat.h>
 34  #include <sys/types.h>
 35  #include <sys/mman.h>
 36  #include <sys/wait.h>
 37  #include <unistd.h>
 38  
 39  #include "res.h"
 40  
 41  #ifdef __x86_64__
 42    typedef Elf64_Ehdr  Elf_Ehdr;
 43    typedef Elf64_Shdr  Elf_Shdr;
 44    typedef Elf64_Sym   Elf_Sym;
 45  #else
 46    typedef Elf32_Ehdr  Elf_Ehdr;
 47    typedef Elf32_Shdr  Elf_Shdr;
 48    typedef Elf32_Sym   Elf_Sym;
 49  #endif
 50  
 51  #define LIBGCC_S_SO  "libgcc_s.so.1"
 52  #define LIBSTDCXX_SO "libstdc++.so.6"
 53  #define LIBGCC_DIR   "gcc"
 54  #define STDCXX_DIR   "cxx"
 55  
 56  #define MAX(X,Y)  (((X) > (Y)) ? (X) : (Y))
 57  #define PRINT_VERBOSE(FMT, ...)  if (verbose) { fprintf(stderr, FMT, __VA_ARGS__); }
 58  
 59  
 60  static char *get_libpath(const char *lib, char verbose)
 61  {
 62    struct link_map *map = NULL;
 63    char *path;
 64  
 65    /* It's very important to use dlmopen() together with the argument LM_ID_NEWLM,
 66     * otherwise the bundled library and the system library will use the same
 67     * namespace and the path of the bundled library might be returned when you
 68     * expected the system one.
 69     */
 70    void *handle = dlmopen(LM_ID_NEWLM, lib, RTLD_LAZY);
 71    if (!handle) {
 72      PRINT_VERBOSE("error: failed to dlmopen() file: %s\n", lib);
 73      return NULL;
 74    }
 75  
 76    if (dlinfo(handle, RTLD_DI_LINKMAP, &map) == -1) {
 77      PRINT_VERBOSE("error: could not retrieve information: %s\n", lib);
 78      dlclose(handle);
 79      return NULL;
 80    }
 81  
 82    path = strdup(map->l_name);
 83    dlclose(handle);
 84  
 85    return path;
 86  }
 87  
 88  static int symbol_version(const char *lib, const char *sym_prefix, char verbose)
 89  {
 90    int fd = -1;
 91    void *addr = NULL;
 92    const char *error = "";
 93  
 94    /* let dlopen() do all the compatibility checks */
 95    char *orig = get_libpath(lib, verbose);
 96    if (!orig) return -1;
 97  
 98    fd = open(orig, O_RDONLY);
 99    if (fd < 0) {
100      PRINT_VERBOSE("error: failed to open() file: %s\n", orig);
101      free(orig);
102      return -1;
103    }
104  
105    if (verbose) {
106      if (strcmp(lib, orig) == 0) {
107        fprintf(stderr, "%s\n", orig);
108      } else {
109        fprintf(stderr, "%s -> %s\n", lib, orig);
110      }
111    }
112  
113    free(orig);
114  
115    /* make sure file size is larger than the required ELF header size */
116    struct stat st;
117    if (fstat(fd, &st) < 0 || st.st_size < sizeof(Elf_Ehdr)) {
118      error = "fstat() failed or returned a too low file size";
119      goto symbol_version_error;
120    }
121  
122    /* mmap() library */
123    addr = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
124    if (addr == MAP_FAILED) {
125      error = "mmap() failed";
126      goto symbol_version_error;
127    }
128  
129    /* ELF header */
130    Elf_Ehdr *ehdr = addr;
131    error = "offset exceeding filesize";
132    if (ehdr->e_shoff > st.st_size) {
133      goto symbol_version_error;
134    }
135  
136    Elf_Shdr *shdr = addr + ehdr->e_shoff;
137    int shnum = ehdr->e_shnum;
138    Elf_Shdr *sh_strtab = &shdr[ehdr->e_shstrndx];
139  
140    if (sh_strtab->sh_offset > st.st_size) {
141      goto symbol_version_error;
142    }
143    const char *sh_strtab_p = addr + sh_strtab->sh_offset;
144    const char *sh_dynstr_p = sh_strtab_p;
145    const char * const sh_strtab_p_const = sh_strtab_p;
146  
147    /* get strtab/dynstr */
148    for (int i = 0; i < shnum; ++i) {
149      if (shdr[i].sh_type != SHT_STRTAB) continue;
150      const char *name = sh_strtab_p_const + shdr[i].sh_name;
151  
152      if (strcmp(name, ".strtab") == 0) {
153        if (shdr[i].sh_offset > st.st_size) {
154          goto symbol_version_error;
155        }
156        sh_strtab_p = addr + shdr[i].sh_offset;
157        break;
158      } else if (strcmp(name, ".dynstr") == 0) {
159        if (shdr[i].sh_offset > st.st_size) {
160          goto symbol_version_error;
161        }
162        sh_dynstr_p = addr + shdr[i].sh_offset;
163        break;
164      }
165    }
166  
167    const char *symbol = NULL;
168    size_t len = strlen(sym_prefix);
169  
170    /* iterate through sections */
171    for (int i = 0; i < shnum; ++i) {
172      if (shdr[i].sh_type != SHT_SYMTAB && shdr[i].sh_type != SHT_DYNSYM) {
173        continue;
174      }
175  
176      if (shdr[i].sh_offset > st.st_size) {
177        goto symbol_version_error;
178      }
179      Elf_Sym *syms_data = addr + shdr[i].sh_offset;
180  
181      /* iterate through symbols */
182      for (size_t j = 0; j < shdr[i].sh_size / sizeof(Elf_Sym); ++j) {
183        if (syms_data[j].st_shndx != SHN_ABS) {
184          continue;
185        }
186  
187        const char *name;
188        if (shdr[i].sh_type == SHT_DYNSYM) {
189          name = sh_dynstr_p + syms_data[j].st_name;
190        } else {
191          name = sh_strtab_p + syms_data[j].st_name;
192        }
193  
194        if (strncmp(name, sym_prefix, len) != 0) {
195          continue;
196        }
197  
198        if (!symbol) {
199          symbol = name;
200          continue;
201        }
202  
203        if (strverscmp(name, symbol) > 0) {
204          symbol = name;
205        }
206      }
207    }
208  
209    int maj = 0, min = 0, pat = 0;
210    if (sscanf(symbol + len, "%d.%d.%d", &maj, &min, &pat) < 1) {
211      goto symbol_version_error;
212    }
213  
214    PRINT_VERBOSE("%s%d.%d.%d\n", sym_prefix, maj, min, pat);
215    munmap(addr, st.st_size);
216    close(fd);
217    return (pat + min*1000 + maj*1000000);
218  
219  symbol_version_error:
220    PRINT_VERBOSE("error: %s: %s\n", error, orig);
221    munmap(addr, st.st_size);
222    close(fd);
223    return -1;
224  }
225  
226  static void dump_file(const char *dest, unsigned char *data, unsigned int len)
227  {
228    int fd = creat(dest, DEFFILEMODE);
229    if (fd == -1) return;
230    if (write(fd, data, len) != len) unlink(dest);
231    close(fd);
232  }
233  
234  static int copy_lib(const char *lib, const char *destDir, char verbose)
235  {
236    char *destFull = NULL;
237    int fdIn = -1, fdOut = -1;
238  
239    /* get full source and target paths */
240    char *srcFull = get_libpath(lib, verbose);
241    if (!srcFull) goto copy_lib_error;
242  
243    char *base = basename(srcFull);
244    if (!base) goto copy_lib_error;
245  
246    size_t len = strlen(destDir);
247    size_t len2 = strlen(base);
248    destFull = malloc(len + MAX(len2,32) + 2);
249    sprintf(destFull, "%s/%s", destDir, base);
250  
251    /* open source for reading */
252    fdIn = open(srcFull, O_RDONLY|O_CLOEXEC);
253    if (fdIn == -1) goto copy_lib_error;
254  
255    /* open target for writing */
256    mkdir(destDir, ACCESSPERMS);
257    fdOut = creat(destFull, DEFFILEMODE);
258    if (fdOut == -1) goto copy_lib_error;
259  
260    /* copy data into target */
261    ssize_t n;
262    unsigned char buf[512*1024];
263    while ((n = read(fdIn, &buf, sizeof(buf))) > 0) {
264      if (write(fdOut, &buf, n) != n) {
265        goto copy_lib_error;
266      }
267    }
268  
269    int rv = 0;
270    fprintf(stderr, ">> %s\ncopied to -> %s\n", srcFull, destFull);
271  
272    /* dump COPYING files */
273    char *p = destFull + len + 1;
274    strcpy(p, "COPYING.RUNTIME.gz");
275    dump_file(destFull, COPYING_RUNTIME_gz, COPYING_RUNTIME_gz_len);
276    p += 7;
277    strcpy(p, "3.gz");
278    dump_file(destFull, COPYING3_gz, COPYING3_gz_len);
279    strcpy(p, ".libgcc");
280    dump_file(destFull, COPYING_libgcc, COPYING_libgcc_len);
281    strcpy(p, ".libstdc++");
282    dump_file(destFull, COPYING_libstdc__, COPYING_libstdc___len);
283    fprintf(stderr, "(COPYING files were added too)\n");
284  
285    goto copy_lib_end;
286  
287  copy_lib_error:
288    rv = -1;
289  
290  copy_lib_end:
291    if (fdOut != -1) close(fdOut);
292    if (fdIn != -1) close(fdIn);
293    if (srcFull) free(srcFull);
294    if (destFull) free(destFull);
295  
296    return rv;
297  }
298  
299  
300  int main(int argc, char **argv)
301  {
302  #if CHECKRT_TEST == 1
303  
304    printf("Test:\n\n");
305    copy_lib(LIBGCC_S_SO, "./" LIBGCC_DIR, 1);
306    copy_lib(LIBSTDCXX_SO, "./" STDCXX_DIR, 1);
307    putchar('\n');
308    symbol_version("./" LIBGCC_DIR "/" LIBGCC_S_SO, "GCC_", 1);
309    putchar('\n');
310    symbol_version("./" STDCXX_DIR "/" LIBSTDCXX_SO, "GLIBCXX_", 1);
311    putchar('\n');
312    symbol_version(LIBGCC_S_SO, "GCC_", 1);
313    putchar('\n');
314    symbol_version(LIBSTDCXX_SO, "GLIBCXX_", 1);
315  
316  #else
317  
318    char v = 0, copy = 0;
319  
320    for (int i=1; i < argc; i++) {
321      if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
322        fprintf(stderr,
323          "usage:\n"
324          "  %s -c|--copy-libraries\n"
325          "  %s -h|--help\n"
326          "  %s -v|--verbose\n"
327          "\n"
328          "This program will look for the following libraries relative to its\n"
329          "location, check if they are usable and add them to LD_LIBRARY_PATH\n"
330          "if they are newer than the system's equivalent:\n"
331          "\n"
332          "  " LIBGCC_DIR "/" LIBGCC_S_SO "\n"
333          "  " STDCXX_DIR "/" LIBSTDCXX_SO "\n", argv[0], argv[0], argv[0]);
334        return 0;
335      } else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
336        v = 1;
337      } else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--copy-libraries") == 0) {
338        copy = 1;
339      } else {
340        fprintf(stderr, "error: invalid argument: %s\nTry `%s --help' for more information.\n", argv[i], argv[0]);
341        return 1;
342      }
343    }
344  
345    char *currdir = realpath("/proc/self/exe", NULL);
346    if (!currdir) {
347      fprintf(stderr, "error: realpath() failed to resolve /proc/self/exe\n");
348      return 1;
349    }
350  
351    char *p = strrchr(currdir, '/');
352    if (!p) {
353      perror("strrchr()");
354      free(currdir);
355      return 1;
356    }
357    *(p+1) = 0;
358  
359    size_t len = strlen(currdir);
360    char *libpath = malloc(len + MAX(sizeof(STDCXX_DIR), sizeof(LIBGCC_DIR)) +
361      MAX(sizeof(LIBSTDCXX_SO), sizeof(LIBGCC_S_SO)) + 2);
362    strcpy(libpath, currdir);
363    p = libpath + len;
364  
365    if (copy) {
366      /* copy system libraries */
367      strcpy(p, LIBGCC_DIR);
368      copy_lib(LIBGCC_S_SO, libpath, v);
369      strcpy(p, STDCXX_DIR);
370      copy_lib(LIBSTDCXX_SO, libpath, v);
371    } else {
372      /* get symbol versions */
373  
374      strcpy(p, LIBGCC_DIR "/" LIBGCC_S_SO);
375      int ver = symbol_version(libpath, "GCC_", v);
376      if (ver != -1 && ver > symbol_version(LIBGCC_S_SO, "GCC_", v)) {
377        printf("%s" LIBGCC_DIR ":", currdir);
378      }
379  
380      strcpy(p, STDCXX_DIR "/" LIBSTDCXX_SO);
381      ver = symbol_version(libpath, "GLIBCXX_", v);
382      if (ver != -1 && ver > symbol_version(LIBSTDCXX_SO, "GLIBCXX_", v)) {
383        printf("%s" STDCXX_DIR ":", currdir);
384      }
385  
386      putchar('\n');
387    }
388  
389    free(libpath);
390    free(currdir);
391  #endif
392  
393    return 0;
394  }
395  
396  
397