Skip to content

Commit 5a58e98

Browse files
authored
[ELF] Align the end of PT_GNU_RELRO associated PT_LOAD to a common-page-size boundary (llvm#66042)
Close llvm#57618: currently we align the end of PT_GNU_RELRO to a common-page-size boundary, but do not align the end of the associated PT_LOAD. This is benign when runtime_page_size >= common-page-size. However, when runtime_page_size < common-page-size, it is possible that `alignUp(end(PT_LOAD), page_size) < alignDown(end(PT_GNU_RELRO), page_size)`. In this case, rtld's mprotect call for PT_GNU_RELRO will apply to unmapped regions and lead to an error, e.g. ``` error while loading shared libraries: cannot apply additional memory protection after relocation: Cannot allocate memory ``` To fix the issue, add a padding section .relro_padding like mold, which is contained in the PT_GNU_RELRO segment and the associated PT_LOAD segment. The section also prevents strip from corrupting PT_LOAD program headers. .relro_padding has the largest `sortRank` among RELRO sections. Therefore, it is naturally placed at the end of `PT_GNU_RELRO` segment in the absence of `PHDRS`/`SECTIONS` commands. In the presence of `SECTIONS` commands, we place .relro_padding immediately before a symbol assignment using DATA_SEGMENT_RELRO_END (see also https://reviews.llvm.org/D124656), if present. DATA_SEGMENT_RELRO_END is changed to align to max-page-size instead of common-page-size. Some edge cases worth mentioning: * ppc64-toc-addis-nop.s: when PHDRS is present, do not append .relro_padding * avoid-empty-program-headers.s: when the only RELRO section is .tbss, it is not part of PT_LOAD segment, therefore we do not append .relro_padding. --- Close llvm#65002: GNU ld from 2.39 onwards aligns the end of PT_GNU_RELRO to a max-page-size boundary (https://sourceware.org/PR28824) so that the last page is protected even if runtime_page_size > common-page-size. In my opinion, losing protection for the last page when the runtime page size is larger than common-page-size is not really an issue. Double mapping a page of up to max-common-page for the protection could cause undesired VM waste. Internally we had users complaining about 2MiB max-page-size applying to shared objects. Therefore, the end of .relro_padding is padded to a common-page-size boundary. Users who are really anxious can set common-page-size to match their runtime page size. --- 17 tests need updating as there are lots of change detectors.
1 parent 21ab252 commit 5a58e98

30 files changed

+241
-60
lines changed

lld/ELF/Driver.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1586,8 +1586,8 @@ static void readConfigs(opt::InputArgList &args) {
15861586

15871587
// Page alignment can be disabled by the -n (--nmagic) and -N (--omagic).
15881588
// As PT_GNU_RELRO relies on Paging, do not create it when we have disabled
1589-
// it.
1590-
if (config->nmagic || config->omagic)
1589+
// it. Also disable RELRO for -r.
1590+
if (config->nmagic || config->omagic || config->relocatable)
15911591
config->zRelro = false;
15921592

15931593
std::tie(config->buildId, config->buildIdVector) = getBuildId(args);

lld/ELF/LinkerScript.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,10 @@ void LinkerScript::diagnoseOrphanHandling() const {
887887
if (config->orphanHandling == OrphanHandlingPolicy::Place)
888888
return;
889889
for (const InputSectionBase *sec : orphanSections) {
890+
// .relro_padding is inserted before DATA_SEGMENT_RELRO_END, if present,
891+
// automatically. The section is not supposed to be specified by scripts.
892+
if (sec == in.relroPadding.get())
893+
continue;
890894
// Input SHT_REL[A] retained by --emit-relocs are ignored by
891895
// computeInputSections(). Don't warn/error.
892896
if (isa<InputSection>(sec) &&
@@ -1079,6 +1083,11 @@ void LinkerScript::assignOffsets(OutputSection *sec) {
10791083
}
10801084
}
10811085

1086+
// If .relro_padding is present, round up the end to a common-page-size
1087+
// boundary to protect the last page.
1088+
if (in.relroPadding && sec == in.relroPadding->getParent())
1089+
expandOutputSection(alignToPowerOf2(dot, config->commonPageSize) - dot);
1090+
10821091
// Non-SHF_ALLOC sections do not affect the addresses of other OutputSections
10831092
// as they are not part of the process image.
10841093
if (!(sec->flags & SHF_ALLOC)) {
@@ -1160,6 +1169,7 @@ void LinkerScript::adjustOutputSections() {
11601169
uint64_t flags = SHF_ALLOC;
11611170

11621171
SmallVector<StringRef, 0> defPhdrs;
1172+
bool seenRelro = false;
11631173
for (SectionCommand *&cmd : sectionCommands) {
11641174
if (!isa<OutputDesc>(cmd))
11651175
continue;
@@ -1196,9 +1206,17 @@ void LinkerScript::adjustOutputSections() {
11961206
if (sec->sectionIndex != UINT32_MAX)
11971207
maybePropagatePhdrs(*sec, defPhdrs);
11981208

1209+
// Discard .relro_padding if we have not seen one RELRO section. Note: when
1210+
// .tbss is the only RELRO section, there is no associated PT_LOAD segment
1211+
// (needsPtLoad), so we don't append .relro_padding in the case.
1212+
if (in.relroPadding && in.relroPadding->getParent() == sec && !seenRelro)
1213+
discardable = true;
11991214
if (discardable) {
12001215
sec->markDead();
12011216
cmd = nullptr;
1217+
} else {
1218+
seenRelro |=
1219+
sec->relro && !(sec->type == SHT_NOBITS && (sec->flags & SHF_TLS));
12021220
}
12031221
}
12041222

lld/ELF/LinkerScript.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ struct SymbolAssignment : SectionCommand {
105105
bool provide = false;
106106
bool hidden = false;
107107

108+
// This assignment references DATA_SEGMENT_RELRO_END.
109+
bool dataSegmentRelroEnd = false;
110+
108111
unsigned symOrder;
109112

110113
// Holds file name and line number for error reporting.
@@ -352,6 +355,8 @@ class LinkerScript final {
352355
SmallVector<PhdrsCommand, 0> phdrsCommands;
353356

354357
bool hasSectionsCommand = false;
358+
bool seenDataAlign = false;
359+
bool seenRelroEnd = false;
355360
bool errorOnMissingSection = false;
356361

357362
// List of section patterns specified with KEEP commands. They will

lld/ELF/ScriptParser.cpp

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,6 @@ class ScriptParser final : ScriptLexer {
136136
// True if a script being read is in the --sysroot directory.
137137
bool isUnderSysroot = false;
138138

139-
bool seenDataAlign = false;
140-
bool seenRelroEnd = false;
141-
142139
// A set to detect an INCLUDE() cycle.
143140
StringSet<> seen;
144141
};
@@ -600,7 +597,7 @@ void ScriptParser::readSections() {
600597

601598
// If DATA_SEGMENT_RELRO_END is absent, for sections after DATA_SEGMENT_ALIGN,
602599
// the relro fields should be cleared.
603-
if (!seenRelroEnd)
600+
if (!script->seenRelroEnd)
604601
for (SectionCommand *cmd : v)
605602
if (auto *osd = dyn_cast<OutputDesc>(cmd))
606603
osd->osec.relro = false;
@@ -916,7 +913,7 @@ OutputDesc *ScriptParser::readOutputSectionDescription(StringRef outSec) {
916913
script->createOutputSection(unquote(outSec), getCurrentLocation());
917914
OutputSection *osec = &cmd->osec;
918915
// Maybe relro. Will reset to false if DATA_SEGMENT_RELRO_END is absent.
919-
osec->relro = seenDataAlign && !seenRelroEnd;
916+
osec->relro = script->seenDataAlign && !script->seenRelroEnd;
920917

921918
size_t symbolsReferenced = script->referencedSymbols.size();
922919

@@ -1051,6 +1048,7 @@ SymbolAssignment *ScriptParser::readAssignment(StringRef tok) {
10511048

10521049
size_t oldPos = pos;
10531050
SymbolAssignment *cmd = nullptr;
1051+
bool savedSeenRelroEnd = script->seenRelroEnd;
10541052
const StringRef op = peek();
10551053
if (op.starts_with("=")) {
10561054
// Support = followed by an expression without whitespace.
@@ -1071,6 +1069,7 @@ SymbolAssignment *ScriptParser::readAssignment(StringRef tok) {
10711069
}
10721070

10731071
if (cmd) {
1072+
cmd->dataSegmentRelroEnd = !savedSeenRelroEnd && script->seenRelroEnd;
10741073
cmd->commandString =
10751074
tok.str() + " " +
10761075
llvm::join(tokens.begin() + oldPos, tokens.begin() + pos, " ");
@@ -1439,7 +1438,7 @@ Expr ScriptParser::readPrimary() {
14391438
expect(",");
14401439
readExpr();
14411440
expect(")");
1442-
seenDataAlign = true;
1441+
script->seenDataAlign = true;
14431442
return [=] {
14441443
uint64_t align = std::max(uint64_t(1), e().getValue());
14451444
return (script->getDot() + align - 1) & -align;
@@ -1460,9 +1459,8 @@ Expr ScriptParser::readPrimary() {
14601459
expect(",");
14611460
readExpr();
14621461
expect(")");
1463-
seenRelroEnd = true;
1464-
Expr e = getPageSize();
1465-
return [=] { return alignToPowerOf2(script->getDot(), e().getValue()); };
1462+
script->seenRelroEnd = true;
1463+
return [=] { return alignToPowerOf2(script->getDot(), config->maxPageSize); };
14661464
}
14671465
if (tok == "DEFINED") {
14681466
StringRef name = unquote(readParenLiteral());

lld/ELF/SyntheticSections.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2688,6 +2688,10 @@ size_t IBTPltSection::getSize() const {
26882688

26892689
bool IBTPltSection::isNeeded() const { return in.plt->getNumEntries() > 0; }
26902690

2691+
RelroPaddingSection::RelroPaddingSection()
2692+
: SyntheticSection(SHF_ALLOC | SHF_WRITE, SHT_NOBITS, 1, ".relro_padding") {
2693+
}
2694+
26912695
// The string hash function for .gdb_index.
26922696
static uint32_t computeGdbHash(StringRef s) {
26932697
uint32_t h = 0;
@@ -3839,6 +3843,7 @@ void InStruct::reset() {
38393843
got.reset();
38403844
gotPlt.reset();
38413845
igotPlt.reset();
3846+
relroPadding.reset();
38423847
armCmseSGSection.reset();
38433848
ppc64LongBranchTarget.reset();
38443849
mipsAbiFlags.reset();

lld/ELF/SyntheticSections.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,16 @@ class IBTPltSection : public SyntheticSection {
778778
size_t getSize() const override;
779779
};
780780

781+
// Used to align the end of the PT_GNU_RELRO segment and the associated PT_LOAD
782+
// segment to a common-page-size boundary. This padding section ensures that all
783+
// pages in the PT_LOAD segment is covered by at least one section.
784+
class RelroPaddingSection final : public SyntheticSection {
785+
public:
786+
RelroPaddingSection();
787+
size_t getSize() const override { return 0; }
788+
void writeTo(uint8_t *buf) override {}
789+
};
790+
781791
class GdbIndexSection final : public SyntheticSection {
782792
public:
783793
struct AddressEntry {
@@ -1333,6 +1343,7 @@ struct InStruct {
13331343
std::unique_ptr<GotSection> got;
13341344
std::unique_ptr<GotPltSection> gotPlt;
13351345
std::unique_ptr<IgotPltSection> igotPlt;
1346+
std::unique_ptr<RelroPaddingSection> relroPadding;
13361347
std::unique_ptr<SyntheticSection> armCmseSGSection;
13371348
std::unique_ptr<PPC64LongBranchTargetSection> ppc64LongBranchTarget;
13381349
std::unique_ptr<SyntheticSection> mipsAbiFlags;

lld/ELF/Writer.cpp

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,13 @@ template <class ELFT> void elf::createSyntheticSections() {
458458
add(*in.gotPlt);
459459
in.igotPlt = std::make_unique<IgotPltSection>();
460460
add(*in.igotPlt);
461+
// Add .relro_padding if DATA_SEGMENT_RELRO_END is used; otherwise, add the
462+
// section in the absence of PHDRS/SECTIONS commands.
463+
if (config->zRelro && ((script->phdrsCommands.empty() &&
464+
!script->hasSectionsCommand) || script->seenRelroEnd)) {
465+
in.relroPadding = std::make_unique<RelroPaddingSection>();
466+
add(*in.relroPadding);
467+
}
461468

462469
if (config->emachine == EM_ARM) {
463470
in.armCmseSGSection = std::make_unique<ArmCmseSGSection>();
@@ -818,6 +825,9 @@ static bool isRelroSection(const OutputSection *sec) {
818825
if (sec == in.gotPlt->getParent())
819826
return config->zNow;
820827

828+
if (in.relroPadding && sec == in.relroPadding->getParent())
829+
return true;
830+
821831
// .dynamic section contains data for the dynamic linker, and
822832
// there's no need to write to it at runtime, so it's better to put
823833
// it into RELRO.
@@ -857,7 +867,7 @@ enum RankFlags {
857867
RF_BSS = 1 << 7,
858868
};
859869

860-
static unsigned getSectionRank(const OutputSection &osec) {
870+
static unsigned getSectionRank(OutputSection &osec) {
861871
unsigned rank = osec.partition * RF_PARTITION;
862872

863873
// We want to put section specified by -T option first, so we
@@ -920,7 +930,9 @@ static unsigned getSectionRank(const OutputSection &osec) {
920930
// TLS sections directly before the other RELRO sections.
921931
if (!(osec.flags & SHF_TLS))
922932
rank |= RF_NOT_TLS;
923-
if (!isRelroSection(&osec))
933+
if (isRelroSection(&osec))
934+
osec.relro = true;
935+
else
924936
rank |= RF_NOT_RELRO;
925937
// Place .ldata and .lbss after .bss. Making .bss closer to .text alleviates
926938
// relocation overflow pressure.
@@ -1140,6 +1152,18 @@ findOrphanPos(SmallVectorImpl<SectionCommand *>::iterator b,
11401152
SmallVectorImpl<SectionCommand *>::iterator e) {
11411153
OutputSection *sec = &cast<OutputDesc>(*e)->osec;
11421154

1155+
// As a special case, place .relro_padding before the SymbolAssignment using
1156+
// DATA_SEGMENT_RELRO_END, if present.
1157+
if (in.relroPadding && sec == in.relroPadding->getParent()) {
1158+
auto i = std::find_if(b, e, [=](SectionCommand *a) {
1159+
if (auto *assign = dyn_cast<SymbolAssignment>(a))
1160+
return assign->dataSegmentRelroEnd;
1161+
return false;
1162+
});
1163+
if (i != e)
1164+
return i;
1165+
}
1166+
11431167
// Find the first element that has as close a rank as possible.
11441168
auto i = std::max_element(b, e, [=](SectionCommand *a, SectionCommand *b) {
11451169
return getRankProximity(sec, a) < getRankProximity(sec, b);
@@ -2334,6 +2358,7 @@ SmallVector<PhdrEntry *, 0> Writer<ELFT>::createPhdrs(Partition &part) {
23342358
relroEnd = sec;
23352359
}
23362360
}
2361+
relRo->p_align = 1;
23372362

23382363
for (OutputSection *sec : outputSections) {
23392364
if (!needsPtLoad(sec))
@@ -2677,16 +2702,6 @@ template <class ELFT> void Writer<ELFT>::setPhdrs(Partition &part) {
26772702
if (!p->hasLMA)
26782703
p->p_paddr = first->getLMA();
26792704
}
2680-
2681-
if (p->p_type == PT_GNU_RELRO) {
2682-
p->p_align = 1;
2683-
// musl/glibc ld.so rounds the size down, so we need to round up
2684-
// to protect the last page. This is a no-op on FreeBSD which always
2685-
// rounds up.
2686-
p->p_memsz =
2687-
alignToPowerOf2(p->p_offset + p->p_memsz, config->commonPageSize) -
2688-
p->p_offset;
2689-
}
26902705
}
26912706
}
26922707

lld/docs/ELF/linker_script.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,18 @@ description in the ``OVERWRITE_SECTIONS`` command while the insert command
172172
still applies (possibly after orphan section placement). It is recommended to
173173
leave the brace empty (i.e. ``section : {}``) for the insert command, because
174174
its description will be ignored anyway.
175+
176+
Built-in functions
177+
~~~~~~~~~~~~~~~~~~
178+
179+
``DATA_SEGMENT_RELRO_END(offset, exp)`` defines the end of the ``PT_GNU_RELRO``
180+
segment when ``-z relro`` (default) is in effect. Sections between
181+
``DATA_SEGMENT_ALIGN`` and ``DATA_SEGMENT_RELRO_END`` are considered RELRO.
182+
183+
The typical use case is ``. = DATA_SEGMENT_RELRO_END(0, .);`` followed by
184+
writable but non-RELRO sections. LLD ignores ``offset`` and ``exp`` and aligns
185+
the current location to a max-page-size boundary, ensuring that the next
186+
``PT_LOAD`` segment will not overlap with the ``PT_GNU_RELRO`` segment.
187+
188+
LLD will insert ``.relro_padding`` immediately before the symbol assignment
189+
using ``DATA_SEGMENT_RELRO_END``.

lld/docs/ReleaseNotes.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ ELF Improvements
2929
* ``--fat-lto-objects`` option is added to support LLVM FatLTO.
3030
Without ``--fat-lto-objects``, LLD will link LLVM FatLTO objects using the
3131
relocatable object file. (`D146778 <https://reviews.llvm.org/D146778>`_)
32-
32+
* common-page-size can now be larger than the system page-size.
33+
(`#57618 <https://github.com/llvm/llvm-project/issues/57618>`_)
3334

3435
Breaking changes
3536
----------------

lld/test/ELF/arm-execute-only.s

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// CHECK: LOAD 0x000000 0x00000000 0x00000000 0x0016d 0x0016d R 0x10000
1414
// CHECK: LOAD 0x000170 0x00010170 0x00010170 0x{{.*}} 0x{{.*}} R E 0x10000
1515
// CHECK: LOAD 0x000174 0x00020174 0x00020174 0x{{.*}} 0x{{.*}} E 0x10000
16-
// CHECK: LOAD 0x000178 0x00030178 0x00030178 0x00038 0x00038 RW 0x10000
16+
// CHECK: LOAD 0x000178 0x00030178 0x00030178 0x00038 0x00e88 RW 0x10000
1717

1818
// CHECK: 01 .dynsym .gnu.hash .hash .dynstr
1919
// CHECK: 02 .text
@@ -22,7 +22,7 @@
2222

2323
// DIFF: LOAD 0x000000 0x00000000 0x00000000 0x0014d 0x0014d R 0x10000
2424
// DIFF: LOAD 0x000150 0x00010150 0x00010150 0x0000c 0x0000c R E 0x10000
25-
// DIFF: LOAD 0x00015c 0x0002015c 0x0002015c 0x00038 0x00038 RW 0x10000
25+
// DIFF: LOAD 0x00015c 0x0002015c 0x0002015c 0x00038 0x00ea4 RW 0x10000
2626

2727
// DIFF: 01 .dynsym .gnu.hash .hash .dynstr
2828
// DIFF: 02 .text .foo

lld/test/ELF/end-dso-defined.s

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,16 @@
2121
# CHECK-NEXT: AddressAlignment:
2222
# CHECK-NEXT: EntrySize:
2323
# CHECK-NEXT: SectionData (
24-
# CHECK-NEXT: 0000: 08232000 00000000 08232000 00000000
24+
# CHECK-NEXT: 0000: 00302000 00000000 00302000 00000000
2525
# CHECK-NEXT: )
2626

2727
# CHECK: Symbol {
2828
# CHECK: Name: _end
29-
# CHECK-NEXT: Value: 0x202308
29+
# CHECK-NEXT: Value: 0x203000
3030

3131
# CHECK: Symbol {
3232
# CHECK: Name: end
33-
# CHECK-NEXT: Value: 0x202308
33+
# CHECK-NEXT: Value: 0x203000
3434

3535
.global _start
3636
_start:

0 commit comments

Comments
 (0)