dcee1e5c54
The clang-r370808 upgrade contains a change to LLD allow PT_LOAD segments to reside at non-multiples of the page size in the resulting object file. https://reviews.llvm.org/rL369344 While this helps reduce the alignment waste and resulting image size, it has interesting implications for execute only memory (XOM): The runtime loader will now load code or data from other segments into pages with different protections than intended. This would partially defeat execute only (XOM) text sections as the segment could now overlap with previous and following sections. This might allow for code or data from the preceding and following sections (like .eh_frame, and .data.rel.ro) to be executable, and either ends of .text to be readable. When the runtime loader (linker[64]) `mmap`s segments from *.so files, the file offset parameter (see `man 2 mmap`) MUST be a multiple of the page size. Since the updated LLD can now pack segments in a file (which helps minimize resulting object file size) (previously, the segment offsets were page aligned), this has interesting implications. To appreciate the current bug, consider the following output from `readelf` before this patch is applied, but after the toolchain upgrade: ``` $ readelf -lSW $OUT/symbols/apex/com.android.runtime/lib64/bionic/libc.so ... [Nr] Name Type Address Off Size ES Flg Lk Inf Al ... [13] .eh_frame PROGBITS 000000000002e7c0 02e7c0 013374 00 A 0 0 8 [14] .text PROGBITS 0000000000042b40 041b40 09ecb4 00 AX 0 0 64 [15] .plt PROGBITS 00000000000e1800 0e0800 001f30 00 AX 0 0 16 [16] .data.rel.ro PROGBITS 00000000000e4740 0e2740 005208 00 WA 0 0 32 ... Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x000230 0x000230 R 0x8 LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x041b34 0x041b34 R 0x1000 LOAD 0x041b40 0x0000000000042b40 0x0000000000042b40 0x0a0bf0 0x0a0bf0 E 0x1000 LOAD 0x0e2740 0x00000000000e4740 0x00000000000e4740 0x006720 0x006720 RW 0x1000 ... 01 .note.android.ident .note.gnu.build-id .dynsym .gnu.version .gnu.version_d .gnu.version_r .gnu.hash .dynstr .rela.dyn .rela.plt .rodata .eh_frame_hdr .eh_frame 02 .text .plt 03 .data.rel.ro .fini_array .init_array .dynamic .got .got.plt ... The above output tells us: 1. .text will wind up in the third (02) segment. 2. The third segment will be (LOAD)'ed as (E)xecutable. 3. Because the file (Offset) of the first segment (0x41b40) is NOT a multiple of the page size, it cannot be passed as the `offset` to `mmap`. As such it will be rounded down to the first multiple of the page size, 0x41000. 4. The preceding section (.eh_frame) will be loaded in the preceding segment (01). It occupies file (Off)set range [(0x2e7c0):0x41b34]. 0x41b34 is not explicit in the output, instead you must use the formula: Off + Size == End ie. 0x2e7c0 + 0x13374 == 0x41b34 (This happens to match (FileSiz) of the second segment, which makes sense as .eh_frame is the final section in the second segment.) 5. mmap'ing file offset 0x41000 when loading the second segment will include 0x4c0 bytes (0x42000 - 0x41b40) from .text, now mapped as readable (oops). Suddenly code from .text is now readable (and thus scannable for gadgets for ROP chains). 6. mmap'ing file offset 0x41000 when loading the third segment will include 0xb34 bytes (0x41b34 - 0x41000) from .eh_frame, now mapped as executable (oops). Suddenly data from .eh_frame is now executable (and thus a potential gadget for ROP chains). 7. mmap'ing file offset 0xe2000 when loading the third segment will include 0x8CO bytes (0xe3000 - 0xe2740) from .data.rel.ro, now mapped as executable (oops). Suddenly data from .data.rel.ro is now executable (and thus a potential gadget for ROP chains). 8. mmap'ing file offset 0xe2000 when loading the fourth segment will include 0x730 bytes (0xe0800 + 0x1f30 - 0xe2000) from .plt, now mapped as readable (oops). Suddenly data from .plt is now readable (and thus scannable for gadgets for ROP chains). All these oops' could be avoided if the linker placed .text+.plt at page size aligned file offsets, which is what `-Wl,-z,separate-code` code does. After this patch, we have: ``` $ readelf -lSW $OUT/symbols/apex/com.android.runtime/lib64/bionic/libc.so ... Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x000230 0x000230 R 0x8 LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x041b34 0x041b34 R 0x1000 LOAD 0x042000 0x0000000000042000 0x0000000000042000 0x0a0be0 0x0a0be0 E 0x1000 LOAD 0x0e3000 0x00000000000e3000 0x00000000000e3000 0x006720 0x006720 RW 0x1000 ``` In the future, we could go back to tightly packing segments in the binary if the runtime loader was improved to detect the previously stated problem, and `memset` over the problematic ranges of the freshly `mmap`ed pages (implying additional startup cost for reduced binary size). This might save ~6 KB from each native binary, which adds up to ~17 MB for an AOSP image. Also, prefer -Wl,--execute-only rather than -Wl,-execute-only Bug: 139945549 Bug: 146144180 Test: readelf -lSW $OUT/symbols/apex/com.android.runtime/lib64/bionic/libc.so Change-Id: I64527e034ca3c71565ea52ed06f81f75d5216627 Reported-by: Ryan Prichard <rprichard@google.com> Suggested-by: Fangrui Song <maskray@google.com> Signed-off-by: Nick Desaulniers <ndesaulniers@google.com>
79 lines
2.1 KiB
Go
79 lines
2.1 KiB
Go
// Copyright 2018 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package cc
|
|
|
|
import (
|
|
"android/soong/android"
|
|
)
|
|
|
|
type XomProperties struct {
|
|
Xom *bool
|
|
}
|
|
|
|
type xom struct {
|
|
Properties XomProperties
|
|
}
|
|
|
|
func (xom *xom) props() []interface{} {
|
|
return []interface{}{&xom.Properties}
|
|
}
|
|
|
|
func (xom *xom) begin(ctx BaseModuleContext) {}
|
|
|
|
func (xom *xom) deps(ctx BaseModuleContext, deps Deps) Deps {
|
|
return deps
|
|
}
|
|
|
|
func (xom *xom) flags(ctx ModuleContext, flags Flags) Flags {
|
|
disableXom := false
|
|
|
|
if !ctx.Config().EnableXOM() || ctx.Config().XOMDisabledForPath(ctx.ModuleDir()) {
|
|
disableXom = true
|
|
}
|
|
|
|
if xom.Properties.Xom != nil && !*xom.Properties.Xom {
|
|
return flags
|
|
}
|
|
|
|
// If any static dependencies have XOM disabled, we should disable XOM in this module,
|
|
// the assumption being if it's been explicitly disabled then there's probably incompatible
|
|
// code in the library which may get pulled in.
|
|
if !disableXom {
|
|
ctx.VisitDirectDeps(func(m android.Module) {
|
|
cc, ok := m.(*Module)
|
|
if !ok || cc.xom == nil || !cc.static() {
|
|
return
|
|
}
|
|
if cc.xom.Properties.Xom != nil && !*cc.xom.Properties.Xom {
|
|
disableXom = true
|
|
return
|
|
}
|
|
})
|
|
}
|
|
|
|
// Enable execute-only if none of the dependencies disable it,
|
|
// also if it's explicitly set true (allows overriding dependencies disabling it).
|
|
if !disableXom || (xom.Properties.Xom != nil && *xom.Properties.Xom) {
|
|
// XOM is only supported on AArch64 when using lld.
|
|
if ctx.Arch().ArchType == android.Arm64 && ctx.useClangLld(ctx) {
|
|
flags.Local.LdFlags = append(flags.Local.LdFlags,
|
|
"-Wl,--execute-only",
|
|
"-Wl,-z,separate-code",
|
|
)
|
|
}
|
|
}
|
|
|
|
return flags
|
|
}
|