diff --git a/cpython-unix/build-cpython.sh b/cpython-unix/build-cpython.sh index 8224aca26..381521282 100755 --- a/cpython-unix/build-cpython.sh +++ b/cpython-unix/build-cpython.sh @@ -729,6 +729,9 @@ autoconf # 3.12 and earlier) CFLAGS_JIT="${CFLAGS}" +# JIT stencils should not inherit target-wide stack hardening flags. +CFLAGS_JIT+=" -fno-stack-protector -fno-stack-clash-protection" + # In 3.14+, the JIT compiler on x86-64 Linux uses a model that conflicts with `-fPIC`, so strip it # from the flags. See: # - https://github.com/python/cpython/issues/135690 @@ -743,6 +746,14 @@ CFLAGS=$CFLAGS CPPFLAGS=$CFLAGS CFLAGS_JIT=$CFLAGS_JIT LDFLAGS=$LDFLAGS \ # Supplement produced Makefile with our modifications. cat ../Makefile.extra >> Makefile +# Debian's PPC64LE GCC 6 defaults to PIE but selects the non-PIE startup object unless -pie is +# passed explicitly. Add it only to LINKFORSHARED, which CPython uses when linking executables. +# See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81170. +# TODO(jjh)Remove when the ppc64le toolchain changes +if [[ "${TARGET_TRIPLE}" = "ppc64le-unknown-linux-gnu" ]]; then + printf '\nLINKFORSHARED += -pie\n' >> Makefile +fi + make -j "${NUM_CPUS}" make -j "${NUM_CPUS}" sharedinstall DESTDIR="${ROOT}/out/python" make -j "${NUM_CPUS}" install DESTDIR="${ROOT}/out/python" diff --git a/cpython-unix/build.py b/cpython-unix/build.py index eb0e6ae86..d8bf04308 100755 --- a/cpython-unix/build.py +++ b/cpython-unix/build.py @@ -208,6 +208,11 @@ def add_target_env(env, build_platform, target_triple, build_env, build_options) else: raise Exception("unhandled build platform: %s" % build_platform) + if "debug" in build_options and "-linux-" in target_triple: + # Fortification requires optimization and is ineffective with debug's -O0. Keep this last + # so it overrides any configured fortification level. + extra_target_cflags.append("-U_FORTIFY_SOURCE") + env["EXTRA_HOST_CFLAGS"] = " ".join(extra_host_cflags) env["EXTRA_HOST_LDFLAGS"] = " ".join(extra_host_ldflags) env["EXTRA_TARGET_CFLAGS"] = " ".join(extra_target_cflags) diff --git a/cpython-unix/targets.yml b/cpython-unix/targets.yml index d0db3e39e..88f3f94c8 100644 --- a/cpython-unix/targets.yml +++ b/cpython-unix/targets.yml @@ -130,6 +130,11 @@ aarch64-unknown-linux-gnu: target_cc: clang target_cxx: clang++ target_cflags: + # Hardening + - '-fstack-protector' + - '-fstack-clash-protection' + - '-U_FORTIFY_SOURCE' + - '-D_FORTIFY_SOURCE=2' - '-fvisibility=hidden' # Enable frame pointers - '-fno-omit-frame-pointer' @@ -139,6 +144,8 @@ aarch64-unknown-linux-gnu: target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb @@ -185,9 +192,16 @@ armv7-unknown-linux-gnueabi: host_cxx: /usr/bin/x86_64-linux-gnu-g++ target_cc: /usr/bin/arm-linux-gnueabi-gcc target_cxx: /usr/bin/arm-linux-gnueabi-g++ + target_cflags: + # Hardening + - '-fstack-protector' + - '-U_FORTIFY_SOURCE' + - '-D_FORTIFY_SOURCE=2' target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb @@ -229,9 +243,16 @@ armv7-unknown-linux-gnueabihf: host_cxx: /usr/bin/x86_64-linux-gnu-g++ target_cc: /usr/bin/arm-linux-gnueabihf-gcc target_cxx: /usr/bin/arm-linux-gnueabihf-g++ + target_cflags: + # Hardening + - '-fstack-protector' + - '-U_FORTIFY_SOURCE' + - '-D_FORTIFY_SOURCE=2' target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb @@ -273,9 +294,16 @@ loongarch64-unknown-linux-gnu: host_cxx: /usr/bin/x86_64-linux-gnu-g++ target_cc: /usr/bin/loongarch64-linux-gnu-gcc target_cxx: /usr/bin/loongarch64-linux-gnu-g++ + target_cflags: + # Hardening + - '-fstack-protector' + - '-U_FORTIFY_SOURCE' + - '-D_FORTIFY_SOURCE=2' target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' - '-Wl,-z,max-page-size=0x10000' needs: - autoconf @@ -318,9 +346,16 @@ mips-unknown-linux-gnu: host_cxx: /usr/bin/x86_64-linux-gnu-g++ target_cc: /usr/bin/mips-linux-gnu-gcc target_cxx: /usr/bin/mips-linux-gnu-g++ + target_cflags: + # Hardening + - '-fstack-protector' + - '-U_FORTIFY_SOURCE' + - '-D_FORTIFY_SOURCE=2' target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb @@ -362,9 +397,16 @@ mipsel-unknown-linux-gnu: host_cxx: /usr/bin/x86_64-linux-gnu-g++ target_cc: /usr/bin/mipsel-linux-gnu-gcc target_cxx: /usr/bin/mipsel-linux-gnu-g++ + target_cflags: + # Hardening + - '-fstack-protector' + - '-U_FORTIFY_SOURCE' + - '-D_FORTIFY_SOURCE=2' target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb @@ -406,9 +448,16 @@ ppc64le-unknown-linux-gnu: host_cxx: /usr/bin/x86_64-linux-gnu-g++ target_cc: /usr/bin/powerpc64le-linux-gnu-gcc target_cxx: /usr/bin/powerpc64le-linux-gnu-g++ + target_cflags: + # Hardening + - '-fstack-protector' + - '-U_FORTIFY_SOURCE' + - '-D_FORTIFY_SOURCE=2' target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb @@ -450,9 +499,16 @@ riscv64-unknown-linux-gnu: host_cxx: /usr/bin/x86_64-linux-gnu-g++ target_cc: /usr/bin/riscv64-linux-gnu-gcc target_cxx: /usr/bin/riscv64-linux-gnu-g++ + target_cflags: + # Hardening + - '-fstack-protector' + - '-U_FORTIFY_SOURCE' + - '-D_FORTIFY_SOURCE=2' target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb @@ -495,11 +551,17 @@ s390x-unknown-linux-gnu: target_cc: /usr/bin/s390x-linux-gnu-gcc target_cxx: /usr/bin/s390x-linux-gnu-g++ target_cflags: + # Hardening + - '-fstack-protector' + - '-U_FORTIFY_SOURCE' + - '-D_FORTIFY_SOURCE=2' # set the minimum compatibility level to z10 (released 2008) - '-march=z10' target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb @@ -598,6 +660,11 @@ x86_64-unknown-linux-gnu: target_cc: clang target_cxx: clang++ target_cflags: + # Hardening + - '-fstack-protector' + - '-fstack-clash-protection' + - '-U_FORTIFY_SOURCE' + - '-D_FORTIFY_SOURCE=2' - '-fvisibility=hidden' # Enable frame pointers - '-fno-omit-frame-pointer' @@ -607,6 +674,8 @@ x86_64-unknown-linux-gnu: target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb @@ -650,6 +719,11 @@ x86_64_v2-unknown-linux-gnu: target_cc: clang target_cxx: clang++ target_cflags: + # Hardening + - '-fstack-protector' + - '-fstack-clash-protection' + - '-U_FORTIFY_SOURCE' + - '-D_FORTIFY_SOURCE=2' - '-march=x86-64-v2' - '-fvisibility=hidden' # Enable frame pointers @@ -660,6 +734,8 @@ x86_64_v2-unknown-linux-gnu: target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb @@ -703,6 +779,11 @@ x86_64_v3-unknown-linux-gnu: target_cc: clang target_cxx: clang++ target_cflags: + # Hardening + - '-fstack-protector' + - '-fstack-clash-protection' + - '-U_FORTIFY_SOURCE' + - '-D_FORTIFY_SOURCE=2' - '-march=x86-64-v3' - '-fvisibility=hidden' # Enable frame pointers @@ -713,6 +794,8 @@ x86_64_v3-unknown-linux-gnu: target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb @@ -756,6 +839,11 @@ x86_64_v4-unknown-linux-gnu: target_cc: clang target_cxx: clang++ target_cflags: + # Hardening + - '-fstack-protector' + - '-fstack-clash-protection' + - '-U_FORTIFY_SOURCE' + - '-D_FORTIFY_SOURCE=2' - '-march=x86-64-v4' - '-fvisibility=hidden' # Enable frame pointers @@ -766,6 +854,8 @@ x86_64_v4-unknown-linux-gnu: target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb @@ -809,6 +899,9 @@ x86_64-unknown-linux-musl: target_cc: musl-clang target_cxx: clang++ # TODO: Explore a musl-clang++ shim? target_cflags: + # Hardening + - '-fstack-protector' + - '-fstack-clash-protection' - '-fvisibility=hidden' # Enable frame pointers - '-fno-omit-frame-pointer' @@ -816,6 +909,8 @@ x86_64-unknown-linux-musl: target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb @@ -859,6 +954,9 @@ x86_64_v2-unknown-linux-musl: target_cc: musl-clang target_cxx: clang++ # TODO: Explore a musl-clang++ shim? target_cflags: + # Hardening + - '-fstack-protector' + - '-fstack-clash-protection' - '-march=x86-64-v2' - '-fvisibility=hidden' # Enable frame pointers @@ -867,6 +965,8 @@ x86_64_v2-unknown-linux-musl: target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb @@ -910,6 +1010,9 @@ x86_64_v3-unknown-linux-musl: target_cc: musl-clang target_cxx: clang++ # TODO: Explore a musl-clang++ shim? target_cflags: + # Hardening + - '-fstack-protector' + - '-fstack-clash-protection' - '-march=x86-64-v3' - '-fvisibility=hidden' # Enable frame pointers @@ -918,6 +1021,8 @@ x86_64_v3-unknown-linux-musl: target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb @@ -961,6 +1066,9 @@ x86_64_v4-unknown-linux-musl: target_cc: musl-clang target_cxx: clang++ # TODO: Explore a musl-clang++ shim? target_cflags: + # Hardening + - '-fstack-protector' + - '-fstack-clash-protection' - '-march=x86-64-v4' - '-fvisibility=hidden' # Enable frame pointers @@ -969,6 +1077,8 @@ x86_64_v4-unknown-linux-musl: target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb @@ -1016,6 +1126,9 @@ aarch64-unknown-linux-musl: target_cc: musl-clang target_cxx: clang++ target_cflags: + # Hardening + - '-fstack-protector' + - '-fstack-clash-protection' - '-fvisibility=hidden' # Enable frame pointers - '-fno-omit-frame-pointer' @@ -1023,6 +1136,8 @@ aarch64-unknown-linux-musl: target_ldflags: # Hardening - '-Wl,-z,noexecstack' + - '-Wl,-z,relro' + - '-Wl,-z,now' needs: - autoconf - bdb diff --git a/src/validation.rs b/src/validation.rs index fb1824bc9..f0bbc71b6 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -10,8 +10,9 @@ use { object::{ Architecture, Endianness, FileKind, Object, SectionIndex, SymbolScope, elf::{ - ET_DYN, ET_EXEC, FileHeader32, FileHeader64, PF_X, PT_GNU_STACK, SHN_UNDEF, STB_GLOBAL, - STB_WEAK, STV_DEFAULT, STV_HIDDEN, + DF_1_NOW, DF_BIND_NOW, DF_TEXTREL, DT_BIND_NOW, DT_FLAGS, DT_FLAGS_1, DT_TEXTREL, + ET_DYN, ET_EXEC, FileHeader32, FileHeader64, PF_W, PF_X, PT_GNU_RELRO, PT_GNU_STACK, + PT_LOAD, SHN_UNDEF, STB_GLOBAL, STB_WEAK, STV_DEFAULT, STV_HIDDEN, }, macho::{LC_CODE_SIGNATURE, MH_OBJECT, MH_TWOLEVEL, MachHeader32, MachHeader64}, read::{ @@ -258,7 +259,10 @@ static ELF_ALLOWED_LIBRARIES_BY_TRIPLE: Lazy>( let versions = sections.versions(endian, data)?; + let mut has_dynamic_section = false; + let mut has_bind_now = false; + let mut has_text_relocations = false; + for (section_index, section) in sections.iter().enumerate() { // Dynamic sections defined needed libraries, which we validate. if let Some((entries, index)) = section.dynamic(endian, data)? { + has_dynamic_section = true; let strings = sections.strings(endian, data, index).unwrap_or_default(); for entry in entries { @@ -1087,6 +1096,20 @@ fn validate_elf>( } } } + + match entry.tag32(endian) { + Some(DT_BIND_NOW) => has_bind_now = true, + Some(DT_FLAGS) => { + let flags = entry.val32(endian).unwrap_or_default(); + has_bind_now |= flags & DF_BIND_NOW != 0; + has_text_relocations |= flags & DF_TEXTREL != 0; + } + Some(DT_FLAGS_1) => { + has_bind_now |= entry.val32(endian).unwrap_or_default() & DF_1_NOW != 0; + } + Some(DT_TEXTREL) => has_text_relocations = true, + _ => {} + } } } @@ -1180,8 +1203,9 @@ fn validate_elf>( } } - // Verify that objects are not requesting an executable stack. For backwards compatibility, - // Linux (the kernel when loading an executable, and glibc when loading a shared library) + // Verify that objects are not requesting an executable stack and that other hardening + // properties exist in the linked outputs. For backwards compatibility, Linux (the kernel + // when loading an executable, and glibc when loading a shared library) // assumes you need an executable stack unless you request otherwise. In linked outputs // (executables and shared libraries) this is in the program header: the flags of a // PT_GNU_STACK entry specify stack permissions, and the default if unspecified is RWX. In @@ -1194,15 +1218,27 @@ fn validate_elf>( if matches!(elf.e_type(endian), ET_EXEC | ET_DYN) { let mut found_pt_gnu_stack = false; + let mut found_pt_gnu_relro = false; for phdr in elf.program_headers(endian, data)? { - if phdr.p_type(endian) != PT_GNU_STACK { - continue; - } - found_pt_gnu_stack = true; - if (phdr.p_flags(endian) & PF_X) != 0 { - context - .errors - .push(format!("{} requests executable stack", path.display())); + let flags = phdr.p_flags(endian); + + match phdr.p_type(endian) { + PT_GNU_STACK => { + found_pt_gnu_stack = true; + if flags & PF_X != 0 { + context + .errors + .push(format!("{} requests executable stack", path.display())); + } + } + PT_GNU_RELRO => found_pt_gnu_relro = true, + PT_LOAD if flags & (PF_W | PF_X) == (PF_W | PF_X) => { + context.errors.push(format!( + "{} has a writable and executable PT_LOAD segment", + path.display(), + )); + } + _ => {} } } if !found_pt_gnu_stack { @@ -1211,6 +1247,23 @@ fn validate_elf>( path.display(), )); } + if !found_pt_gnu_relro { + context.errors.push(format!( + "{} is missing a PT_GNU_RELRO segment", + path.display(), + )); + } + if has_dynamic_section && !has_bind_now { + context.errors.push(format!( + "{} does not request immediate symbol binding", + path.display(), + )); + } + if has_text_relocations { + context + .errors + .push(format!("{} contains text relocations", path.display())); + } } Ok(())