#!/bin/sh # Build cgit for a release with Linux hardening flags. These are ELF and GCC # or Clang specific, so this targets a Linux deploy rather than local macOS # development, where a plain make is enough. The flags add a stack protector, # fortified libc calls, a position independent executable, and full RELRO. By # default Lua is pinned off so the binary needs no Lua at runtime, and running # it as ./tools/release-build.sh lua links in the backend behind the "lua:" # filter prefix instead, which needs a Lua dev package installed. set -eu # The argument is checked rather than assumed, since anything unrecognised # would otherwise fall through to a quiet Lua-less build that looks like it # worked. if [ "$#" -gt 1 ]; then echo "usage: $0 [lua]" >&2 exit 2 fi case "${1:-}" in "") set -- NO_LUA=1 ;; lua) set -- ;; *) echo "$0: unknown argument \"$1\", expected \"lua\" or nothing" >&2 exit 2 ;; esac # Every make target below is written relative to the repository root, so go # there rather than requiring the caller to. cd "$(dirname "$0")/.." CC=${CC:-cc} # Not every hardening flag exists on every toolchain, and an unknown one would # fail the build rather than be ignored, so the ones that might be missing are # compiled before they are used. The argument is left unquoted so a caller can # probe several flags at once. supports() { printf 'int main(void){return 0;}\n' >"$probe.c" $CC $1 -o "$probe.out" "$probe.c" >/dev/null 2>&1 } # The template is spelled out rather than passed with -t, whose handling of a # prefix differs between the GNU and BSD versions. probe=$(mktemp "${TMPDIR:-/tmp}/cgit-probe.XXXXXX") trap 'rm -f "$probe" "$probe.c" "$probe.out"' EXIT CFLAGS="-O2 -g -Wall \ -fstack-protector-strong \ -fPIE \ -fno-plt" # Level 3 adds the bounds checks level 2 could not prove, and needs GCC 12 or # Clang 15. Fall back rather than lose fortification altogether. if supports "-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -O2"; then CFLAGS="$CFLAGS -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3" else CFLAGS="$CFLAGS -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2" fi # Without this a large stack frame can step over a guard page rather than # touch it. if supports "-fstack-clash-protection"; then CFLAGS="$CFLAGS -fstack-clash-protection" fi LDFLAGS="-pie \ -Wl,-z,relro,-z,now \ -Wl,-z,noexecstack" # These reach only the cgit objects, so git's own sources are not held to them. # -Wformat-security is an error because a non-literal format with no arguments # is never intentional. The shape that let a repository supply its own format # string through module-link was the other one, a non-literal that does take # arguments, and only -Wformat-nonliteral reports that, and that one is left out because it also fires # on forwarding a va_list and on local format constants, so it cannot be an # error without false positives. It is still worth running by hand when # touching anything that formats. # # make cgit CGIT_EXTRA_CFLAGS=-Wformat-nonliteral # CGIT_EXTRA_CFLAGS="-Wformat -Wformat-security -Werror=format-security" # The build starts from clean so the bundled git objects pick up the same # flags, and cleanall rather than clean because only cleanall descends into # vendor/git. make cleanall exec make "$@" CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" \ CGIT_EXTRA_CFLAGS="$CGIT_EXTRA_CFLAGS"