Skip to content

Commit 62e6c1e

Browse files
authored
[lld/mac] Allow -segprot having stricter initprot than maxprot on mac (#107269)
...including for catalyst. The usecase for this is to put certain security-critical variables into a special segment/section that's mapped as read-only most of the time, and that temporary gets remapped as writeable when these variables are written to be the program. This protects against them being written to by heap spraying attacks. This special section should be mapped as read-only at program start, so using `-segprot MY_PROTECTED_MEMORY_THINGER rw r` to mark that segment as rw maxprot and r initprot is exactly what we want. lld has so far rejected mismatching initprot and maxprot. ld64 doesn't reject this, but silently writes initprot into both fields (!) It looks like this might not be fully intentional, see https://crbug.com/41495919#comment5 and http://crbug.com/41495919#comment8. In any case, when postprocessing ld64's output to have different values for initprot and maxprot, the dynamic loader seems to do the right thing (see also the previous two links). The same technique also works on Windows, using both link.exe and lld-link.exe using `/SECTION:myprotsect,R`. So, since this is useful, allow it when targeting macOS, and make it do what you'd expect. Since loader support for this on iOS is less clear, keep disallowing it there for now. See the PR for the program I used to check that this seems to work. (I only checked on arm64 macOS 14.5 so far; will run this on many more systems on bots once this is merged and rolled in.)
1 parent be1958f commit 62e6c1e

File tree

3 files changed

+56
-9
lines changed

3 files changed

+56
-9
lines changed

lld/MachO/Driver.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1882,9 +1882,21 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
18821882
StringRef segName = arg->getValue(0);
18831883
uint32_t maxProt = parseProtection(arg->getValue(1));
18841884
uint32_t initProt = parseProtection(arg->getValue(2));
1885-
if (maxProt != initProt && config->arch() != AK_i386)
1886-
error("invalid argument '" + arg->getAsString(args) +
1887-
"': max and init must be the same for non-i386 archs");
1885+
1886+
// FIXME: Check if this works on more platforms.
1887+
bool allowsDifferentInitAndMaxProt =
1888+
config->platform() == PLATFORM_MACOS ||
1889+
config->platform() == PLATFORM_MACCATALYST;
1890+
if (allowsDifferentInitAndMaxProt) {
1891+
if (initProt > maxProt)
1892+
error("invalid argument '" + arg->getAsString(args) +
1893+
"': init must not be more permissive than max");
1894+
} else {
1895+
if (maxProt != initProt && config->arch() != AK_i386)
1896+
error("invalid argument '" + arg->getAsString(args) +
1897+
"': max and init must be the same for non-macOS non-i386 archs");
1898+
}
1899+
18881900
if (segName == segment_names::linkEdit)
18891901
error("-segprot cannot be used to change __LINKEDIT's protections");
18901902
config->segmentProtections.push_back({segName, maxProt, initProt});

lld/MachO/OutputSegment.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ static uint32_t initProt(StringRef name) {
4242
static uint32_t maxProt(StringRef name) {
4343
assert(config->arch() != AK_i386 &&
4444
"TODO: i386 has different maxProt requirements");
45+
auto it = find_if(
46+
config->segmentProtections,
47+
[&](const SegmentProtection &segprot) { return segprot.name == name; });
48+
if (it != config->segmentProtections.end())
49+
return it->maxProt;
50+
4551
return initProt(name);
4652
}
4753

lld/test/MachO/segprot.s

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o
33

44
## Make sure the option parser doesn't think --x and -w are flags.
5-
# RUN: %lld -dylib -o %t %t.o -segprot FOO rwx xwr -segprot BAR --x --x -segprot BAZ -w -w
5+
# RUN: %lld -dylib -o %t %t.o \
6+
# RUN: -segprot FOO rwx xwr \
7+
# RUN: -segprot BAR --x --x \
8+
# RUN: -segprot BAZ -w -w
69
# RUN: llvm-readobj --macho-segment %t | FileCheck %s
710

811
# CHECK: Name: FOO
@@ -32,12 +35,38 @@
3235
# CHECK-NEXT: maxprot: -w-
3336
# CHECK-NEXT: initprot: -w-
3437

35-
# RUN: not %lld -dylib -o /dev/null %t.o -segprot FOO rwx rw 2>&1 | FileCheck %s --check-prefix=MISMATCH
36-
# RUN: not %lld -dylib -o /dev/null %t.o -segprot __LINKEDIT rwx rwx 2>&1 | FileCheck %s --check-prefix=NO-LINKEDIT
37-
# RUN: not %lld -dylib -o /dev/null %t.o -segprot FOO uhh wat 2>&1 | FileCheck %s --check-prefix=MISPARSE
38-
# RUN: not %lld -dylib -o /dev/null %t.o -segprot FOO rwx 2>&1 | FileCheck %s --check-prefix=MISSING
38+
# RUN: %lld -dylib -o %t.different %t.o -segprot FOO rw r
39+
# RUN: llvm-readobj --macho-segment %t.different \
40+
# RUN: | FileCheck %s --check-prefix=DIFFERENT
3941

40-
# MISMATCH: error: invalid argument '-segprot FOO rwx rw': max and init must be the same for non-i386 archs
42+
# RUN: %no-arg-lld -arch x86_64 -platform_version "mac catalyst" 14.0.0 17.5 \
43+
# RUN: -dylib -o /dev/null %t.o -segprot FOO rw r
44+
# RUN: llvm-readobj --macho-segment %t.different \
45+
# RUN: | FileCheck %s --check-prefix=DIFFERENT
46+
47+
# DIFFERENT: Name: FOO
48+
# DIFFERENT-NEXT: Size:
49+
# DIFFERENT-NEXT: vmaddr:
50+
# DIFFERENT-NEXT: vmsize:
51+
# DIFFERENT-NEXT: fileoff:
52+
# DIFFERENT-NEXT: filesize:
53+
# DIFFERENT-NEXT: maxprot: rw-
54+
# DIFFERENT-NEXT: initprot: r--
55+
56+
# RUN: not %no-arg-lld -arch x86_64 -platform_version ios-simulator 14.0 15.0 \
57+
# RUN: -dylib -o /dev/null %t.o -segprot FOO rwx rw 2>&1 \
58+
# RUN: | FileCheck %s --check-prefix=MISMATCH
59+
# RUN: not %lld -dylib -o /dev/null %t.o -segprot FOO r rw 2>&1 \
60+
# RUN: | FileCheck %s --check-prefix=INITTOOPERMISSIVE
61+
# RUN: not %lld -dylib -o /dev/null %t.o -segprot __LINKEDIT rwx rwx 2>&1 \
62+
# RUN: | FileCheck %s --check-prefix=NO-LINKEDIT
63+
# RUN: not %lld -dylib -o /dev/null %t.o -segprot FOO uhh wat 2>&1 \
64+
# RUN: | FileCheck %s --check-prefix=MISPARSE
65+
# RUN: not %lld -dylib -o /dev/null %t.o -segprot FOO rwx 2>&1 \
66+
# RUN: | FileCheck %s --check-prefix=MISSING
67+
68+
# MISMATCH: error: invalid argument '-segprot FOO rwx rw': max and init must be the same for non-macOS non-i386 archs
69+
# INITTOOPERMISSIVE: error: invalid argument '-segprot FOO r rw': init must not be more permissive than max
4170
# NO-LINKEDIT: error: -segprot cannot be used to change __LINKEDIT's protections
4271
# MISPARSE: error: unknown -segprot letter 'u' in uhh
4372
# MISPARSE: error: unknown -segprot letter 'a' in wat

0 commit comments

Comments
 (0)