Skip to content

Add --remove-needed-version #564

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Makefile
/tests/contiguous-note-sections
/tests/simple-pie
/tests/simple-execstack
/tests/symver

.direnv/
.vscode/
Expand Down
5 changes: 5 additions & 0 deletions patchelf.1
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ This means that when a shared library has an entry point (so that it
can be run as an executable), the debugger does not connect to it correctly and
symbols are not resolved.

.IP "--remove-needed-version LIBRARY VERSION_SYMBOL"
Removes VERSION_SYMBOL from LIBRARY in .gnu.version_r and resets entries referenced
the version in .gnu.version, could be used to remove symbol versioning. LIBRARY and
VERSION_SYMBOL can be retrieved from the output of "readelf -V".

.IP "--print-execstack"
Prints the state of the executable flag of the GNU_STACK program header, if present.

Expand Down
176 changes: 176 additions & 0 deletions src/patchelf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2269,6 +2269,174 @@ void ElfFile<ElfFileParamNames>::renameDynamicSymbols(const std::unordered_map<s
this->rewriteSections();
}

template<ElfFileParams>
void ElfFile<ElfFileParamNames>::removeNeededVersion(const std::map<std::string, std::set<std::string>> & verMap)
{
if (verMap.empty()) return;

auto idxVersionR = getSectionIndex(".gnu.version_r");
if (!idxVersionR) {
error("no .gnu.version_r section found\n");
return;
}
auto & shdrVersionR = shdrs.at(idxVersionR);
Elf_Shdr & shdrVersionRStrings = shdrs.at(rdi(shdrVersionR.sh_link));

auto mapOffset = rdi(shdrVersionR.sh_addr) - rdi(shdrVersionR.sh_offset);

size_t verNeedNum = rdi(shdrVersionR.sh_info);
std::set<size_t> removedVersionIds;

// file names and versions here
char * verStrTab = (char *) fileContents->data() + rdi(shdrVersionRStrings.sh_offset);

debug("found .gnu.version_r with %i entries\n", verNeedNum);

bool removeEntireSection = false;

auto vn = (Elf_Verneed *)(fileContents->data() + rdi(shdrVersionR.sh_offset));
Elf_Verneed * prevVn = nullptr;
for (size_t i = 0; i < verNeedNum; ++i) {
char * file = verStrTab + rdi(vn->vn_file);

if (verMap.count(file)) {
size_t verNauxNum = rdi(vn->vn_cnt);
size_t newVerNauxNum = 0;

auto va = (follow<Elf_Vernaux>(vn, rdi(vn->vn_aux)));
Elf_Vernaux * prevVa = nullptr;

auto & versions = verMap.at(file);

for (size_t j = 0; j < verNauxNum; ++j) {
char * name = verStrTab + rdi(va->vna_name);

auto version = rdi(va->vna_other);
off_t next = rdi(va->vna_next);
if (versions.count(name)) {
debug("removing symbol %s with version %d in entry %s\n", name, version, file);
// 1 for unversioned symbol
removedVersionIds.insert(version);

if (prevVa) {
wri(prevVa->vna_next, next ? rdi(prevVa->vna_next) + next : 0);
} else {
wri(vn->vn_aux, next ? rdi(vn->vn_aux) + next : 0);
}
changed = true;

auto nextVa = follow<Elf_Vernaux>(va, next);
// remove the version data
memset(va, 0, sizeof(Elf_Vernaux));
va = nextVa;
} else {
debug("keeping symbol %s with version %d in entry %s\n", name, version, file);
++newVerNauxNum;
prevVa = va;
va = follow<Elf_Vernaux>(va, next);
}
}

off_t next = rdi(vn->vn_next);
// there are versions left
if (prevVa) {
wri(vn->vn_cnt, newVerNauxNum);
prevVn = vn;
vn = follow<Elf_Verneed>(vn, next);
} else {
// remove entire file entry
if (prevVn) {
wri(prevVn->vn_next, next ? rdi(prevVn->vn_next) + next : 0);
} else {
// there are file entries left
if (next) {
wri(shdrVersionR.sh_offset, rdi(shdrVersionR.sh_offset) + next);
} else {
wri(shdrVersionR.sh_size, 0);

removeEntireSection = true;
}
}
wri(shdrVersionR.sh_info, rdi(shdrVersionR.sh_info) - 1);

Elf_Verneed * nextVn = follow<Elf_Verneed>(vn, next);
memset(vn, 0, sizeof(Elf_Verneed));
vn = nextVn;
}
} else {
off_t next = rdi(vn->vn_next);
prevVn = vn;
vn = follow<Elf_Verneed>(vn, next);
}
}

// virtual address and file offset need to be the same
if (!removeEntireSection) {
wri(shdrVersionR.sh_addr, mapOffset + rdi(shdrVersionR.sh_offset));
}

if (auto shdrDynamic = tryFindSectionHeader(".dynamic")) {
auto dyn = (Elf_Dyn *)(fileContents->data() + rdi(shdrDynamic->get().sh_offset));

// keep DT_VERNEED and DT_VERNEEDNUM in sync, DT_VERNEED handled by rewriteHeaders
Elf_Dyn * last = dyn;
for ( ; rdi(dyn->d_tag) != DT_NULL; dyn++) {
if (rdi(dyn->d_tag) == DT_VERNEEDNUM) {
if (!removeEntireSection) {
dyn->d_un.d_val = shdrVersionR.sh_info;
*last++ = *dyn;
}
} else if (rdi(dyn->d_tag) == DT_VERNEED) {
if (!removeEntireSection) {
*last++ = *dyn;
}
} else if (rdi(dyn->d_tag) == DT_VERSYM) {
if (!removeEntireSection) {
*last++ = *dyn;
}
} else {
*last++ = *dyn;
}
}
memset(last, 0, sizeof(Elf_Dyn) * (dyn - last));
}

auto spanVersyms = tryGetSectionSpan<Elf_Versym>(".gnu.version");

if (spanVersyms) {
for (auto & versym : spanVersyms) {
if (removedVersionIds.count(versym)) {
wri(versym, 1);
}
}
}

if (removeEntireSection) {
debug("removing .gnu.version_r and .gnu.version section\n");
wri(shdrVersionR.sh_type, SHT_NULL);
auto idxVersion = getSectionIndex(".gnu.version");
if (idxVersion) {
auto & shdrVersion = shdrs.at(idxVersion);
wri(shdrVersion.sh_type, SHT_NULL);
memset(fileContents->data() + rdi(shdrVersion.sh_offset), 0, rdi(shdrVersion.sh_size));
wri(shdrVersion.sh_size, 0);
}
} else {
debug("remaining entries in .gnu.version_r: %i\n", rdi(shdrVersionR.sh_info));
}

// we did not change phdrAddress, rewrite it in place
Elf_Addr phdrAddress;
for (auto & phdr : phdrs) {
if (rdi(phdr.p_type) == PT_PHDR) {
phdrAddress = rdi(phdr.p_vaddr);
break;
}
}

rewriteHeaders(phdrAddress);
}

