Skip to content

Commit 01a4f02

Browse files
committed
feat: add --remove-needed-version switch
Should fix #252, #284, --remove-needed-version works by removing specified version in .gnu.version_r, and resets version in referenced entry in .gnu.version.
1 parent a0f5433 commit 01a4f02

File tree

2 files changed

+135
-0
lines changed

2 files changed

+135
-0
lines changed

src/patchelf.cc

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2215,6 +2215,130 @@ void ElfFile<ElfFileParamNames>::renameDynamicSymbols(const std::unordered_map<s
22152215
this->rewriteSections();
22162216
}
22172217

2218+
template<ElfFileParams>
2219+
void ElfFile<ElfFileParamNames>::removeNeededVersion(const std::map<std::string, std::set<std::string>> & verMap)
2220+
{
2221+
if (verMap.empty()) return;
2222+
2223+
auto idxVersionR = getSectionIndex(".gnu.version_r");
2224+
if (!idxVersionR) {
2225+
error("no .gnu.version_r section found\n");
2226+
return;
2227+
}
2228+
auto & shdrVersionR = shdrs.at(idxVersionR);
2229+
Elf_Shdr & shdrVersionRStrings = shdrs.at(rdi(shdrVersionR.sh_link));
2230+
2231+
auto spanVersyms = tryGetSectionSpan<Elf_Versym>(".gnu.version");
2232+
2233+
size_t verNeedNum = rdi(shdrVersionR.sh_info);
2234+
std::set<size_t> removedVersionIds;
2235+
2236+
// file names and versions here
2237+
char * verStrTab = (char *) fileContents->data() + rdi(shdrVersionRStrings.sh_offset);
2238+
2239+
debug("found .gnu.version_r with %i entries\n", verNeedNum);
2240+
2241+
auto vn = (Elf_Verneed *)(fileContents->data() + rdi(shdrVersionR.sh_offset));
2242+
Elf_Verneed * prevVn = nullptr;
2243+
for (size_t i = 0; i < verNeedNum; ++i) {
2244+
char * file = verStrTab + rdi(vn->vn_file);
2245+
2246+
if (verMap.count(file)) {
2247+
size_t verNauxNum = rdi(vn->vn_cnt);
2248+
size_t newVerNauxNum = 0;
2249+
2250+
auto va = (follow<Elf_Vernaux>(vn, rdi(vn->vn_aux)));
2251+
Elf_Vernaux * prevVa = nullptr;
2252+
2253+
auto & versions = verMap.at(file);
2254+
2255+
for (size_t j = 0; j < verNauxNum; ++j) {
2256+
char * name = verStrTab + rdi(va->vna_name);
2257+
2258+
auto version = rdi(va->vna_other);
2259+
off_t next = rdi(va->vna_next);
2260+
if (versions.count(name)) {
2261+
debug("removing symbol %s with version %d in entry %s\n", name, version, file);
2262+
// 1 for unversioned symbol
2263+
removedVersionIds.insert(version);
2264+
2265+
if (prevVa) {
2266+
wri(prevVa->vna_next, next ? rdi(prevVa->vna_next) + next : 0);
2267+
} else {
2268+
wri(vn->vn_aux, next ? rdi(vn->vn_aux) + next : 0);
2269+
}
2270+
changed = true;
2271+
2272+
auto nextVa = follow<Elf_Vernaux>(va, next);
2273+
// remove the version data
2274+
memset(va, 0, sizeof(Elf_Vernaux));
2275+
va = nextVa;
2276+
} else {
2277+
debug("keeping symbol %s with version %d in entry %s\n", name, version, file);
2278+
++newVerNauxNum;
2279+
prevVa = va;
2280+
va = follow<Elf_Vernaux>(va, next);
2281+
}
2282+
}
2283+
2284+
off_t next = rdi(vn->vn_next);
2285+
// there are versions left
2286+
if (prevVa) {
2287+
wri(vn->vn_cnt, newVerNauxNum);
2288+
prevVn = vn;
2289+
vn = follow<Elf_Verneed>(vn, next);
2290+
} else {
2291+
// remove entire file entry
2292+
if (prevVn) {
2293+
wri(prevVn->vn_next, next ? rdi(prevVn->vn_next) + next : 0);
2294+
} else {
2295+
// there are file entries left
2296+
if (next) {
2297+
wri(shdrVersionR.sh_offset, rdi(shdrVersionR.sh_offset) + next);
2298+
} else {
2299+
// FIXME: remove entire section?
2300+
wri(shdrVersionR.sh_offset, 0);
2301+
wri(shdrVersionR.sh_size, 0);
2302+
}
2303+
}
2304+
wri(shdrVersionR.sh_info, rdi(shdrVersionR.sh_info) - 1);
2305+
2306+
Elf_Verneed * nextVn = follow<Elf_Verneed>(vn, next);
2307+
memset(vn, 0, sizeof(Elf_Verneed));
2308+
vn = nextVn;
2309+
}
2310+
} else {
2311+
off_t next = rdi(vn->vn_next);
2312+
prevVn = vn;
2313+
vn = follow<Elf_Verneed>(vn, next);
2314+
}
2315+
}
2316+
// virtual address and file offset need to be the same for .gnu.version_r
2317+
shdrVersionR.sh_addr = shdrVersionR.sh_offset;
2318+
2319+
if (auto shdrDynamic = tryFindSectionHeader(".dynamic")) {
2320+
auto dyn = (Elf_Dyn *)(fileContents->data() + rdi(shdrDynamic->get().sh_offset));
2321+
2322+
// keep DT_VERNEED and DT_VERNEEDNUM in sync, DT_VERNEEDNUM handled by rewriteHeaders
2323+
for ( ; rdi(dyn->d_tag) != DT_NULL; dyn++) {
2324+
if (rdi(dyn->d_tag) == DT_VERNEEDNUM) {
2325+
dyn->d_un.d_val = shdrVersionR.sh_info;
2326+
}
2327+
}
2328+
}
2329+
2330+
if (spanVersyms) {
2331+
for (auto & versym : spanVersyms) {
2332+
if (removedVersionIds.count(versym)) {
2333+
wri(versym, 1);
2334+
}
2335+
}
2336+
}
2337+
2338+
debug("remaining entries in .gnu.version_r: %i\n", rdi(shdrVersionR.sh_info));
2339+
this->rewriteSections(true);
2340+
}
2341+
22182342
template<ElfFileParams>
22192343
void ElfFile<ElfFileParamNames>::clearSymbolVersions(const std::set<std::string> & syms)
22202344
{
@@ -2376,6 +2500,7 @@ static bool addDebugTag = false;
23762500
static bool renameDynamicSymbols = false;
23772501
static bool printRPath = false;
23782502
static std::string newRPath;
2503+
static std::map<std::string, std::set<std::string>> neededVersionsToRemove;
23792504
static std::set<std::string> neededLibsToRemove;
23802505
static std::map<std::string, std::string> neededLibsToReplace;
23812506
static std::set<std::string> neededLibsToAdd;
@@ -2430,6 +2555,7 @@ static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, con
24302555

24312556
if (printNeeded) elfFile.printNeededLibs();
24322557

2558+
elfFile.removeNeededVersion(neededVersionsToRemove);
24332559
elfFile.removeNeeded(neededLibsToRemove);
24342560
elfFile.replaceNeeded(neededLibsToReplace);
24352561
elfFile.addNeeded(neededLibsToAdd);
@@ -2505,6 +2631,7 @@ static void showHelp(const std::string & progName)
25052631
[--clear-symbol-version SYMBOL]\n\
25062632
[--add-debug-tag]\n\
25072633
[--print-execstack]\t\tPrints whether the object requests an executable stack\n\
2634+
[--remove-needed-version LIBRARY VERSION_SYMBOL]\n\
25082635
[--clear-execstack]\n\
25092636
[--set-execstack]\n\
25102637
[--rename-dynamic-symbols NAME_MAP_FILE]\tRenames dynamic symbols. The map file should contain two symbols (old_name new_name) per line\n\
@@ -2613,6 +2740,11 @@ static int mainWrapped(int argc, char * * argv)
26132740
neededLibsToReplace[ argv[i+1] ] = argv[i+2];
26142741
i += 2;
26152742
}
2743+
else if (arg == "--remove-needed-version") {
2744+
if (i+2 >= argc) error("missing argument(s)");
2745+
neededVersionsToRemove[ argv[i+1] ].insert(resolveArgument(argv[i+2]));
2746+
i += 2;
2747+
}
26162748
else if (arg == "--clear-symbol-version") {
26172749
if (++i == argc) error("missing argument");
26182750
symbolsToClearVersion.insert(resolveArgument(argv[i]));

src/patchelf.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <stdexcept>
66
#include <string>
77
#include <vector>
8+
#include <unordered_map>
89

910
#include "elf.h"
1011

@@ -169,6 +170,8 @@ class ElfFile
169170

170171
void renameDynamicSymbols(const std::unordered_map<std::string_view, std::string>&);
171172

173+
void removeNeededVersion(const std::map<std::string, std::set<std::string>> & verMap);
174+
172175
void clearSymbolVersions(const std::set<std::string> & syms);
173176

174177
enum class ExecstackMode { print, set, clear };

0 commit comments

Comments
 (0)