Skip to content

Commit f7d304e

Browse files
committed
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.
1 parent 5908e16 commit f7d304e

7 files changed

+380
-7
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Makefile
3232
/tests/libbig-dynstr.debug
3333
/tests/contiguous-note-sections
3434
/tests/simple-pie
35+
/tests/simple-execstack
3536

3637
.direnv/
3738
.vscode/

patchelf.1

+9
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,15 @@ This means that when a shared library has an entry point (so that it
114114
can be run as an executable), the debugger does not connect to it correctly and
115115
symbols are not resolved.
116116

117+
.IP "--print-execstack"
118+
Prints the state of the executable flag of the GNU_STACK program header, if present.
119+
120+
.IP "--clear-execstack"
121+
Clears the executable flag of the GNU_STACK program header, or adds a new header.
122+
123+
.IP "--set-execstack"
124+
Sets the executable flag of the GNU_STACK program header, or adds a new header.
125+
117126
.IP "--output FILE"
118127
Set the output file name. If not specified, the input will be modified in place.
119128

src/patchelf.cc

+103-2
Original file line numberDiff line numberDiff line change
@@ -1010,10 +1010,10 @@ void ElfFile<ElfFileParamNames>::normalizeNoteSegments()
10101010

10111011

10121012
template<ElfFileParams>
1013-
void ElfFile<ElfFileParamNames>::rewriteSections()
1013+
void ElfFile<ElfFileParamNames>::rewriteSections(bool force)
10141014
{
10151015

1016-
if (replacedSections.empty()) return;
1016+
if (!force && replacedSections.empty()) return;
10171017

10181018
for (auto & i : replacedSections)
10191019
debug("replacing section '%s' with size %d\n",
@@ -1890,6 +1890,85 @@ void ElfFile<ElfFileParamNames>::clearSymbolVersions(const std::set<std::string>
18901890
this->rewriteSections();
18911891
}
18921892

1893+
template<ElfFileParams>
1894+
void ElfFile<ElfFileParamNames>::modifyExecstack(ExecstackMode op)
1895+
{
1896+
if (op == ExecstackMode::clear || op == ExecstackMode::set) {
1897+
size_t nullhdr = (size_t)-1;
1898+
1899+
for (size_t i = 0; i < phdrs.size(); i++) {
1900+
auto & header = phdrs[i];
1901+
const auto type = rdi(header.p_type);
1902+
if (type != PT_GNU_STACK) {
1903+
if (!nullhdr && type == PT_NULL)
1904+
nullhdr = i;
1905+
continue;
1906+
}
1907+
1908+
if (op == ExecstackMode::clear && (rdi(header.p_flags) & PF_X) == PF_X) {
1909+
debug("simple execstack clear of header %zu\n", i);
1910+
1911+
wri(header.p_flags, rdi(header.p_flags) & ~PF_X);
1912+
* ((Elf_Phdr *) (fileContents->data() + rdi(hdr()->e_phoff)) + i) = header;
1913+
changed = true;
1914+
} else if (op == ExecstackMode::set && (rdi(header.p_flags) & PF_X) != PF_X) {
1915+
debug("simple execstack set of header %zu\n", i);
1916+
1917+
wri(header.p_flags, rdi(header.p_flags) | PF_X);
1918+
* ((Elf_Phdr *) (fileContents->data() + rdi(hdr()->e_phoff)) + i) = header;
1919+
changed = true;
1920+
} else {
1921+
debug("execstack already in requested state\n");
1922+
}
1923+
1924+
return;
1925+
}
1926+
1927+
if (nullhdr != (size_t)-1) {
1928+
debug("replacement execstack of header %zu\n", nullhdr);
1929+
1930+
auto & header = phdrs[nullhdr];
1931+
header = {};
1932+
wri(header.p_type, PT_GNU_STACK);
1933+
wri(header.p_flags, PF_R | PF_W | (op == ExecstackMode::set ? PF_X : 0));
1934+
wri(header.p_align, 0x1);
1935+
1936+
* ((Elf_Phdr *) (fileContents->data() + rdi(hdr()->e_phoff)) + nullhdr) = header;
1937+
changed = true;
1938+
return;
1939+
}
1940+
1941+
debug("header addition for execstack\n");
1942+
1943+
Elf_Phdr new_phdr = {};
1944+
wri(new_phdr.p_type, PT_GNU_STACK);
1945+
wri(new_phdr.p_flags, PF_R | PF_W | (op == ExecstackMode::set ? PF_X : 0));
1946+
wri(new_phdr.p_align, 0x1);
1947+
phdrs.push_back(new_phdr);
1948+
1949+
wri(hdr()->e_phnum, rdi(hdr()->e_phnum) + 1);
1950+
1951+
changed = true;
1952+
rewriteSections(true);
1953+
return;
1954+
}
1955+
1956+
char result = '?';
1957+
1958+
for (const auto & header : phdrs) {
1959+
if (rdi(header.p_type) != PT_GNU_STACK)
1960+
continue;
1961+
1962+
if ((rdi(header.p_flags) & PF_X) == PF_X)
1963+
result = 'X';
1964+
else
1965+
result = '-';
1966+
break;
1967+
}
1968+
1969+
printf("execstack: %c\n", result);
1970+
}
1971+
18931972
static bool printInterpreter = false;
18941973
static bool printOsAbi = false;
18951974
static bool setOsAbi = false;
@@ -1912,6 +1991,9 @@ static std::set<std::string> neededLibsToAdd;
19121991
static std::set<std::string> symbolsToClearVersion;
19131992
static bool printNeeded = false;
19141993
static bool noDefaultLib = false;
1994+
static bool printExecstack = false;
1995+
static bool clearExecstack = false;
1996+
static bool setExecstack = false;
19151997

19161998
template<class ElfFile>
19171999
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
19372019
if (printRPath)
19382020
elfFile.modifyRPath(elfFile.rpPrint, {}, "");
19392021

2022+
if (printExecstack)
2023+
elfFile.modifyExecstack(ElfFile::ExecstackMode::print);
2024+
else if (clearExecstack)
2025+
elfFile.modifyExecstack(ElfFile::ExecstackMode::clear);
2026+
else if (setExecstack)
2027+
elfFile.modifyExecstack(ElfFile::ExecstackMode::set);
2028+
19402029
if (shrinkRPath)
19412030
elfFile.modifyRPath(elfFile.rpShrink, allowedRpathPrefixes, "");
19422031
else if (removeRPath)
@@ -2019,6 +2108,9 @@ void showHelp(const std::string & progName)
20192108
[--no-sort]\t\tDo not sort program+section headers; useful for debugging patchelf.\n\
20202109
[--clear-symbol-version SYMBOL]\n\
20212110
[--add-debug-tag]\n\
2111+
[--print-execstack]\t\tPrints whether the object requests an executable stack\n\
2112+
[--clear-execstack]\n\
2113+
[--set-execstack]\n\
20222114
[--output FILE]\n\
20232115
[--debug]\n\
20242116
[--version]\n\
@@ -2127,6 +2219,15 @@ int mainWrapped(int argc, char * * argv)
21272219
if (++i == argc) error("missing argument");
21282220
symbolsToClearVersion.insert(resolveArgument(argv[i]));
21292221
}
2222+
else if (arg == "--print-execstack") {
2223+
printExecstack = true;
2224+
}
2225+
else if (arg == "--clear-execstack") {
2226+
clearExecstack = true;
2227+
}
2228+
else if (arg == "--set-execstack") {
2229+
setExecstack = true;
2230+
}
21302231
else if (arg == "--output") {
21312232
if (++i == argc) error("missing argument");
21322233
outputFileName = resolveArgument(argv[i]);

src/patchelf.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class ElfFile
105105

106106
public:
107107

108-
void rewriteSections();
108+
void rewriteSections(bool force = false);
109109

110110
std::string getInterpreter();
111111

@@ -139,6 +139,10 @@ class ElfFile
139139

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

142+
enum class ExecstackMode { print, set, clear };
143+
144+
void modifyExecstack(ExecstackMode op);
145+
142146
private:
143147

144148
/* Convert an integer in big or little endian representation (as

tests/Makefile.am

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
LIBS =
22

3-
check_PROGRAMS = simple-pie simple main too-many-strtab main-scoped big-dynstr no-rpath contiguous-note-sections
3+
check_PROGRAMS = simple-pie simple simple-execstack main too-many-strtab main-scoped big-dynstr no-rpath contiguous-note-sections
44

55
no_rpath_arch_TESTS = \
66
no-rpath-amd64.sh \
@@ -43,7 +43,9 @@ src_TESTS = \
4343
replace-needed.sh \
4444
replace-add-needed.sh \
4545
add-debug-tag.sh \
46-
empty-note.sh
46+
empty-note.sh \
47+
print-execstack.sh \
48+
modify-execstack.sh
4749

4850
build_TESTS = \
4951
$(no_rpath_arch_TESTS)
@@ -71,10 +73,15 @@ export NIX_LDFLAGS=
7173
simple_SOURCES = simple.c
7274
# no -fpic for simple.o
7375
simple_CFLAGS =
76+
simple_LDFLAGS = -Wl,-z,noexecstack
7477

7578
simple_pie_SOURCES = simple.c
7679
simple_pie_CFLAGS = -fPIC -pie
7780

81+
simple_execstack_SOURCES = simple.c
82+
simple_execstack_CFLAGS =
83+
simple_execstack_LDFLAGS = -Wl,-z,execstack
84+
7885
main_SOURCES = main.c
7986
main_LDADD = -lfoo $(AM_LDADD)
8087
main_DEPENDENCIES = libfoo.so
@@ -108,7 +115,7 @@ check_DATA = libbig-dynstr.debug
108115
# - without libtool, only archives (static libraries) can be built by automake
109116
# - with libtool, it is difficult to control options
110117
# - with libtool, it is not possible to compile convenience *dynamic* libraries :-(
111-
check_PROGRAMS += libfoo.so libfoo-scoped.so libbar.so libbar-scoped.so libsimple.so libbuildid.so libtoomanystrtab.so \
118+
check_PROGRAMS += libfoo.so libfoo-scoped.so libbar.so libbar-scoped.so libsimple.so libsimple-execstack.so libbuildid.so libtoomanystrtab.so \
112119
phdr-corruption.so
113120

114121
libbuildid_so_SOURCES = simple.c
@@ -131,7 +138,10 @@ libbar_scoped_so_SOURCES = bar.c
131138
libbar_scoped_so_LDFLAGS = $(LDFLAGS_sharedlib)
132139

133140
libsimple_so_SOURCES = simple.c
134-
libsimple_so_LDFLAGS = $(LDFLAGS_sharedlib)
141+
libsimple_so_LDFLAGS = $(LDFLAGS_sharedlib) -Wl,-z,noexecstack
142+
143+
libsimple_execstack_so_SOURCES = simple.c
144+
libsimple_execstack_so_LDFLAGS = $(LDFLAGS_sharedlib) -Wl,-z,execstack
135145

136146
too_many_strtab_SOURCES = too-many-strtab.c too-many-strtab2.s
137147
libtoomanystrtab_so_SOURCES = too-many-strtab.c too-many-strtab2.s

0 commit comments

Comments
 (0)