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/libbig-dynstr.debug
/tests/contiguous-note-sections /tests/contiguous-note-sections
/tests/simple-pie /tests/simple-pie
/tests/simple-execstack
.direnv/ .direnv/
.vscode/ .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 can be run as an executable), the debugger does not connect to it correctly and
symbols are not resolved. 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" .IP "--output FILE"
Set the output file name. If not specified, the input will be modified in place. 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> 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) for (auto & i : replacedSections)
debug("replacing section '%s' with size %d\n", debug("replacing section '%s' with size %d\n",
@ -1890,6 +1890,85 @@ void ElfFile<ElfFileParamNames>::clearSymbolVersions(const std::set<std::string>
this->rewriteSections(); 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 printInterpreter = false;
static bool printOsAbi = false; static bool printOsAbi = false;
static bool setOsAbi = false; static bool setOsAbi = false;
@ -1912,6 +1991,9 @@ static std::set<std::string> neededLibsToAdd;
static std::set<std::string> symbolsToClearVersion; static std::set<std::string> symbolsToClearVersion;
static bool printNeeded = false; static bool printNeeded = false;
static bool noDefaultLib = false; static bool noDefaultLib = false;
static bool printExecstack = false;
static bool clearExecstack = false;
static bool setExecstack = false;
template<class ElfFile> template<class ElfFile>
static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, const std::string & fileName) 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) if (printRPath)
elfFile.modifyRPath(elfFile.rpPrint, {}, ""); 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) if (shrinkRPath)
elfFile.modifyRPath(elfFile.rpShrink, allowedRpathPrefixes, ""); elfFile.modifyRPath(elfFile.rpShrink, allowedRpathPrefixes, "");
else if (removeRPath) 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\ [--no-sort]\t\tDo not sort program+section headers; useful for debugging patchelf.\n\
[--clear-symbol-version SYMBOL]\n\ [--clear-symbol-version SYMBOL]\n\
[--add-debug-tag]\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\ [--output FILE]\n\
[--debug]\n\ [--debug]\n\
[--version]\n\ [--version]\n\
@ -2127,6 +2219,15 @@ int mainWrapped(int argc, char * * argv)
if (++i == argc) error("missing argument"); if (++i == argc) error("missing argument");
symbolsToClearVersion.insert(resolveArgument(argv[i])); 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") { else if (arg == "--output") {
if (++i == argc) error("missing argument"); if (++i == argc) error("missing argument");
outputFileName = resolveArgument(argv[i]); outputFileName = resolveArgument(argv[i]);

View File

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

View File

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