Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

fortify: Add compile-time FORTIFY_SOURCE tests

While the run-time testing of FORTIFY_SOURCE is already present in
LKDTM, there is no testing of the expected compile-time detections. In
preparation for correctly supporting FORTIFY_SOURCE under Clang, adding
additional FORTIFY_SOURCE defenses, and making sure FORTIFY_SOURCE
doesn't silently regress with GCC, introduce a build-time test suite that
checks each expected compile-time failure condition.

As this is relatively backwards from standard build rules in the
sense that a successful test is actually a compile _failure_, create
a wrapper script to check for the correct errors, and wire it up as
a dummy dependency to lib/string.o, collecting the results into a log
file artifact.

Signed-off-by: Kees Cook <keescook@chromium.org>

+226
+9
MAINTAINERS
··· 7323 7323 S: Maintained 7324 7324 F: drivers/net/ethernet/nvidia/* 7325 7325 7326 + FORTIFY_SOURCE 7327 + M: Kees Cook <keescook@chromium.org> 7328 + L: linux-hardening@vger.kernel.org 7329 + S: Supported 7330 + F: include/linux/fortify-string.h 7331 + F: lib/test_fortify/* 7332 + F: scripts/test_fortify.sh 7333 + K: \b__NO_FORTIFY\b 7334 + 7326 7335 FPGA DFL DRIVERS 7327 7336 M: Wu Hao <hao.wu@intel.com> 7328 7337 R: Tom Rix <trix@redhat.com>
+2
lib/.gitignore
··· 4 4 /gen_crc32table 5 5 /gen_crc64table 6 6 /oid_registry_data.c 7 + /test_fortify.log 8 + /test_fortify/*.log
+33
lib/Makefile
··· 360 360 obj-$(CONFIG_SLUB_KUNIT_TEST) += slub_kunit.o 361 361 362 362 obj-$(CONFIG_GENERIC_LIB_DEVMEM_IS_ALLOWED) += devmem_is_allowed.o 363 + 364 + # FORTIFY_SOURCE compile-time behavior tests 365 + TEST_FORTIFY_SRCS = $(wildcard $(srctree)/$(src)/test_fortify/*-*.c) 366 + TEST_FORTIFY_LOGS = $(patsubst $(srctree)/$(src)/%.c, %.log, $(TEST_FORTIFY_SRCS)) 367 + TEST_FORTIFY_LOG = test_fortify.log 368 + 369 + quiet_cmd_test_fortify = TEST $@ 370 + cmd_test_fortify = $(CONFIG_SHELL) $(srctree)/scripts/test_fortify.sh \ 371 + $< $@ "$(NM)" $(CC) $(c_flags) \ 372 + $(call cc-disable-warning,fortify-source) 373 + 374 + targets += $(TEST_FORTIFY_LOGS) 375 + clean-files += $(TEST_FORTIFY_LOGS) 376 + clean-files += $(addsuffix .o, $(TEST_FORTIFY_LOGS)) 377 + $(obj)/test_fortify/%.log: $(src)/test_fortify/%.c \ 378 + $(src)/test_fortify/test_fortify.h \ 379 + $(srctree)/include/linux/fortify-string.h \ 380 + $(srctree)/scripts/test_fortify.sh \ 381 + FORCE 382 + $(call if_changed,test_fortify) 383 + 384 + quiet_cmd_gen_fortify_log = GEN $@ 385 + cmd_gen_fortify_log = cat </dev/null $(filter-out FORCE,$^) 2>/dev/null > $@ || true 386 + 387 + targets += $(TEST_FORTIFY_LOG) 388 + clean-files += $(TEST_FORTIFY_LOG) 389 + $(obj)/$(TEST_FORTIFY_LOG): $(addprefix $(obj)/, $(TEST_FORTIFY_LOGS)) FORCE 390 + $(call if_changed,gen_fortify_log) 391 + 392 + # Fake dependency to trigger the fortify tests. 393 + ifeq ($(CONFIG_FORTIFY_SOURCE),y) 394 + $(obj)/string.o: $(obj)/$(TEST_FORTIFY_LOG) 395 + endif
+5
lib/test_fortify/read_overflow-memchr.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + memchr(small, 0x7A, sizeof(small) + 1) 4 + 5 + #include "test_fortify.h"
+5
lib/test_fortify/read_overflow-memchr_inv.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + memchr_inv(small, 0x7A, sizeof(small) + 1) 4 + 5 + #include "test_fortify.h"
+5
lib/test_fortify/read_overflow-memcmp.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + memcmp(small, large, sizeof(small) + 1) 4 + 5 + #include "test_fortify.h"
+5
lib/test_fortify/read_overflow-memscan.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + memscan(small, 0x7A, sizeof(small) + 1) 4 + 5 + #include "test_fortify.h"
+5
lib/test_fortify/read_overflow2-memcmp.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + memcmp(large, small, sizeof(small) + 1) 4 + 5 + #include "test_fortify.h"
+5
lib/test_fortify/read_overflow2-memcpy.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + memcpy(large, instance.buf, sizeof(large)) 4 + 5 + #include "test_fortify.h"
+5
lib/test_fortify/read_overflow2-memmove.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + memmove(large, instance.buf, sizeof(large)) 4 + 5 + #include "test_fortify.h"
+35
lib/test_fortify/test_fortify.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0-only */ 2 + #include <linux/kernel.h> 3 + #include <linux/printk.h> 4 + #include <linux/slab.h> 5 + #include <linux/string.h> 6 + 7 + void do_fortify_tests(void); 8 + 9 + #define __BUF_SMALL 16 10 + #define __BUF_LARGE 32 11 + struct fortify_object { 12 + int a; 13 + char buf[__BUF_SMALL]; 14 + int c; 15 + }; 16 + 17 + #define LITERAL_SMALL "AAAAAAAAAAAAAAA" 18 + #define LITERAL_LARGE "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" 19 + const char small_src[__BUF_SMALL] = LITERAL_SMALL; 20 + const char large_src[__BUF_LARGE] = LITERAL_LARGE; 21 + 22 + char small[__BUF_SMALL]; 23 + char large[__BUF_LARGE]; 24 + struct fortify_object instance; 25 + size_t size; 26 + 27 + void do_fortify_tests(void) 28 + { 29 + /* Normal initializations. */ 30 + memset(&instance, 0x32, sizeof(instance)); 31 + memset(small, 0xA5, sizeof(small)); 32 + memset(large, 0x5A, sizeof(large)); 33 + 34 + TEST; 35 + }
+5
lib/test_fortify/write_overflow-memcpy.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + memcpy(instance.buf, large_src, sizeof(large_src)) 4 + 5 + #include "test_fortify.h"
+5
lib/test_fortify/write_overflow-memmove.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + memmove(instance.buf, large_src, sizeof(large_src)) 4 + 5 + #include "test_fortify.h"
+5
lib/test_fortify/write_overflow-memset.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + memset(instance.buf, 0x5A, sizeof(large_src)) 4 + 5 + #include "test_fortify.h"
+5
lib/test_fortify/write_overflow-strcpy-lit.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + strcpy(small, LITERAL_LARGE) 4 + 5 + #include "test_fortify.h"
+5
lib/test_fortify/write_overflow-strcpy.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + strcpy(small, large_src) 4 + 5 + #include "test_fortify.h"
+5
lib/test_fortify/write_overflow-strlcpy-src.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + strlcpy(small, large_src, sizeof(small) + 1) 4 + 5 + #include "test_fortify.h"
+5
lib/test_fortify/write_overflow-strlcpy.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + strlcpy(instance.buf, large_src, sizeof(instance.buf) + 1) 4 + 5 + #include "test_fortify.h"
+5
lib/test_fortify/write_overflow-strncpy-src.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + strncpy(small, large_src, sizeof(small) + 1) 4 + 5 + #include "test_fortify.h"
+5
lib/test_fortify/write_overflow-strncpy.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + strncpy(instance.buf, large_src, sizeof(instance.buf) + 1) 4 + 5 + #include "test_fortify.h"
+5
lib/test_fortify/write_overflow-strscpy.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #define TEST \ 3 + strscpy(instance.buf, large_src, sizeof(instance.buf) + 1) 4 + 5 + #include "test_fortify.h"
+62
scripts/test_fortify.sh
··· 1 + #!/bin/sh 2 + # SPDX-License-Identifier: GPL-2.0-only 3 + set -e 4 + 5 + # Argument 1: Source file to build. 6 + IN="$1" 7 + shift 8 + # Extract just the filename for error messages below. 9 + FILE="${IN##*/}" 10 + # Extract the function name for error messages below. 11 + FUNC="${FILE#*-}" 12 + FUNC="${FUNC%%-*}" 13 + FUNC="${FUNC%%.*}" 14 + # Extract the symbol to test for in build/symbol test below. 15 + WANT="__${FILE%%-*}" 16 + 17 + # Argument 2: Where to write the build log. 18 + OUT="$1" 19 + shift 20 + TMP="${OUT}.tmp" 21 + 22 + # Argument 3: Path to "nm" tool. 23 + NM="$1" 24 + shift 25 + 26 + # Remaining arguments are: $(CC) $(c_flags) 27 + 28 + # Clean up temporary file at exit. 29 + __cleanup() { 30 + rm -f "$TMP" 31 + } 32 + trap __cleanup EXIT 33 + 34 + # Function names in warnings are wrapped in backticks under UTF-8 locales. 35 + # Run the commands with LANG=C so that grep output will not change. 36 + export LANG=C 37 + 38 + status= 39 + # Attempt to build a source that is expected to fail with a specific warning. 40 + if "$@" -Werror -c "$IN" -o "$OUT".o 2> "$TMP" ; then 41 + # If the build succeeds, either the test has failed or the 42 + # warning may only happen at link time (Clang). In that case, 43 + # make sure the expected symbol is unresolved in the symbol list. 44 + # If so, FORTIFY is working for this case. 45 + if ! $NM -A "$OUT".o | grep -m1 "\bU ${WANT}$" >>"$TMP" ; then 46 + status="warning: unsafe ${FUNC}() usage lacked '$WANT' symbol in $IN" 47 + fi 48 + else 49 + # If the build failed, check for the warning in the stderr (gcc). 50 + if ! grep -q -m1 "error: call to .\b${WANT}\b." "$TMP" ; then 51 + status="warning: unsafe ${FUNC}() usage lacked '$WANT' warning in $IN" 52 + fi 53 + fi 54 + 55 + if [ -n "$status" ]; then 56 + # Report on failure results, including compilation warnings. 57 + echo "$status" | tee "$OUT" >&2 58 + else 59 + # Report on good results, and save any compilation output to log. 60 + echo "ok: unsafe ${FUNC}() usage correctly detected with '$WANT' in $IN" >"$OUT" 61 + fi 62 + cat "$TMP" >>"$OUT"