/* * Delete the existing runpath from the object. * * entry: * dynsec - Dynamic section * numdyn - # of elements in dynamic array * * exit: * Returns True (1) if the dynamic section was modified, and * False (0) otherwise. * * note: * The way this works is that we look at each item in the * dynamic section and simply remove any DT_RPATH or * DT_RUNPATH entries, copying up anything following in order * to fill the hole. This is all that is needed to completely * remove a runpath from an object. Note that the string is left * in the string table. There is no safe way to remove it, since * we cannot know that it is not used by any other item in the file, * and no way to track or reuse the space if we did remove it. * On the plus side, this means we can always restore the old * runpath without having to add a new string. */ static int remove_runpath(Cache *dynsec, GElf_Word numdyn) { GElf_Dyn dyn; GElf_Word ndx, cpndx; int changed = 0; for (ndx = cpndx = 0; ndx < numdyn; ndx++) { if (gelf_getdyn(dynsec->c_data, ndx, &dyn) == NULL) msg_elf("gelf_getdyn"); /* Skip over any runpath element */ if ((dyn.d_tag == DT_RPATH) || (dyn.d_tag == DT_RUNPATH)) { msg_debug("[%d]%s[%d]: skip runpath\n", dynsec->c_ndx, dynsec->c_name, ndx); continue; } /* * If cpndx and ndx differ, it means that we * have skipped over a runpath in an earlier * iteration. In this case, we need to copy * the item in dynamic[ndx] back to dynamic[cpndx]. */ if (ndx != cpndx) { msg_debug("[%d]%s[%d]: copy from [%d]\n", dynsec->c_ndx, dynsec->c_name, cpndx, ndx); if (gelf_update_dyn(dynsec->c_data, cpndx, &dyn) == 0) msg_elf("gelf_update_dyn"); changed = 1; } /* Advance copy index to receive next item */ cpndx++; } /* * If we removed a runpath element, then we should zero * the slots left at the end. This is not strictly * necessary, since the linker should not look past the * first DT_NULL, but it is good to not leave garbage * lying around. */ bzero(&dyn, sizeof (dyn)); /* Note: DT_NULL is 0 */ for (; cpndx < numdyn; cpndx++) { msg_debug("[%d]%s[%d]: Set to DT_NULL\n", dynsec->c_ndx, dynsec->c_name, cpndx); if (gelf_update_dyn(dynsec->c_data, cpndx, &dyn) == 0) msg_elf("gelf_update_dyn"); changed = 1; } return (changed); }
/* * Returns the offset of the specified string from within * the given string table. * * entry: * dynsec - Dynamic section descriptor * strsec - Descriptor for string table associated with dynamic section * dyn_strpad - DT_SUNW_STRPAD element from dynamic section * str - String we are looking for. * * exit: * On success, the offset of the given string within the string * table is returned. If the string does not exist within the table, * but there is a valid DT_SUNW_STRPAD reserved section, then we * add the string, and update the dynamic section STRPAD element * to reflect the space we use. * * This routine does not return on failure. */ static GElf_Word dynstr_insert(Cache *dynsec, Cache *strsec, dyn_elt_t *dyn_strpad, const char *str) { GElf_Word ins_off; /* Table offset to 1st reserved byte */ char *s; /* ptr to strings within table */ GElf_Word len; /* Length of str inc. NULL byte */ GElf_Word tail_ign; /* # reserved bytes at end of strtab */ tail_ign = dyn_strpad->seen ? dyn_strpad->dyn.d_un.d_val : 0; /* Does the string already exist in the string table? */ if (sec_findstr(strsec, tail_ign, str, &len)) return (len); /* * The desired string does not already exist. Do we have * room to add it? */ len = strlen(str) + 1; if (!dyn_strpad->seen || (len > dyn_strpad->dyn.d_un.d_val)) msg_fatal("[%d]%s: String table does not have room " "to add string\n", strsec->c_shdr.sh_link, strsec->c_name); /* * We will add the string at the first byte of the reserved NULL * area at the end. The DT_SUNW_STRPAD dynamic element gives us * the size of that reserved space. */ ins_off = strsec->c_shdr.sh_size - tail_ign; s = ((char *)strsec->c_data->d_buf) + ins_off; /* Announce the operation */ msg_debug("[%d]%s[%d]: Using %d/%d byes from reserved area to " "add string: %s\n", strsec->c_shdr.sh_link, strsec->c_name, ins_off, len, (int)dyn_strpad->dyn.d_un.d_val, str); /* * Copy the string into the pad area at the end, and * mark the data area as dirty so libelf will flush our * changes to the string data. */ (void) strncpy(s, str, dyn_strpad->dyn.d_un.d_val); (void) elf_flagdata(strsec->c_data, ELF_C_SET, ELF_F_DIRTY); /* Update the DT_STRPAD dynamic entry */ dyn_strpad->dyn.d_un.d_val -= len; if (gelf_update_dyn(dynsec->c_data, dyn_strpad->ndx, &dyn_strpad->dyn) == 0) msg_elf("gelf_update_dyn"); return (ins_off); }
static void set_flag(char *ifile, ulong_t flval) { Elf *elf; Elf_Scn *scn; Elf_Data *data; GElf_Shdr shdr; GElf_Dyn dyn; int fd, secidx, nent, i; (void) elf_version(EV_CURRENT); if ((fd = open(ifile, O_RDWR)) < 0) die("Can't open %s", ifile); if ((elf = elf_begin(fd, ELF_C_RDWR, NULL)) == NULL) elfdie("Can't start ELF for %s", ifile); if ((secidx = findelfsecidx(elf, ".dynamic")) == -1) die("Can't find .dynamic section in %s\n", ifile); if ((scn = elf_getscn(elf, secidx)) == NULL) elfdie("elf_getscn (%d)", secidx); if (gelf_getshdr(scn, &shdr) == NULL) elfdie("gelf_shdr"); if ((data = elf_getdata(scn, NULL)) == NULL) elfdie("elf_getdata"); nent = shdr.sh_size / shdr.sh_entsize; for (i = 0; i < nent; i++) { if (gelf_getdyn(data, i, &dyn) == NULL) elfdie("gelf_getdyn"); if (dyn.d_tag == DT_FLAGS_1) { dyn.d_un.d_val |= (Elf64_Xword)flval; if (gelf_update_dyn(data, i, &dyn) == 0) elfdie("gelf_update_dyn"); break; } } if (i == nent) { die("%s's .dynamic section doesn't have a DT_FLAGS_1 " "field\n", ifile); } if (elf_update(elf, ELF_C_WRITE) == -1) elfdie("Couldn't update %s with changes", ifile); (void) elf_end(elf); (void) close(fd); }
static void rel_dyn_fixup_fn(ElfCreator *ctor, Elf64_Sxword d_tag, Elf_Scn *scn) { GElf_Shdr *shdr, shdr_mem; GElf_Dyn *dyn, dyn_mem; size_t idx; dyn = get_dyn_by_tag(ctor, d_tag, &dyn_mem, &idx); shdr = gelf_getshdr(scn, &shdr_mem); if (shdr) { dyn->d_un.d_ptr = shdr->sh_addr; gelf_update_dyn(ctor->dyndata, idx, dyn); } else { remove_dyn(ctor, idx); dyn = get_dyn_by_tag(ctor, DT_RELSZ, &dyn_mem, &idx); if (dyn) { dyn->d_un.d_val = 0; gelf_update_dyn(ctor->dyndata, idx, dyn); } } }
static void remove_dyn(ElfCreator *ctor, size_t idx) { size_t cnt; for (cnt = idx; cnt < ctor->dynshdr->sh_size/ctor->dynshdr->sh_entsize; cnt++) { GElf_Dyn *dyn, dyn_mem; if (cnt+1 == ctor->dynshdr->sh_size/ctor->dynshdr->sh_entsize) { memset(&dyn_mem, '\0', sizeof(dyn_mem)); gelf_update_dyn(ctor->dyndata, cnt, &dyn_mem); break; } dyn = gelf_getdyn(ctor->dyndata, cnt+1, &dyn_mem); gelf_update_dyn(ctor->dyndata, cnt, dyn); } ctor->dynshdr->sh_size--; gelf_update_shdr(ctor->dynscn, ctor->dynshdr); update_dyn_cache(ctor); }
int main(int argc, char **argv) { int c, ndx; int readonly; int fd; const char *file; const char *runpath; Elf *elf; GElf_Ehdr ehdr; size_t shstrndx, shnum; Elf_Scn *scn; Elf_Data *data; char *shnames = NULL; Cache *cache, *_cache; Cache *dynsec, *strsec; GElf_Word numdyn; dyn_elt_t rpath_elt; dyn_elt_t runpath_elt; dyn_elt_t strpad_elt; dyn_elt_t flags_1_elt; dyn_elt_t null_elt; int changed = 0; opterr = 0; while ((c = getopt(argc, argv, "dr")) != EOF) { switch (c) { case 'd': d_flg = 1; break; case 'r': r_flg = 1; break; case '?': msg_usage(); } } /* * The first plain argument is the file name, and is required. * The second plain argument is the runpath, and is optional. * If no runpath is given, we print the current runpath to stdout * and exit. If it is present, we modify the ELF file to use it. */ argc = argc - optind; argv += optind; if ((argc < 1) || (argc > 2)) msg_usage(); if ((argc == 2) && r_flg) msg_usage(); readonly = (argc == 1) && !r_flg; file = argv[0]; if (!readonly) runpath = argv[1]; if ((fd = open(file, readonly ? O_RDONLY : O_RDWR)) == -1) msg_fatal("unable to open file: %s: %s\n", file, strerror(errno)); (void) elf_version(EV_CURRENT); elf = elf_begin(fd, readonly ? ELF_C_READ : ELF_C_RDWR, NULL); if (elf == NULL) msg_elf("elf_begin"); /* We only handle standalone ELF files */ switch (elf_kind(elf)) { case ELF_K_AR: msg_fatal("unable to edit ELF archive: %s\n", file); break; case ELF_K_ELF: break; default: msg_fatal("unable to edit non-ELF file: %s\n", file); break; } if (gelf_getehdr(elf, &ehdr) == NULL) msg_elf("gelf_getehdr"); if (elf_getshnum(elf, &shnum) == 0) msg_elf("elf_getshnum"); if (elf_getshstrndx(elf, &shstrndx) == 0) msg_elf("elf_getshstrndx"); /* * Obtain the .shstrtab data buffer to provide the required section * name strings. */ if ((scn = elf_getscn(elf, shstrndx)) == NULL) msg_elf("elf_getscn"); if ((data = elf_getdata(scn, NULL)) == NULL) msg_elf("elf_getdata"); shnames = data->d_buf; /* * Allocate a cache to maintain a descriptor for each section. */ if ((cache = malloc(shnum * sizeof (Cache))) == NULL) msg_fatal("unable to allocate section cache: %s\n", strerror(errno)); bzero(cache, sizeof (cache[0])); cache->c_name = ""; _cache = cache + 1; /* * Fill in cache with information for each section, and * locate the dynamic section. */ dynsec = strsec = NULL; for (ndx = 1, scn = NULL; scn = elf_nextscn(elf, scn); ndx++, _cache++) { _cache->c_ndx = ndx; if (gelf_getshdr(scn, &_cache->c_shdr) == NULL) msg_elf("gelf_getshdr"); _cache->c_data = elf_getdata(scn, NULL); _cache->c_name = shnames + _cache->c_shdr.sh_name; if (_cache->c_shdr.sh_type == SHT_DYNAMIC) { dynsec = _cache; numdyn = dynsec->c_shdr.sh_size / dynsec->c_shdr.sh_entsize; msg_debug("[%d]%s: dynamic section\n", ndx, _cache->c_name); } } /* * If we got a dynamic section, locate the string table. * If not, we can't continue. */ if (dynsec == NULL) msg_fatal("file lacks a dynamic section: %s\n", file); strsec = &cache[dynsec->c_shdr.sh_link]; msg_debug("[%d]%s: dynamic string table section\n", strsec->c_ndx, strsec->c_name); /* * History Lesson And Strategy: * * This routine handles both DT_RPATH and DT_RUNPATH entries, altering * either or both if they are present. * * The original SYSV ABI only had DT_RPATH, and the runtime loader used * it to search for things in the following order: * * DT_RPATH, LD_LIBRARY_PATH, defaults * * Solaris did not follow this rule. Environment variables should * supersede everything else, so we have always deviated from the * ABI and and instead search in the order * * LD_LIBRARY_PATH, DT_RPATH, defaults * * Other Unix variants initially followed the ABI, but in recent years * realized that it was a mistake. Hence, DT_RUNPATH was invented, * with the search order: * * LD_LIBRARY_PATH, DT_RUNPATH, defaults * * So for Solaris, DT_RPATH and DT_RUNPATH mean the same thing. If both * are present (which does happen), we set them both to the new * value. If either one is present, we set that one. If neither is * present, and we have a spare DT_NULL slot, we create a DT_RUNPATH. */ /* * Examine the dynamic section to determine the index for * - DT_RPATH * - DT_RUNPATH * - DT_SUNW_STRPAD * - DT_NULL, and whether there are any extra DT_NULL slots */ dyn_elt_init(&rpath_elt); dyn_elt_init(&runpath_elt); dyn_elt_init(&strpad_elt); dyn_elt_init(&flags_1_elt); dyn_elt_init(&null_elt); for (ndx = 0; ndx < numdyn; ndx++) { GElf_Dyn dyn; if (gelf_getdyn(dynsec->c_data, ndx, &dyn) == NULL) msg_elf("gelf_getdyn"); switch (dyn.d_tag) { case DT_NULL: /* * Remember the state of the first DT_NULL. If there * are more than one (i.e. the first one is not * in the final spot), and there is no runpath, then * we will turn the first one into a DT_RUNPATH. */ if (!null_elt.seen) { dyn_elt_save(&null_elt, ndx, &dyn); msg_debug("[%d]%s[%d]: DT_NULL\n", dynsec->c_ndx, dynsec->c_name, ndx); } break; case DT_RPATH: dyn_elt_save(&rpath_elt, ndx, &dyn); msg_debug("[%d]%s[%d]: DT_RPATH: %s\n", dynsec->c_ndx, dynsec->c_name, ndx, DYN_ELT_STRING(&rpath_elt, strsec)); break; case DT_RUNPATH: dyn_elt_save(&runpath_elt, ndx, &dyn); msg_debug("[%d]%s[%d]: DT_RUNPATH: %s\n", dynsec->c_ndx, dynsec->c_name, ndx, DYN_ELT_STRING(&runpath_elt, strsec)); break; case DT_SUNW_STRPAD: dyn_elt_save(&strpad_elt, ndx, &dyn); msg_debug("[%d]%s[%d]: DT_STRPAD: %d\n", dynsec->c_ndx, dynsec->c_name, ndx, (int)strpad_elt.dyn.d_un.d_val); break; case DT_FLAGS_1: dyn_elt_save(&flags_1_elt, ndx, &dyn); break; } } /* * If this is a readonly session, then print the existing * runpath and exit. DT_RPATH and DT_RUNPATH should have * the same value, so we arbitrarily favor DT_RUNPATH. */ if (readonly) { if (runpath_elt.seen) (void) printf("%s\n", DYN_ELT_STRING(&runpath_elt, strsec)); else if (rpath_elt.seen) (void) printf("%s\n", DYN_ELT_STRING(&rpath_elt, strsec)); else msg_debug("ELF file does not have a runpath: %s\n", file); return (0); } /* Edit the file, either to remove the runpath or to add/modify it */ if (r_flg) { if (!(runpath_elt.seen || rpath_elt.seen)) msg_debug("[%d]%s: no runpath found\n", dynsec->c_ndx, dynsec->c_name); else changed = remove_runpath(dynsec, numdyn); } else { changed = new_runpath(runpath, dynsec, numdyn, strsec, &rpath_elt, &runpath_elt, &strpad_elt, &null_elt); } if (changed) { /* * If possible, set the DF_1_EDITED flag, indicating that * this file has been edited after the fact. */ if (flags_1_elt.seen) { flags_1_elt.dyn.d_un.d_val |= DF_1_EDITED; } else if (null_elt.seen && (null_elt.ndx < (numdyn - 1))) { msg_debug("[%d]%s: No existing flags_1 entry to " "modify. Will use extra DT_NULL in slot [%d] \n", dynsec->c_ndx, dynsec->c_name, null_elt.ndx); flags_1_elt.seen = 1; flags_1_elt.ndx = null_elt.ndx; flags_1_elt.dyn.d_tag = DT_FLAGS_1; flags_1_elt.dyn.d_un.d_val = DF_1_EDITED; } if (flags_1_elt.seen) { msg_debug("[%d]%s[%d]: Set DF_1_EDITED flag\n", dynsec->c_ndx, dynsec->c_name, flags_1_elt.ndx); if (gelf_update_dyn(dynsec->c_data, flags_1_elt.ndx, &flags_1_elt.dyn) == 0) msg_elf("gelf_update_dyn"); } /* * Mark the data area as dirty so libelf will flush our * changes to the dynamic section data. */ (void) elf_flagdata(dynsec->c_data, ELF_C_SET, ELF_F_DIRTY); /* Flush the file to disk */ if (elf_update(elf, ELF_C_WRITE) == -1) msg_elf("elf_update"); (void) close(fd); (void) elf_end(elf); } return (0); }
/* * Add/modify the runpath for the object. * * entry: * dynsec - Dynamic section * numdyn - # of elements in dynamic array * * exit: * Returns True (1) if the dynamic section was modified, and * False (0) otherwise. */ static int new_runpath(const char *runpath, Cache *dynsec, GElf_Word numdyn, Cache *strsec, dyn_elt_t *rpath_elt, dyn_elt_t *runpath_elt, dyn_elt_t *strpad_elt, dyn_elt_t *null_elt) { /* Do we have an available dynamic section entry to use? */ if (rpath_elt->seen || runpath_elt->seen) { /* * We have seen a DT_RPATH, or a DT_RUNPATH, or both. * If all of these have the same string as the desired * new value, then we don't need to alter anything and can * simply return. Otherwise, we'll modify them all to have * the new string (below). */ if ((!rpath_elt->seen || (strcmp(DYN_ELT_STRING(rpath_elt, strsec), runpath) == 0)) && (!runpath_elt->seen || (strcmp(DYN_ELT_STRING(runpath_elt, strsec), runpath) == 0))) { if (rpath_elt->seen) msg_debug("[%d]%s[%d]: Existing DT_RPATH " "already has desired value\n", dynsec->c_ndx, dynsec->c_name, rpath_elt->ndx); if (runpath_elt->seen) msg_debug("[%d]%s[%d]: Existing DT_RUNPATH " "already has desired value\n", dynsec->c_ndx, dynsec->c_name, runpath_elt->ndx); return (0); } } else if (!null_elt->seen || (null_elt->ndx == (numdyn - 1))) { /* * There is no DT_RPATH or DT_RUNPATH in the dynamic array, * and there are no extra DT_NULL entries that we can * convert into one. We cannot proceed. */ msg_debug("[%d]%s: Dynamic section does not have room " "to add a runpath\n", dynsec->c_ndx, dynsec->c_name); msg_fatal("unable to add runpath to file: %s\n", file); } /* Does the string exist in the table already, or can we add it? */ rpath_elt->dyn.d_un.d_val = runpath_elt->dyn.d_un.d_val = dynstr_insert(dynsec, strsec, strpad_elt, runpath); /* Update DT_RPATH entry if present */ if (rpath_elt->seen) { msg_debug("[%d]%s[%d]: Reusing existing DT_RPATH entry: %s\n", dynsec->c_ndx, dynsec->c_name, rpath_elt->ndx, DYN_ELT_STRING(rpath_elt, strsec)); if (gelf_update_dyn(dynsec->c_data, rpath_elt->ndx, &rpath_elt->dyn) == 0) msg_elf("gelf_update_dyn"); } /* * Update the DT_RUNPATH entry in the dynamic section, if present. * If one is not present, and there is also no DT_RPATH, then * we use a spare DT_NULL entry to create a new DT_RUNPATH. */ if (runpath_elt->seen || !rpath_elt->seen) { if (runpath_elt->seen) { msg_debug("[%d]%s[%d]: Reusing existing DT_RUNPATH " "entry: %s\n", dynsec->c_ndx, dynsec->c_name, runpath_elt->ndx, DYN_ELT_STRING(runpath_elt, strsec)); } else { /* Using a spare DT_NULL entry */ msg_debug("[%d]%s: No existing runpath to modify. " "Will use extra DT_NULL in slot [%d] \n", dynsec->c_ndx, dynsec->c_name, null_elt->ndx); runpath_elt->seen = 1; runpath_elt->ndx = null_elt->ndx; runpath_elt->dyn.d_tag = DT_RUNPATH; null_elt->ndx++; } if (gelf_update_dyn(dynsec->c_data, runpath_elt->ndx, &runpath_elt->dyn) == 0) msg_elf("gelf_update_dyn"); } return (1); }