Add options to print, clear and set executable stack state

Add options the modify the state of the executable flag of the GNU_STACK
program header. That header indicates whether the object is requiring an
executable stack.
This commit is contained in:
Christian Göttsche 2023-01-27 17:15:02 +01:00
parent 5908e16cd5
commit f7d304eeb1
7 changed files with 380 additions and 7 deletions

1
.gitignore vendored
View File

@ -32,6 +32,7 @@ Makefile
/tests/libbig-dynstr.debug
/tests/contiguous-note-sections
/tests/simple-pie
/tests/simple-execstack
.direnv/
.vscode/

View File

@ -114,6 +114,15 @@ 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 "--print-execstack"
Prints the state of the executable flag of the GNU_STACK program header, if present.
.IP "--clear-execstack"
Clears the executable flag of the GNU_STACK program header, or adds a new header.
.IP "--set-execstack"
Sets the executable flag of the GNU_STACK program header, or adds a new header.
.IP "--output FILE"
Set the output file name. If not specified, the input will be modified in place.

View File

@ -1010,10 +1010,10 @@ void ElfFile<ElfFileParamNames>::normalizeNoteSegments()
template<ElfFileParams>
void ElfFile<ElfFileParamNames>::rewriteSections()
void ElfFile<ElfFileParamNames>::rewriteSections(bool force)
{
if (replacedSections.empty()) return;
if (!force && replacedSections.empty()) return;
for (auto & i : replacedSections)
debug("replacing section '%s' with size %d\n",
@ -1890,6 +1890,85 @@ void ElfFile<ElfFileParamNames>::clearSymbolVersions(const std::set<std::string>
this->rewriteSections();
}
template<ElfFileParams>
void ElfFile<ElfFileParamNames>::modifyExecstack(ExecstackMode op)
{
if (op == ExecstackMode::clear || op == ExecstackMode::set) {
size_t nullhdr = (size_t)-1;
for (size_t i = 0; i < phdrs.size(); i++) {
auto & header = phdrs[i];
const auto type = rdi(header.p_type);
if (type != PT_GNU_STACK) {
if (!nullhdr && type == PT_NULL)
nullhdr = i;
continue;
}
if (op == ExecstackMode::clear && (rdi(header.p_flags) & PF_X) == PF_X) {
debug("simple execstack clear of header %zu\n", i);
wri(header.p_flags, rdi(header.p_flags) & ~PF_X);
* ((Elf_Phdr *) (fileContents->data() + rdi(hdr()->e_phoff)) + i) = header;
changed = true;
} else if (op == ExecstackMode::set && (rdi(header.p_flags) & PF_X) != PF_X) {
debug("simple execstack set of header %zu\n", i);
wri(header.p_flags, rdi(header.p_flags) | PF_X);
* ((Elf_Phdr *) (fileContents->data() + rdi(hdr()->e_phoff)) + i) = header;
changed = true;
} else {
debug("execstack already in requested state\n");
}
return;
}
if (nullhdr != (size_t)-1) {
debug("replacement execstack of header %zu\n", nullhdr);
auto & header = phdrs[nullhdr];
header = {};
wri(header.p_type, PT_GNU_STACK);
wri(header.p_flags, PF_R | PF_W | (op == ExecstackMode::set ? PF_X : 0));
wri(header.p_align, 0x1);
* ((Elf_Phdr *) (fileContents->data() + rdi(hdr()->e_phoff)) + nullhdr) = header;
changed = true;
return;
}
debug("header addition for execstack\n");
Elf_Phdr new_phdr = {};
wri(new_phdr.p_type, PT_GNU_STACK);
wri(new_phdr.p_flags, PF_R | PF_W | (op == ExecstackMode::set ? PF_X : 0));
wri(new_phdr.p_align, 0x1);
phdrs.push_back(new_phdr);
wri(hdr()->e_phnum, rdi(hdr()->e_phnum) + 1);
changed = true;
rewriteSections(true);
return;
}
char result = '?';
for (const auto & header : phdrs) {
if (rdi(header.p_type) != PT_GNU_STACK)
continue;
if ((rdi(header.p_flags) & PF_X) == PF_X)
result = 'X';
else
result = '-';
break;
}
printf("execstack: %c\n", result);
}
static bool printInterpreter = false;
static bool printOsAbi = false;
static bool setOsAbi = false;
@ -1912,6 +1991,9 @@ static std::set<std::string> neededLibsToAdd;
static std::set<std::string> symbolsToClearVersion;
static bool printNeeded = false;
static bool noDefaultLib = false;
static bool printExecstack = false;
static bool clearExecstack = false;
static bool setExecstack = false;
template<class ElfFile>
static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, const std::string & fileName)
@ -1937,6 +2019,13 @@ static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, con
if (printRPath)
elfFile.modifyRPath(elfFile.rpPrint, {}, "");
if (printExecstack)
elfFile.modifyExecstack(ElfFile::ExecstackMode::print);
else if (clearExecstack)
elfFile.modifyExecstack(ElfFile::ExecstackMode::clear);
else if (setExecstack)
elfFile.modifyExecstack(ElfFile::ExecstackMode::set);
if (shrinkRPath)
elfFile.modifyRPath(elfFile.rpShrink, allowedRpathPrefixes, "");
else if (removeRPath)
@ -2019,6 +2108,9 @@ void showHelp(const std::string & progName)
[--no-sort]\t\tDo not sort program+section headers; useful for debugging patchelf.\n\
[--clear-symbol-version SYMBOL]\n\
[--add-debug-tag]\n\
[--print-execstack]\t\tPrints whether the object requests an executable stack\n\
[--clear-execstack]\n\
[--set-execstack]\n\
[--output FILE]\n\
[--debug]\n\
[--version]\n\
@ -2127,6 +2219,15 @@ int mainWrapped(int argc, char * * argv)
if (++i == argc) error("missing argument");
symbolsToClearVersion.insert(resolveArgument(argv[i]));
}
else if (arg == "--print-execstack") {
printExecstack = true;
}
else if (arg == "--clear-execstack") {
clearExecstack = true;
}
else if (arg == "--set-execstack") {
setExecstack = true;
}
else if (arg == "--output") {
if (++i == argc) error("missing argument");
outputFileName = resolveArgument(argv[i]);

View File

@ -105,7 +105,7 @@ private:
public:
void rewriteSections();
void rewriteSections(bool force = false);
std::string getInterpreter();
@ -139,6 +139,10 @@ public:
void clearSymbolVersions(const std::set<std::string> & syms);
enum class ExecstackMode { print, set, clear };
void modifyExecstack(ExecstackMode op);
private:
/* Convert an integer in big or little endian representation (as

View File

@ -1,6 +1,6 @@
LIBS =
check_PROGRAMS = simple-pie simple 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
no_rpath_arch_TESTS = \
no-rpath-amd64.sh \
@ -43,7 +43,9 @@ src_TESTS = \
replace-needed.sh \
replace-add-needed.sh \
add-debug-tag.sh \
empty-note.sh
empty-note.sh \
print-execstack.sh \
modify-execstack.sh
build_TESTS = \
$(no_rpath_arch_TESTS)
@ -71,10 +73,15 @@ export NIX_LDFLAGS=
simple_SOURCES = simple.c
# no -fpic for simple.o
simple_CFLAGS =
simple_LDFLAGS = -Wl,-z,noexecstack
simple_pie_SOURCES = simple.c
simple_pie_CFLAGS = -fPIC -pie
simple_execstack_SOURCES = simple.c
simple_execstack_CFLAGS =
simple_execstack_LDFLAGS = -Wl,-z,execstack
main_SOURCES = main.c
main_LDADD = -lfoo $(AM_LDADD)
main_DEPENDENCIES = libfoo.so
@ -108,7 +115,7 @@ check_DATA = libbig-dynstr.debug
# - without libtool, only archives (static libraries) can be built by automake
# - 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 libbuildid.so libtoomanystrtab.so \
check_PROGRAMS += libfoo.so libfoo-scoped.so libbar.so libbar-scoped.so libsimple.so libsimple-execstack.so libbuildid.so libtoomanystrtab.so \
phdr-corruption.so
libbuildid_so_SOURCES = simple.c
@ -131,7 +138,10 @@ libbar_scoped_so_SOURCES = bar.c
libbar_scoped_so_LDFLAGS = $(LDFLAGS_sharedlib)
libsimple_so_SOURCES = simple.c
libsimple_so_LDFLAGS = $(LDFLAGS_sharedlib)
libsimple_so_LDFLAGS = $(LDFLAGS_sharedlib) -Wl,-z,noexecstack
libsimple_execstack_so_SOURCES = simple.c
libsimple_execstack_so_LDFLAGS = $(LDFLAGS_sharedlib) -Wl,-z,execstack
too_many_strtab_SOURCES = too-many-strtab.c too-many-strtab2.s
libtoomanystrtab_so_SOURCES = too-many-strtab.c too-many-strtab2.s

225
tests/modify-execstack.sh Executable file
View File

@ -0,0 +1,225 @@
#! /bin/sh -e
SCRATCH=scratch/$(basename $0 .sh)
PATCHELF=$(readlink -f "../src/patchelf")
rm -rf ${SCRATCH}
mkdir -p ${SCRATCH}
cp simple ${SCRATCH}/
cp simple-execstack ${SCRATCH}/
cp libsimple.so ${SCRATCH}/
cp libsimple-execstack.so ${SCRATCH}/
cd ${SCRATCH}
## simple
cp simple backup
if ! ${PATCHELF} --print-execstack simple | grep -q 'execstack: -'; then
echo "[simple] wrong initial execstack detection"
${PATCHELF} --print-execstack simple
exit 1
fi
if ! ${PATCHELF} --clear-execstack simple; then
echo "[simple] failed noop initial clear"
exit 1
fi
if ! ${PATCHELF} --set-execstack simple; then
echo "[simple] failed set"
exit 1
fi
if ! ${PATCHELF} --print-execstack simple | grep -q 'execstack: X'; then
echo "[simple] wrong execstack detection after set"
${PATCHELF} --print-execstack simple
exit 1
fi
if diff simple backup; then
echo "[simple] no change after set"
exit 1
fi
if ! ${PATCHELF} --set-execstack simple; then
echo "[simple] failed noop set"
exit 1
fi
if ! ${PATCHELF} --clear-execstack simple; then
echo "[simple] failed clear after set"
exit 1
fi
if ! ${PATCHELF} --print-execstack simple | grep -q 'execstack: -'; then
echo "[simple] wrong execstack detection after clear after set"
${PATCHELF} --print-execstack simple
exit 1
fi
if ! diff simple backup; then
echo "[simple] change against backup after clear after set"
exit 1
fi
## simple-execstack
cp simple-execstack backup
if ! ${PATCHELF} --print-execstack simple-execstack | grep -q 'execstack: X'; then
echo "[simple-execstack] wrong initial execstack detection"
${PATCHELF} --print-execstack simple-execstack
exit 1
fi
if ! ${PATCHELF} --set-execstack simple-execstack; then
echo "[simple-execstack] failed noop initial set"
exit 1
fi
if ! ${PATCHELF} --clear-execstack simple-execstack; then
echo "[simple-execstack] failed clear"
exit 1
fi
if ! ${PATCHELF} --print-execstack simple-execstack | grep -q 'execstack: -'; then
echo "[simple-execstack] wrong execstack detection after clear"
${PATCHELF} --print-execstack simple-execstack
exit 1
fi
if diff simple-execstack backup; then
echo "[simple-execstack] no change after set"
exit 1
fi
if ! ${PATCHELF} --clear-execstack simple-execstack; then
echo "[simple-execstack] failed noop clear"
exit 1
fi
if ! ${PATCHELF} --set-execstack simple-execstack; then
echo "[simple-execstack] failed set after clear"
exit 1
fi
if ! ${PATCHELF} --print-execstack simple-execstack | grep -q 'execstack: X'; then
echo "[simple-execstack] wrong execstack detection after set after clear"
${PATCHELF} --print-execstack simple-execstack
exit 1
fi
if ! diff simple-execstack backup; then
echo "[simple-execstack] change against backup after set after clear"
exit 1
fi
## libsimple.so
cp libsimple.so backup
if ! ${PATCHELF} --print-execstack libsimple.so | grep -q 'execstack: -'; then
echo "[libsimple.so] wrong initial execstack detection"
${PATCHELF} --print-execstack libsimple.so
exit 1
fi
if ! ${PATCHELF} --clear-execstack libsimple.so; then
echo "[libsimple.so] failed noop initial clear"
exit 1
fi
if ! ${PATCHELF} --set-execstack libsimple.so; then
echo "[libsimple.so] failed set"
exit 1
fi
if ! ${PATCHELF} --print-execstack libsimple.so | grep -q 'execstack: X'; then
echo "[libsimple.so] wrong execstack detection after set"
${PATCHELF} --print-execstack libsimple.so
exit 1
fi
if diff libsimple.so backup; then
echo "[libsimple.so] no change after set"
exit 1
fi
if ! ${PATCHELF} --set-execstack libsimple.so; then
echo "[libsimple.so] failed noop set"
exit 1
fi
if ! ${PATCHELF} --clear-execstack libsimple.so; then
echo "[libsimple.so] failed clear after set"
exit 1
fi
if ! ${PATCHELF} --print-execstack libsimple.so | grep -q 'execstack: -'; then
echo "[libsimple.so] wrong execstack detection after clear after set"
${PATCHELF} --print-execstack libsimple.so
exit 1
fi
if ! diff libsimple.so backup; then
echo "[libsimple.so] change against backup after clear after set"
exit 1
fi
## libsimple-execstack.so
cp libsimple-execstack.so backup
if ! ${PATCHELF} --print-execstack libsimple-execstack.so | grep -q 'execstack: X'; then
echo "[libsimple-execstack.so] wrong initial execstack detection"
${PATCHELF} --print-execstack libsimple-execstack.so
exit 1
fi
if ! ${PATCHELF} --set-execstack libsimple-execstack.so; then
echo "[libsimple-execstack.so] failed noop initial set"
exit 1
fi
if ! ${PATCHELF} --clear-execstack libsimple-execstack.so; then
echo "[libsimple-execstack.so] failed clear"
exit 1
fi
if ! ${PATCHELF} --print-execstack libsimple-execstack.so | grep -q 'execstack: -'; then
echo "[libsimple-execstack.so] wrong execstack detection after clear"
${PATCHELF} --print-execstack libsimple-execstack.so
exit 1
fi
if diff libsimple-execstack.so backup; then
echo "[libsimple-execstack.so] no change after set"
exit 1
fi
if ! ${PATCHELF} --clear-execstack libsimple-execstack.so; then
echo "[libsimple-execstack.so] failed noop clear"
exit 1
fi
if ! ${PATCHELF} --set-execstack libsimple-execstack.so; then
echo "[libsimple-execstack.so] failed set after clear"
exit 1
fi
if ! ${PATCHELF} --print-execstack libsimple-execstack.so | grep -q 'execstack: X'; then
echo "[libsimple-execstack.so] wrong execstack detection after set after clear"
${PATCHELF} --print-execstack libsimple-execstack.so
exit 1
fi
if ! diff libsimple-execstack.so backup; then
echo "[libsimple-execstack.so] change against backup after set after clear"
exit 1
fi

23
tests/print-execstack.sh Executable file
View File

@ -0,0 +1,23 @@
#! /bin/sh -e
SCRATCH=scratch/$(basename $0 .sh)
PATCHELF=$(readlink -f "../src/patchelf")
rm -rf ${SCRATCH}
mkdir -p ${SCRATCH}
cp simple ${SCRATCH}/
cp simple-execstack ${SCRATCH}/
cd ${SCRATCH}
if ! ${PATCHELF} --print-execstack simple | grep -q 'execstack: -'; then
echo "wrong execstack detection"
${PATCHELF} --print-execstack simple
exit 1
fi
if ! ${PATCHELF} --print-execstack simple-execstack | grep -q 'execstack: X'; then
echo "wrong execstack detection"
${PATCHELF} --print-execstack simple-execstack
exit 1
fi