Pull hardening updates from Kees Cook: "As is pretty normal for this tree, there are changes all over the place, especially for small fixes, selftest improvements, and improved macro usability. Some header changes ended up landing via this tree as they depended on the string header cleanups. Also, a notable set of changes is the work for the reintroduction of the UBSAN signed integer overflow sanitizer so that we can continue to make improvements on the compiler side to make this sanitizer a more viable future security hardening option. Summary: - string.h and related header cleanups (Tanzir Hasan, Andy Shevchenko) - VMCI memcpy() usage and struct_size() cleanups (Vasiliy Kovalev, Harshit Mogalapalli) - selftests/powerpc: Fix load_unaligned_zeropad build failure (Michael Ellerman) - hardened Kconfig fragment updates (Marco Elver, Lukas Bulwahn) - Handle tail call optimization better in LKDTM (Douglas Anderson) - Use long form types in overflow.h (Andy Shevchenko) - Add flags param to string_get_size() (Andy Shevchenko) - Add Coccinelle script for potential struct_size() use (Jacob Keller) - Fix objtool corner case under KCFI (Josh Poimboeuf) - Drop 13 year old backward compat CAP_SYS_ADMIN check (Jingzi Meng) - Add str_plural() helper (Michal Wajdeczko, Kees Cook) - Ignore relocations in .notes section - Add comments to explain how __is_constexpr() works - Fix m68k stack alignment expectations in stackinit Kunit test - Convert string selftests to KUnit - Add KUnit tests for fortified string functions - Improve reporting during fortified string warnings - Allow non-type arg to type_max() and type_min() - Allow strscpy() to be called with only 2 arguments - Add binary mode to leaking_addresses scanner - Various small cleanups to leaking_addresses scanner - Adding wrapping_*() arithmetic helper - Annotate initial signed integer wrap-around in refcount_t - Add explicit UBSAN section to MAINTAINERS - Fix UBSAN self-test warnings - Simplify UBSAN build via removal of CONFIG_UBSAN_SANITIZE_ALL - Reintroduce UBSAN's signed overflow sanitizer" * tag 'hardening-v6.9-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux: (51 commits) selftests/powerpc: Fix load_unaligned_zeropad build failure string: Convert helpers selftest to KUnit string: Convert selftest to KUnit sh: Fix build with CONFIG_UBSAN=y compiler.h: Explain how __is_constexpr() works overflow: Allow non-type arg to type_max() and type_min() VMCI: Fix possible memcpy() run-time warning in vmci_datagram_invoke_guest_handler() lib/string_helpers: Add flags param to string_get_size() x86, relocs: Ignore relocations in .notes section objtool: Fix UNWIND_HINT_{SAVE,RESTORE} across basic blocks overflow: Use POD in check_shl_overflow() lib: stackinit: Adjust target string to 8 bytes for m68k sparc: vdso: Disable UBSAN instrumentation kernel.h: Move lib/cmdline.c prototypes to string.h leaking_addresses: Provide mechanism to scan binary files leaking_addresses: Ignore input device status lines leaking_addresses: Use File::Temp for /tmp files MAINTAINERS: Update LEAKING_ADDRESSES details fortify: Improve buffer overflow reporting fortify: Add KUnit tests for runtime overflows ...
Showing the changes to scripts (5 of 71 files). Show all
Unified · Split
| @@ -175,8 +175,11 @@ endif · 174 unmodified lines | ||
| 175 | 175 | |
| 176 | 176 | ifeq ($(CONFIG_UBSAN),y) |
| 177 | 177 | _c_flags += $(if $(patsubst n%,, \ |
| 178 | − $(UBSAN_SANITIZE_$(basetarget).o)$(UBSAN_SANITIZE)$(CONFIG_UBSAN_SANITIZE_ALL)), \ | |
| 178 | + $(UBSAN_SANITIZE_$(basetarget).o)$(UBSAN_SANITIZE)y), \ | |
| 179 | 179 | $(CFLAGS_UBSAN)) |
| 180 | +_c_flags += $(if $(patsubst n%,, \ | |
| 181 | + $(UBSAN_SIGNED_WRAP_$(basetarget).o)$(UBSAN_SANITIZE_$(basetarget).o)$(UBSAN_SIGNED_WRAP)$(UBSAN_SANITIZE)y), \ | |
| 182 | + $(CFLAGS_UBSAN_SIGNED_WRAP)) | |
| 180 | 183 | endif |
| 181 | 184 | |
| 182 | 185 | ifeq ($(CONFIG_KCOV),y) |
| @@ -10,6 +10,9 @@ ubsan-cflags-$(CONFIG_UBSAN_DIV_ZERO) += -fsanitize=integer-divide-by-zero · 9 unmodified lines | ||
| 10 | 10 | ubsan-cflags-$(CONFIG_UBSAN_UNREACHABLE) += -fsanitize=unreachable |
| 11 | 11 | ubsan-cflags-$(CONFIG_UBSAN_BOOL) += -fsanitize=bool |
| 12 | 12 | ubsan-cflags-$(CONFIG_UBSAN_ENUM) += -fsanitize=enum |
| 13 | −ubsan-cflags-$(CONFIG_UBSAN_TRAP) += -fsanitize-undefined-trap-on-error | |
| 13 | +ubsan-cflags-$(CONFIG_UBSAN_TRAP) += $(call cc-option,-fsanitize-trap=undefined,-fsanitize-undefined-trap-on-error) | |
| 14 | 14 | |
| 15 | 15 | export CFLAGS_UBSAN := $(ubsan-cflags-y) |
| 16 | + | |
| 17 | +ubsan-signed-wrap-cflags-$(CONFIG_UBSAN_SIGNED_WRAP) += -fsanitize=signed-integer-overflow | |
| 18 | +export CFLAGS_UBSAN_SIGNED_WRAP := $(ubsan-signed-wrap-cflags-y) | |
| @@ -0,0 +1,41 @@ | ||
| 1 | +// SPDX-License-Identifier: GPL-2.0-only | |
| 2 | +/// Find places to use string_choices.h's various helpers. | |
| 3 | +// | |
| 4 | +// Confidence: Medium | |
| 5 | +// Options: --no-includes --include-headers | |
| 6 | +virtual patch | |
| 7 | +virtual context | |
| 8 | +virtual report | |
| 9 | + | |
| 10 | +@str_plural depends on patch@ | |
| 11 | +expression E; | |
| 12 | +@@ | |
| 13 | +( | |
| 14 | +- ((E == 1) ? "" : "s") | |
| 15 | ++ str_plural(E) | |
| 16 | +| | |
| 17 | +- ((E != 1) ? "s" : "") | |
| 18 | ++ str_plural(E) | |
| 19 | +| | |
| 20 | +- ((E > 1) ? "s" : "") | |
| 21 | ++ str_plural(E) | |
| 22 | +) | |
| 23 | + | |
| 24 | +@str_plural_r depends on !patch exists@ | |
| 25 | +expression E; | |
| 26 | +position P; | |
| 27 | +@@ | |
| 28 | +( | |
| 29 | +* ((E@P == 1) ? "" : "s") | |
| 30 | +| | |
| 31 | +* ((E@P != 1) ? "s" : "") | |
| 32 | +| | |
| 33 | +* ((E@P > 1) ? "s" : "") | |
| 34 | +) | |
| 35 | + | |
| 36 | +@script:python depends on report@ | |
| 37 | +p << str_plural_r.P; | |
| 38 | +e << str_plural_r.E; | |
| 39 | +@@ | |
| 40 | + | |
| 41 | +coccilib.report.print_report(p[0], "opportunity for str_plural(%s)" % e) | |
| @@ -0,0 +1,74 @@ | ||
| 1 | +// SPDX-License-Identifier: GPL-2.0-only | |
| 2 | +/// | |
| 3 | +/// Check for code that could use struct_size(). | |
| 4 | +/// | |
| 5 | +// Confidence: Medium | |
| 6 | +// Author: Jacob Keller <jacob.e.keller@intel.com> | |
| 7 | +// Copyright: (C) 2023 Intel Corporation | |
| 8 | +// Options: --no-includes --include-headers | |
| 9 | + | |
| 10 | +virtual patch | |
| 11 | +virtual context | |
| 12 | +virtual org | |
| 13 | +virtual report | |
| 14 | + | |
| 15 | +// the overflow Kunit tests have some code which intentionally does not use | |
| 16 | +// the macros, so we want to ignore this code when reporting potential | |
| 17 | +// issues. | |
| 18 | +@overflow_tests@ | |
| 19 | +identifier f = overflow_size_helpers_test; | |
| 20 | +@@ | |
| 21 | + | |
| 22 | +f | |
| 23 | + | |
| 24 | +//---------------------------------------------------------- | |
| 25 | +// For context mode | |
| 26 | +//---------------------------------------------------------- | |
| 27 | + | |
| 28 | +@depends on !overflow_tests && context@ | |
| 29 | +expression E1, E2; | |
| 30 | +identifier m; | |
| 31 | +@@ | |
| 32 | +( | |
| 33 | +* (sizeof(*E1) + (E2 * sizeof(*E1->m))) | |
| 34 | +) | |
| 35 | + | |
| 36 | +//---------------------------------------------------------- | |
| 37 | +// For patch mode | |
| 38 | +//---------------------------------------------------------- | |
| 39 | + | |
| 40 | +@depends on !overflow_tests && patch@ | |
| 41 | +expression E1, E2; | |
| 42 | +identifier m; | |
| 43 | +@@ | |
| 44 | +( | |
| 45 | +- (sizeof(*E1) + (E2 * sizeof(*E1->m))) | |
| 46 | ++ struct_size(E1, m, E2) | |
| 47 | +) | |
| 48 | + | |
| 49 | +//---------------------------------------------------------- | |
| 50 | +// For org and report mode | |
| 51 | +//---------------------------------------------------------- | |
| 52 | + | |
| 53 | +@r depends on !overflow_tests && (org || report)@ | |
| 54 | +expression E1, E2; | |
| 55 | +identifier m; | |
| 56 | +position p; | |
| 57 | +@@ | |
| 58 | +( | |
| 59 | + (sizeof(*E1)@p + (E2 * sizeof(*E1->m))) | |
| 60 | +) | |
| 61 | + | |
| 62 | +@script:python depends on org@ | |
| 63 | +p << r.p; | |
| 64 | +@@ | |
| 65 | + | |
| 66 | +coccilib.org.print_todo(p[0], "WARNING should use struct_size") | |
| 67 | + | |
| 68 | +@script:python depends on report@ | |
| 69 | +p << r.p; | |
| 70 | +@@ | |
| 71 | + | |
| 72 | +msg="WARNING: Use struct_size" | |
| 73 | +coccilib.report.print_report(p[0], msg) | |
| 74 | + | |
| @@ -23,6 +23,7 @@ use strict; · 22 unmodified lines | ||
| 23 | 23 | use POSIX; |
| 24 | 24 | use File::Basename; |
| 25 | 25 | use File::Spec; |
| 26 | +use File::Temp qw/tempfile/; | |
| 26 | 27 | use Cwd 'abs_path'; |
| 27 | 28 | use Term::ANSIColor qw(:constants); |
| 28 | 29 | use Getopt::Long qw(:config no_auto_abbrev); |
| @@ -51,10 +52,13 @@ my $input_raw = ""; # Read raw results from file instead of scanning. · 22 unmodified lines | ||
| 51 | 52 | my $suppress_dmesg = 0; # Don't show dmesg in output. |
| 52 | 53 | my $squash_by_path = 0; # Summary report grouped by absolute path. |
| 53 | 54 | my $squash_by_filename = 0; # Summary report grouped by filename. |
| 55 | +my $kallsyms_file = ""; # Kernel symbols file. | |
| 54 | 56 | my $kernel_config_file = ""; # Kernel configuration file. |
| 55 | 57 | my $opt_32bit = 0; # Scan 32-bit kernel. |
| 56 | 58 | my $page_offset_32bit = 0; # Page offset for 32-bit kernel. |
| 57 | 59 | |
| 60 | +my @kallsyms = (); | |
| 61 | + | |
| 58 | 62 | # Skip these absolute paths. |
| 59 | 63 | my @skip_abs = ( |
| 60 | 64 | '/proc/kmsg', |
| @@ -95,6 +99,8 @@ Options: · 34 unmodified lines | ||
| 95 | 99 | --squash-by-path Show one result per unique path. |
| 96 | 100 | --squash-by-filename Show one result per unique filename. |
| 97 | 101 | --kernel-config-file=<file> Kernel configuration file (e.g /boot/config) |
| 102 | + --kallsyms=<file> Read kernel symbol addresses from file (for | |
| 103 | + scanning binary files). | |
| 98 | 104 | --32-bit Scan 32-bit kernel. |
| 99 | 105 | --page-offset-32-bit=o Page offset (for 32-bit kernel 0xABCD1234). |
| 100 | 106 | -d, --debug Display debugging output. |
| @@ -115,6 +121,7 @@ GetOptions( · 14 unmodified lines | ||
| 115 | 121 | 'squash-by-path' => \$squash_by_path, |
| 116 | 122 | 'squash-by-filename' => \$squash_by_filename, |
| 117 | 123 | 'raw' => \$raw, |
| 124 | + 'kallsyms=s' => \$kallsyms_file, | |
| 118 | 125 | 'kernel-config-file=s' => \$kernel_config_file, |
| 119 | 126 | '32-bit' => \$opt_32bit, |
| 120 | 127 | 'page-offset-32-bit=o' => \$page_offset_32bit, |
| @@ -155,6 +162,25 @@ if ($output_raw) { · 34 unmodified lines | ||
| 155 | 162 | select $fh; |
| 156 | 163 | } |
| 157 | 164 | |
| 165 | +if ($kallsyms_file) { | |
| 166 | + open my $fh, '<', $kallsyms_file or die "$0: $kallsyms_file: $!\n"; | |
| 167 | + while (<$fh>) { | |
| 168 | + chomp; | |
| 169 | + my @entry = split / /, $_; | |
| 170 | + my $addr_text = $entry[0]; | |
| 171 | + if ($addr_text !~ /^0/) { | |
| 172 | + # TODO: Why is hex() so impossibly slow? | |
| 173 | + my $addr = hex($addr_text); | |
| 174 | + my $symbol = $entry[2]; | |
| 175 | + # Only keep kernel text addresses. | |
| 176 | + my $long = pack("J", $addr); | |
| 177 | + my $entry = [$long, $symbol]; | |
| 178 | + push @kallsyms, $entry; | |
| 179 | + } | |
| 180 | + } | |
| 181 | + close $fh; | |
| 182 | +} | |
| 183 | + | |
| 158 | 184 | parse_dmesg(); |
| 159 | 185 | walk(@DIRS); |
| 160 | 186 | |
| @@ -221,6 +247,7 @@ sub get_kernel_config_option · 60 unmodified lines | ||
| 221 | 247 | { |
| 222 | 248 | my ($option) = @_; |
| 223 | 249 | my $value = ""; |
| 250 | + my $tmp_fh; | |
| 224 | 251 | my $tmp_file = ""; |
| 225 | 252 | my @config_files; |
| 226 | 253 | |
| @@ -228,7 +255,8 @@ sub get_kernel_config_option · 1 unmodified line | ||
| 228 | 255 | if ($kernel_config_file ne "") { |
| 229 | 256 | @config_files = ($kernel_config_file); |
| 230 | 257 | } elsif (-R "/proc/config.gz") { |
| 231 | − my $tmp_file = "/tmp/tmpkconf"; | |
| 258 | + ($tmp_fh, $tmp_file) = tempfile("config.gz-XXXXXX", | |
| 259 | + UNLINK => 1); | |
| 232 | 260 | |
| 233 | 261 | if (system("gunzip < /proc/config.gz > $tmp_file")) { |
| 234 | 262 | dprint("system(gunzip < /proc/config.gz) failed\n"); |
| @@ -250,10 +278,6 @@ sub get_kernel_config_option · 15 unmodified lines | ||
| 250 | 278 | } |
| 251 | 279 | } |
| 252 | 280 | |
| 253 | − if ($tmp_file ne "") { | |
| 254 | − system("rm -f $tmp_file"); | |
| 255 | − } | |
| 256 | − | |
| 257 | 281 | return $value; |
| 258 | 282 | } |
| 259 | 283 | |
| @@ -285,9 +309,10 @@ sub is_false_positive · 25 unmodified lines | ||
| 285 | 309 | return is_false_positive_32bit($match); |
| 286 | 310 | } |
| 287 | 311 | |
| 288 | − # 64 bit false positives. | |
| 289 | − | |
| 290 | − if ($match =~ '\b(0x)?(f|F){16}\b' or | |
| 312 | + # Ignore 64 bit false positives: | |
| 313 | + # 0xfffffffffffffff[0-f] | |
| 314 | + # 0x0000000000000000 | |
| 315 | + if ($match =~ '\b(0x)?(f|F){15}[0-9a-f]\b' or | |
| 291 | 316 | $match =~ '\b(0x)?0{16}\b') { |
| 292 | 317 | return 1; |
| 293 | 318 | } |
| @@ -304,7 +329,7 @@ sub is_false_positive_32bit · 10 unmodified lines | ||
| 304 | 329 | my ($match) = @_; |
| 305 | 330 | state $page_offset = get_page_offset(); |
| 306 | 331 | |
| 307 | − if ($match =~ '\b(0x)?(f|F){8}\b') { | |
| 332 | + if ($match =~ '\b(0x)?(f|F){7}[0-9a-f]\b') { | |
| 308 | 333 | return 1; |
| 309 | 334 | } |
| 310 | 335 | |
| @@ -347,18 +372,23 @@ sub is_in_vsyscall_memory_region · 36 unmodified lines | ||
| 347 | 372 | # True if argument potentially contains a kernel address. |
| 348 | 373 | sub may_leak_address |
| 349 | 374 | { |
| 350 | − my ($line) = @_; | |
| 375 | + my ($path, $line) = @_; | |
| 351 | 376 | my $address_re; |
| 352 | 377 | |
| 353 | − # Signal masks. | |
| 378 | + # Ignore Signal masks. | |
| 354 | 379 | if ($line =~ '^SigBlk:' or |
| 355 | 380 | $line =~ '^SigIgn:' or |
| 356 | 381 | $line =~ '^SigCgt:') { |
| 357 | 382 | return 0; |
| 358 | 383 | } |
| 359 | 384 | |
| 360 | − if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or | |
| 361 | − $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') { | |
| 385 | + # Ignore input device reporting. | |
| 386 | + # /proc/bus/input/devices: B: KEY=402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe | |
| 387 | + # /sys/devices/platform/i8042/serio0/input/input1/uevent: KEY=402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe | |
| 388 | + # /sys/devices/platform/i8042/serio0/input/input1/capabilities/key: 402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe | |
| 389 | + if ($line =~ '\bKEY=[[:xdigit:]]{9,14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or | |
| 390 | + ($path =~ '\bkey$' and | |
| 391 | + $line =~ '\b[[:xdigit:]]{9,14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b')) { | |
| 362 | 392 | return 0; |
| 363 | 393 | } |
| 364 | 394 | |
| @@ -401,7 +431,7 @@ sub parse_dmesg · 36 unmodified lines | ||
| 401 | 431 | { |
| 402 | 432 | open my $cmd, '-|', 'dmesg'; |
| 403 | 433 | while (<$cmd>) { |
| 404 | − if (may_leak_address($_)) { | |
| 434 | + if (may_leak_address("dmesg", $_)) { | |
| 405 | 435 | print 'dmesg: ' . $_; |
| 406 | 436 | } |
| 407 | 437 | } |
| @@ -442,6 +472,25 @@ sub timed_parse_file · 34 unmodified lines | ||
| 442 | 472 | } |
| 443 | 473 | } |
| 444 | 474 | |
| 475 | +sub parse_binary | |
| 476 | +{ | |
| 477 | + my ($file) = @_; | |
| 478 | + | |
| 479 | + open my $fh, "<:raw", $file or return; | |
| 480 | + local $/ = undef; | |
| 481 | + my $bytes = <$fh>; | |
| 482 | + close $fh; | |
| 483 | + | |
| 484 | + foreach my $entry (@kallsyms) { | |
| 485 | + my $addr = $entry->[0]; | |
| 486 | + my $symbol = $entry->[1]; | |
| 487 | + my $offset = index($bytes, $addr); | |
| 488 | + if ($offset != -1) { | |
| 489 | + printf("$file: $symbol @ $offset\n"); | |
| 490 | + } | |
| 491 | + } | |
| 492 | +} | |
| 493 | + | |
| 445 | 494 | sub parse_file |
| 446 | 495 | { |
| 447 | 496 | my ($file) = @_; |
| @@ -451,13 +500,22 @@ sub parse_file · 3 unmodified lines | ||
| 451 | 500 | } |
| 452 | 501 | |
| 453 | 502 | if (! -T $file) { |
| 503 | + if ($file =~ m|^/sys/kernel/btf/| or | |
| 504 | + $file =~ m|^/sys/devices/pci| or | |
| 505 | + $file =~ m|^/sys/firmware/efi/efivars/| or | |
| 506 | + $file =~ m|^/proc/bus/pci/|) { | |
| 507 | + return; | |
| 508 | + } | |
| 509 | + if (scalar @kallsyms > 0) { | |
| 510 | + parse_binary($file); | |
| 511 | + } | |
| 454 | 512 | return; |
| 455 | 513 | } |
| 456 | 514 | |
| 457 | 515 | open my $fh, "<", $file or return; |
| 458 | 516 | while ( <$fh> ) { |
| 459 | 517 | chomp; |
| 460 | − if (may_leak_address($_)) { | |
| 518 | + if (may_leak_address($file, $_)) { | |
| 461 | 519 | printf("$file: $_\n"); |
| 462 | 520 | } |
| 463 | 521 | } |
| @@ -469,7 +527,7 @@ sub check_path_for_leaks · 5 unmodified lines | ||
| 469 | 527 | { |
| 470 | 528 | my ($path) = @_; |
| 471 | 529 | |
| 472 | − if (may_leak_address($path)) { | |
| 530 | + if (may_leak_address($path, $path)) { | |
| 473 | 531 | printf("Path name may contain address: $path\n"); |
| 474 | 532 | } |
| 475 | 533 | } |