template<ElfFileParams>
void ElfFile<ElfFileParamNames>::clearSymbolVersions(const std::set<std::string> & syms)
{
Expand Down Expand Up @@ -2430,6 +2598,7 @@ static bool addDebugTag = false;
static bool renameDynamicSymbols = false;
static bool printRPath = false;
static std::string newRPath;
static std::map<std::string, std::set<std::string>> neededVersionsToRemove;
static std::set<std::string> neededLibsToRemove;
static std::map<std::string, std::string> neededLibsToReplace;
static std::set<std::string> neededLibsToAdd;
Expand Down Expand Up @@ -2484,6 +2653,7 @@ static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, con

if (printNeeded) elfFile.printNeededLibs();

elfFile.removeNeededVersion(neededVersionsToRemove);
elfFile.removeNeeded(neededLibsToRemove);
elfFile.replaceNeeded(neededLibsToReplace);
elfFile.addNeeded(neededLibsToAdd);
Expand Down Expand Up @@ -2559,6 +2729,7 @@ static void showHelp(const std::string & progName)
[--clear-symbol-version SYMBOL]\n\
[--add-debug-tag]\n\
[--print-execstack]\t\tPrints whether the object requests an executable stack\n\
[--remove-needed-version LIBRARY VERSION_SYMBOL]\n\
[--clear-execstack]\n\
[--set-execstack]\n\
[--rename-dynamic-symbols NAME_MAP_FILE]\tRenames dynamic symbols. The map file should contain two symbols (old_name new_name) per line\n\
Expand Down Expand Up @@ -2667,6 +2838,11 @@ static int mainWrapped(int argc, char * * argv)
neededLibsToReplace[ argv[i+1] ] = argv[i+2];
i += 2;
}
else if (arg == "--remove-needed-version") {
if (i+2 >= argc) error("missing argument(s)");
neededVersionsToRemove[ argv[i+1] ].insert(resolveArgument(argv[i+2]));
i += 2;
}
else if (arg == "--clear-symbol-version") {
if (++i == argc) error("missing argument");
symbolsToClearVersion.insert(resolveArgument(argv[i]));
Expand Down
3 changes: 3 additions & 0 deletions src/patchelf.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <stdexcept>
#include <string>
#include <vector>
#include <unordered_map>

#include "elf.h"

Expand Down Expand Up @@ -170,6 +171,8 @@ class ElfFile

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

void removeNeededVersion(const std::map<std::string, std::set<std::string>> & verMap);

void clearSymbolVersions(const std::set<std::string> & syms);

enum class ExecstackMode { print, set, clear };
Expand Down
19 changes: 17 additions & 2 deletions tests/Makefile.am
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
LIBS =

check_PROGRAMS = simple-pie simple simple-execstack main too-many-strtab main-scoped big-dynstr no-rpath contiguous-note-sections
check_PROGRAMS = simple-pie simple simple-execstack main too-many-strtab main-scoped big-dynstr no-rpath contiguous-note-sections \
symver

no_rpath_arch_TESTS = \
no-rpath-alpha.sh \
Expand Down Expand Up @@ -53,6 +54,7 @@ src_TESTS = \
shared-rpath.sh \
short-first-segment.sh \
empty-note.sh \
remove-needed-version.sh \
set-interpreter-same.sh

build_TESTS = \
Expand Down Expand Up @@ -125,7 +127,8 @@ check_DATA = libbig-dynstr.debug
# - with libtool, it is difficult to control options
# - with libtool, it is not possible to compile convenience *dynamic* libraries :-(
check_PROGRAMS += libfoo.so libfoo-scoped.so libbar.so libbar-scoped.so libsimple.so libsimple-execstack.so libbuildid.so libtoomanystrtab.so \
phdr-corruption.so many-syms-main libmany-syms.so liboveralign.so libshared-rpath.so
phdr-corruption.so many-syms-main libmany-syms.so liboveralign.so libshared-rpath.so \
libsymver.so libsymver-old.so

libbuildid_so_SOURCES = simple.c
libbuildid_so_LDFLAGS = $(LDFLAGS_sharedlib) -Wl,--build-id
Expand Down Expand Up @@ -183,6 +186,18 @@ phdr_corruption_so_SOURCES = void.c phdr-corruption.ld
phdr_corruption_so_LDFLAGS = -nostdlib -shared -Wl,-T$(srcdir)/phdr-corruption.ld
phdr_corruption_so_CFLAGS =

libsymver_so_SOURCES = libsymver.c
libsymver_so_LDFLAGS = $(LDFLAGS_sharedlib) -Wl,--version-script=libsymver.map
libsymver_so_CFLAGS = $(AM_CFLAGS)
libsymver_old_so_SOURCES = libsymver-old.c
libsymver_old_so_LDFLAGS = $(LDFLAGS_sharedlib) -Wl,--version-script=libsymver-old.map
libsymver_old_so_CFLAGS = $(AM_CFLAGS)
symver_SOURCES = symver.c
symver_LDFLAGS = $(LDFLAGS_local)
symver_LDADD = -lsymver $(AM_LDADD)
symver_DEPENDENCIES = libsymver.so libsymver-old.so
symver_CFLAGS = $(AM_CFLAGS)

many-syms.c:
i=1; while [ $$i -le 2000 ]; do echo "void f$$i() {};"; i=$$(($$i + 1)); done > $@

Expand Down
11 changes: 11 additions & 0 deletions tests/libsymver-old.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include <stdio.h>

__asm__(".symver foo_v1, foo@@V1");

void foo_v1() {
printf("foo_v1\n");
}

void bar() {
printf("bar_unver\n");
}
4 changes: 4 additions & 0 deletions tests/libsymver-old.map
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
V1 {
global: foo;
local: foo_v1;
};
18 changes: 18 additions & 0 deletions tests/libsymver.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <stdio.h>

__asm__(".symver foo_v1, foo@V1");
__asm__(".symver foo_v2, foo@@V2");

void foo_v1() {
printf("foo_v1\n");
}

void foo_v2() {
printf("foo_v2\n");
}


// version defined in version script
void bar() {
printf("bar_ver\n");
}
11 changes: 11 additions & 0 deletions tests/libsymver.map
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
V1 {
global: foo;
local: foo_v1;
};
V2 {
global: foo;
local: foo_v2;
};
VER {
global: bar;
};
50 changes: 50 additions & 0 deletions tests/remove-needed-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#! /bin/sh -e
SCRATCH="scratch/$(basename "$0" .sh)"
PATCHELF="$(readlink -f "../src/patchelf")"
READELF="${READELF:-readelf}"
MAIN=symver
LIBNEW=libsymver.so
LIBOLD=libsymver-old.so


rm -rf "$SCRATCH"
mkdir -p "$SCRATCH"

cp $MAIN $LIBNEW $LIBOLD "${SCRATCH}/"

cd "$SCRATCH"

fail() {
echo $1
"$READELF" -a -W $MAIN
"$READELF" -a -W $LIBNEW
"$READELF" -a -W $LIBOLD
exit $2
}

# sanity check
exit_code=0
LD_LIBRARY_PATH="$PWD" ./${MAIN} || exit_code=$?
if [ $exit_code -ne 0 ]; then
fail "basic check" $exit_code
fi

# replace with old version
mv $LIBOLD $LIBNEW

# should NOT run before patch
exit_code=0
LD_LIBRARY_PATH="$PWD" ./${MAIN} || exit_code=$?
if [ $exit_code -eq 0 ]; then
fail "patch check" 1
fi

${PATCHELF} --remove-needed-version $LIBNEW V2 \
--remove-needed-version $LIBNEW VER $MAIN || fail "patchelf" -1

# should run after removing version
exit_code=0
LD_LIBRARY_PATH="$PWD" ./${MAIN} || exit_code=$?
if [ $exit_code -ne 0 ]; then
fail "patch check" $exit_code
fi
Loading