From f7d304eeb1f46a15d8fb6564459ed003edbf9bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Fri, 27 Jan 2023 17:15:02 +0100 Subject: [PATCH] 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. --- .gitignore | 1 + patchelf.1 | 9 ++ src/patchelf.cc | 105 +++++++++++++++++- src/patchelf.h | 6 +- tests/Makefile.am | 18 ++- tests/modify-execstack.sh | 225 ++++++++++++++++++++++++++++++++++++++ tests/print-execstack.sh | 23 ++++ 7 files changed, 380 insertions(+), 7 deletions(-) create mode 100755 tests/modify-execstack.sh create mode 100755 tests/print-execstack.sh diff --git a/.gitignore b/.gitignore index c3f60a5..423e8ee 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ Makefile /tests/libbig-dynstr.debug /tests/contiguous-note-sections /tests/simple-pie +/tests/simple-execstack .direnv/ .vscode/ diff --git a/patchelf.1 b/patchelf.1 index 7c8f402..3d46f1f 100644 --- a/patchelf.1 +++ b/patchelf.1 @@ -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. diff --git a/src/patchelf.cc b/src/patchelf.cc index 2bb84eb..e91e2ab 100644 --- a/src/patchelf.cc +++ b/src/patchelf.cc @@ -1010,10 +1010,10 @@ void ElfFile::normalizeNoteSegments() template -void ElfFile::rewriteSections() +void ElfFile::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::clearSymbolVersions(const std::set this->rewriteSections(); } +template +void ElfFile::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 neededLibsToAdd; static std::set symbolsToClearVersion; static bool printNeeded = false; static bool noDefaultLib = false; +static bool printExecstack = false; +static bool clearExecstack = false; +static bool setExecstack = false; template 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]); diff --git a/src/patchelf.h b/src/patchelf.h index f4eec6f..c3096ff 100644 --- a/src/patchelf.h +++ b/src/patchelf.h @@ -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 & syms); + enum class ExecstackMode { print, set, clear }; + + void modifyExecstack(ExecstackMode op); + private: /* Convert an integer in big or little endian representation (as diff --git a/tests/Makefile.am b/tests/Makefile.am index 219f238..9d36645 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -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 diff --git a/tests/modify-execstack.sh b/tests/modify-execstack.sh new file mode 100755 index 0000000..6baae48 --- /dev/null +++ b/tests/modify-execstack.sh @@ -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 diff --git a/tests/print-execstack.sh b/tests/print-execstack.sh new file mode 100755 index 0000000..e34fad8 --- /dev/null +++ b/tests/print-execstack.sh @@ -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