1136fc5c4STobin C. Harding#!/usr/bin/env perl 24f19048fSThomas Gleixner# SPDX-License-Identifier: GPL-2.0-only 3136fc5c4STobin C. Harding# 4136fc5c4STobin C. Harding# (c) 2017 Tobin C. Harding <me@tobin.cc> 5136fc5c4STobin C. Harding# 61410fe4eSTobin C. Harding# leaking_addresses.pl: Scan the kernel for potential leaking addresses. 7136fc5c4STobin C. Harding# - Scans dmesg output. 8136fc5c4STobin C. Harding# - Walks directory tree and parses each file (for each directory in @DIRS). 9136fc5c4STobin C. Harding# 10136fc5c4STobin C. Harding# Use --debug to output path before parsing, this is useful to find files that 11136fc5c4STobin C. Harding# cause the script to choke. 12136fc5c4STobin C. Harding 13472c9e10STobin C. Harding# 14472c9e10STobin C. Harding# When the system is idle it is likely that most files under /proc/PID will be 15472c9e10STobin C. Harding# identical for various processes. Scanning _all_ the PIDs under /proc is 16472c9e10STobin C. Harding# unnecessary and implies that we are thoroughly scanning /proc. This is _not_ 17472c9e10STobin C. Harding# the case because there may be ways userspace can trigger creation of /proc 18472c9e10STobin C. Harding# files that leak addresses but were not present during a scan. For these two 19472c9e10STobin C. Harding# reasons we exclude all PID directories under /proc except '1/' 20472c9e10STobin C. Harding 21136fc5c4STobin C. Hardinguse warnings; 22136fc5c4STobin C. Hardinguse strict; 23136fc5c4STobin C. Hardinguse POSIX; 24136fc5c4STobin C. Hardinguse File::Basename; 25136fc5c4STobin C. Hardinguse File::Spec; 26136fc5c4STobin C. Hardinguse Cwd 'abs_path'; 27136fc5c4STobin C. Hardinguse Term::ANSIColor qw(:constants); 28136fc5c4STobin C. Hardinguse Getopt::Long qw(:config no_auto_abbrev); 2962139c12STobin C. Hardinguse Config; 3087e37588STobin C. Hardinguse bigint qw/hex/; 312f042c93STobin C. Hardinguse feature 'state'; 32136fc5c4STobin C. Harding 33136fc5c4STobin C. Hardingmy $P = $0; 34136fc5c4STobin C. Harding 35136fc5c4STobin C. Harding# Directories to scan. 36136fc5c4STobin C. Hardingmy @DIRS = ('/proc', '/sys'); 37136fc5c4STobin C. Harding 38dd98c252STobin C. Harding# Timer for parsing each file, in seconds. 39dd98c252STobin C. Hardingmy $TIMEOUT = 10; 40dd98c252STobin C. Harding 411410fe4eSTobin C. Harding# Kernel addresses vary by architecture. We can only auto-detect the following 421410fe4eSTobin C. Harding# architectures (using `uname -m`). (flag --32-bit overrides auto-detection.) 431410fe4eSTobin C. Hardingmy @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64', 'x86'); 4462139c12STobin C. Harding 45136fc5c4STobin C. Harding# Command line options. 46136fc5c4STobin C. Hardingmy $help = 0; 47136fc5c4STobin C. Hardingmy $debug = 0; 48d09bd8daSTobin C. Hardingmy $raw = 0; 49d09bd8daSTobin C. Hardingmy $output_raw = ""; # Write raw results to file. 50d09bd8daSTobin C. Hardingmy $input_raw = ""; # Read raw results from file instead of scanning. 51d09bd8daSTobin C. Hardingmy $suppress_dmesg = 0; # Don't show dmesg in output. 52d09bd8daSTobin C. Hardingmy $squash_by_path = 0; # Summary report grouped by absolute path. 53d09bd8daSTobin C. Hardingmy $squash_by_filename = 0; # Summary report grouped by filename. 54f9d2a42dSTobin C. Hardingmy $kernel_config_file = ""; # Kernel configuration file. 551410fe4eSTobin C. Hardingmy $opt_32bit = 0; # Scan 32-bit kernel. 561410fe4eSTobin C. Hardingmy $page_offset_32bit = 0; # Page offset for 32-bit kernel. 57136fc5c4STobin C. Harding 58b401f56fSTobin C. Harding# Skip these absolute paths. 59b401f56fSTobin C. Hardingmy @skip_abs = ( 60b401f56fSTobin C. Harding '/proc/kmsg', 611c1e3be0STobin C. Harding '/proc/device-tree', 622ad74293STobin C. Harding '/proc/1/syscall', 63b401f56fSTobin C. Harding '/sys/firmware/devicetree', 64136fc5c4STobin C. Harding '/sys/kernel/debug/tracing/trace_pipe', 65136fc5c4STobin C. Harding '/sys/kernel/security/apparmor/revision'); 66136fc5c4STobin C. Harding 67b401f56fSTobin C. Harding# Skip these under any subdirectory. 68b401f56fSTobin C. Hardingmy @skip_any = ( 69136fc5c4STobin C. Harding 'pagemap', 70136fc5c4STobin C. Harding 'events', 71136fc5c4STobin C. Harding 'access', 72136fc5c4STobin C. Harding 'registers', 73136fc5c4STobin C. Harding 'snapshot_raw', 74136fc5c4STobin C. Harding 'trace_pipe_raw', 75136fc5c4STobin C. Harding 'ptmx', 76b401f56fSTobin C. Harding 'trace_pipe', 77136fc5c4STobin C. Harding 'fd', 78b401f56fSTobin C. Harding 'usbmon'); 79136fc5c4STobin C. Harding 80136fc5c4STobin C. Hardingsub help 81136fc5c4STobin C. Harding{ 82136fc5c4STobin C. Harding my ($exitcode) = @_; 83136fc5c4STobin C. Harding 84136fc5c4STobin C. Harding print << "EOM"; 85d09bd8daSTobin C. Harding 86136fc5c4STobin C. HardingUsage: $P [OPTIONS] 87136fc5c4STobin C. Harding 88136fc5c4STobin C. HardingOptions: 89136fc5c4STobin C. Harding 90d09bd8daSTobin C. Harding -o, --output-raw=<file> Save results for future processing. 91d09bd8daSTobin C. Harding -i, --input-raw=<file> Read results from file instead of scanning. 92d09bd8daSTobin C. Harding --raw Show raw results (default). 93d09bd8daSTobin C. Harding --suppress-dmesg Do not show dmesg results. 94d09bd8daSTobin C. Harding --squash-by-path Show one result per unique path. 95d09bd8daSTobin C. Harding --squash-by-filename Show one result per unique filename. 96f9d2a42dSTobin C. Harding --kernel-config-file=<file> Kernel configuration file (e.g /boot/config) 971410fe4eSTobin C. Harding --32-bit Scan 32-bit kernel. 981410fe4eSTobin C. Harding --page-offset-32-bit=o Page offset (for 32-bit kernel 0xABCD1234). 99136fc5c4STobin C. Harding -d, --debug Display debugging output. 1009ac060a7STobin C. Harding -h, --help Display this help and exit. 101136fc5c4STobin C. Harding 1021410fe4eSTobin C. HardingScans the running kernel for potential leaking addresses. 103136fc5c4STobin C. Harding 104136fc5c4STobin C. HardingEOM 105136fc5c4STobin C. Harding exit($exitcode); 106136fc5c4STobin C. Harding} 107136fc5c4STobin C. Harding 108136fc5c4STobin C. HardingGetOptions( 109136fc5c4STobin C. Harding 'd|debug' => \$debug, 110136fc5c4STobin C. Harding 'h|help' => \$help, 111d09bd8daSTobin C. Harding 'o|output-raw=s' => \$output_raw, 112d09bd8daSTobin C. Harding 'i|input-raw=s' => \$input_raw, 113d09bd8daSTobin C. Harding 'suppress-dmesg' => \$suppress_dmesg, 114d09bd8daSTobin C. Harding 'squash-by-path' => \$squash_by_path, 115d09bd8daSTobin C. Harding 'squash-by-filename' => \$squash_by_filename, 116d09bd8daSTobin C. Harding 'raw' => \$raw, 117f9d2a42dSTobin C. Harding 'kernel-config-file=s' => \$kernel_config_file, 1181410fe4eSTobin C. Harding '32-bit' => \$opt_32bit, 1191410fe4eSTobin C. Harding 'page-offset-32-bit=o' => \$page_offset_32bit, 120136fc5c4STobin C. Harding) or help(1); 121136fc5c4STobin C. Harding 122136fc5c4STobin C. Hardinghelp(0) if ($help); 123136fc5c4STobin C. Harding 124d09bd8daSTobin C. Hardingif ($input_raw) { 125d09bd8daSTobin C. Harding format_output($input_raw); 126d09bd8daSTobin C. Harding exit(0); 127d09bd8daSTobin C. Harding} 128d09bd8daSTobin C. Harding 129d09bd8daSTobin C. Hardingif (!$input_raw and ($squash_by_path or $squash_by_filename)) { 130d09bd8daSTobin C. Harding printf "\nSummary reporting only available with --input-raw=<file>\n"; 131d09bd8daSTobin C. Harding printf "(First run scan with --output-raw=<file>.)\n"; 132d09bd8daSTobin C. Harding exit(128); 133d09bd8daSTobin C. Harding} 134d09bd8daSTobin C. Harding 1351410fe4eSTobin C. Hardingif (!(is_supported_architecture() or $opt_32bit or $page_offset_32bit)) { 13662139c12STobin C. Harding printf "\nScript does not support your architecture, sorry.\n"; 13762139c12STobin C. Harding printf "\nCurrently we support: \n\n"; 13862139c12STobin C. Harding foreach(@SUPPORTED_ARCHITECTURES) { 13962139c12STobin C. Harding printf "\t%s\n", $_; 14062139c12STobin C. Harding } 1416efb7458STobin C. Harding printf("\n"); 14262139c12STobin C. Harding 1431410fe4eSTobin C. Harding printf("If you are running a 32-bit architecture you may use:\n"); 1441410fe4eSTobin C. Harding printf("\n\t--32-bit or --page-offset-32-bit=<page offset>\n\n"); 1451410fe4eSTobin C. Harding 1466efb7458STobin C. Harding my $archname = `uname -m`; 1476efb7458STobin C. Harding printf("Machine hardware name (`uname -m`): %s\n", $archname); 14862139c12STobin C. Harding 14962139c12STobin C. Harding exit(129); 15062139c12STobin C. Harding} 15162139c12STobin C. Harding 152d09bd8daSTobin C. Hardingif ($output_raw) { 153d09bd8daSTobin C. Harding open my $fh, '>', $output_raw or die "$0: $output_raw: $!\n"; 154d09bd8daSTobin C. Harding select $fh; 155d09bd8daSTobin C. Harding} 156d09bd8daSTobin C. Harding 157136fc5c4STobin C. Hardingparse_dmesg(); 158136fc5c4STobin C. Hardingwalk(@DIRS); 159136fc5c4STobin C. Harding 160136fc5c4STobin C. Hardingexit 0; 161136fc5c4STobin C. Harding 162136fc5c4STobin C. Hardingsub dprint 163136fc5c4STobin C. Harding{ 164136fc5c4STobin C. Harding printf(STDERR @_) if $debug; 165136fc5c4STobin C. Harding} 166136fc5c4STobin C. Harding 16762139c12STobin C. Hardingsub is_supported_architecture 16862139c12STobin C. Harding{ 1691410fe4eSTobin C. Harding return (is_x86_64() or is_ppc64() or is_ix86_32()); 1701410fe4eSTobin C. Harding} 1711410fe4eSTobin C. Harding 1721410fe4eSTobin C. Hardingsub is_32bit 1731410fe4eSTobin C. Harding{ 1741410fe4eSTobin C. Harding # Allow --32-bit or --page-offset-32-bit to override 1751410fe4eSTobin C. Harding if ($opt_32bit or $page_offset_32bit) { 1761410fe4eSTobin C. Harding return 1; 1771410fe4eSTobin C. Harding } 1781410fe4eSTobin C. Harding 1791410fe4eSTobin C. Harding return is_ix86_32(); 1801410fe4eSTobin C. Harding} 1811410fe4eSTobin C. Harding 1821410fe4eSTobin C. Hardingsub is_ix86_32 1831410fe4eSTobin C. Harding{ 1845e4bac34STobin C. Harding state $arch = `uname -m`; 1851410fe4eSTobin C. Harding 1861410fe4eSTobin C. Harding chomp $arch; 1871410fe4eSTobin C. Harding if ($arch =~ m/i[3456]86/) { 1881410fe4eSTobin C. Harding return 1; 1891410fe4eSTobin C. Harding } 1901410fe4eSTobin C. Harding return 0; 19162139c12STobin C. Harding} 19262139c12STobin C. Harding 1935eb0da05STobin C. Hardingsub is_arch 19462139c12STobin C. Harding{ 1955eb0da05STobin C. Harding my ($desc) = @_; 1965eb0da05STobin C. Harding my $arch = `uname -m`; 19762139c12STobin C. Harding 1985eb0da05STobin C. Harding chomp $arch; 1995eb0da05STobin C. Harding if ($arch eq $desc) { 20062139c12STobin C. Harding return 1; 20162139c12STobin C. Harding } 20262139c12STobin C. Harding return 0; 20362139c12STobin C. Harding} 20462139c12STobin C. Harding 2055eb0da05STobin C. Hardingsub is_x86_64 2065eb0da05STobin C. Harding{ 2075e4bac34STobin C. Harding state $is = is_arch('x86_64'); 2085e4bac34STobin C. Harding return $is; 2095eb0da05STobin C. Harding} 2105eb0da05STobin C. Harding 21162139c12STobin C. Hardingsub is_ppc64 21262139c12STobin C. Harding{ 2135e4bac34STobin C. Harding state $is = is_arch('ppc64'); 2145e4bac34STobin C. Harding return $is; 21562139c12STobin C. Harding} 21662139c12STobin C. Harding 217f9d2a42dSTobin C. Harding# Gets config option value from kernel config file. 218f9d2a42dSTobin C. Harding# Returns "" on error or if config option not found. 219f9d2a42dSTobin C. Hardingsub get_kernel_config_option 220f9d2a42dSTobin C. Harding{ 221f9d2a42dSTobin C. Harding my ($option) = @_; 222f9d2a42dSTobin C. Harding my $value = ""; 223f9d2a42dSTobin C. Harding my $tmp_file = ""; 224f9d2a42dSTobin C. Harding my @config_files; 225f9d2a42dSTobin C. Harding 226f9d2a42dSTobin C. Harding # Allow --kernel-config-file to override. 227f9d2a42dSTobin C. Harding if ($kernel_config_file ne "") { 228f9d2a42dSTobin C. Harding @config_files = ($kernel_config_file); 229f9d2a42dSTobin C. Harding } elsif (-R "/proc/config.gz") { 230f9d2a42dSTobin C. Harding my $tmp_file = "/tmp/tmpkconf"; 231f9d2a42dSTobin C. Harding 232f9d2a42dSTobin C. Harding if (system("gunzip < /proc/config.gz > $tmp_file")) { 2330f299433STobin C. Harding dprint("system(gunzip < /proc/config.gz) failed\n"); 234f9d2a42dSTobin C. Harding return ""; 235f9d2a42dSTobin C. Harding } else { 236f9d2a42dSTobin C. Harding @config_files = ($tmp_file); 237f9d2a42dSTobin C. Harding } 238f9d2a42dSTobin C. Harding } else { 239f9d2a42dSTobin C. Harding my $file = '/boot/config-' . `uname -r`; 240f9d2a42dSTobin C. Harding chomp $file; 241f9d2a42dSTobin C. Harding @config_files = ($file, '/boot/config'); 242f9d2a42dSTobin C. Harding } 243f9d2a42dSTobin C. Harding 244f9d2a42dSTobin C. Harding foreach my $file (@config_files) { 2450f299433STobin C. Harding dprint("parsing config file: $file\n"); 246f9d2a42dSTobin C. Harding $value = option_from_file($option, $file); 247f9d2a42dSTobin C. Harding if ($value ne "") { 248f9d2a42dSTobin C. Harding last; 249f9d2a42dSTobin C. Harding } 250f9d2a42dSTobin C. Harding } 251f9d2a42dSTobin C. Harding 252f9d2a42dSTobin C. Harding if ($tmp_file ne "") { 253f9d2a42dSTobin C. Harding system("rm -f $tmp_file"); 254f9d2a42dSTobin C. Harding } 255f9d2a42dSTobin C. Harding 256f9d2a42dSTobin C. Harding return $value; 257f9d2a42dSTobin C. Harding} 258f9d2a42dSTobin C. Harding 259f9d2a42dSTobin C. Harding# Parses $file and returns kernel configuration option value. 260f9d2a42dSTobin C. Hardingsub option_from_file 261f9d2a42dSTobin C. Harding{ 262f9d2a42dSTobin C. Harding my ($option, $file) = @_; 263f9d2a42dSTobin C. Harding my $str = ""; 264f9d2a42dSTobin C. Harding my $val = ""; 265f9d2a42dSTobin C. Harding 266f9d2a42dSTobin C. Harding open(my $fh, "<", $file) or return ""; 267f9d2a42dSTobin C. Harding while (my $line = <$fh> ) { 268f9d2a42dSTobin C. Harding if ($line =~ /^$option/) { 269f9d2a42dSTobin C. Harding ($str, $val) = split /=/, $line; 270f9d2a42dSTobin C. Harding chomp $val; 271f9d2a42dSTobin C. Harding last; 272f9d2a42dSTobin C. Harding } 273f9d2a42dSTobin C. Harding } 274f9d2a42dSTobin C. Harding 275f9d2a42dSTobin C. Harding close $fh; 276f9d2a42dSTobin C. Harding return $val; 277f9d2a42dSTobin C. Harding} 278f9d2a42dSTobin C. Harding 279136fc5c4STobin C. Hardingsub is_false_positive 280136fc5c4STobin C. Harding{ 281136fc5c4STobin C. Harding my ($match) = @_; 282136fc5c4STobin C. Harding 2831410fe4eSTobin C. Harding if (is_32bit()) { 2841410fe4eSTobin C. Harding return is_false_positive_32bit($match); 2851410fe4eSTobin C. Harding } 2861410fe4eSTobin C. Harding 2871410fe4eSTobin C. Harding # 64 bit false positives. 2881410fe4eSTobin C. Harding 289136fc5c4STobin C. Harding if ($match =~ '\b(0x)?(f|F){16}\b' or 290136fc5c4STobin C. Harding $match =~ '\b(0x)?0{16}\b') { 291136fc5c4STobin C. Harding return 1; 292136fc5c4STobin C. Harding } 293136fc5c4STobin C. Harding 29487e37588STobin C. Harding if (is_x86_64() and is_in_vsyscall_memory_region($match)) { 295136fc5c4STobin C. Harding return 1; 296136fc5c4STobin C. Harding } 297136fc5c4STobin C. Harding 298136fc5c4STobin C. Harding return 0; 299136fc5c4STobin C. Harding} 300136fc5c4STobin C. Harding 3011410fe4eSTobin C. Hardingsub is_false_positive_32bit 3021410fe4eSTobin C. Harding{ 3031410fe4eSTobin C. Harding my ($match) = @_; 3041410fe4eSTobin C. Harding state $page_offset = get_page_offset(); 3051410fe4eSTobin C. Harding 3061410fe4eSTobin C. Harding if ($match =~ '\b(0x)?(f|F){8}\b') { 3071410fe4eSTobin C. Harding return 1; 3081410fe4eSTobin C. Harding } 3091410fe4eSTobin C. Harding 3101410fe4eSTobin C. Harding if (hex($match) < $page_offset) { 3111410fe4eSTobin C. Harding return 1; 3121410fe4eSTobin C. Harding } 3131410fe4eSTobin C. Harding 3141410fe4eSTobin C. Harding return 0; 3151410fe4eSTobin C. Harding} 3161410fe4eSTobin C. Harding 3171410fe4eSTobin C. Harding# returns integer value 3181410fe4eSTobin C. Hardingsub get_page_offset 3191410fe4eSTobin C. Harding{ 3201410fe4eSTobin C. Harding my $page_offset; 3211410fe4eSTobin C. Harding my $default_offset = 0xc0000000; 3221410fe4eSTobin C. Harding 3231410fe4eSTobin C. Harding # Allow --page-offset-32bit to override. 3241410fe4eSTobin C. Harding if ($page_offset_32bit != 0) { 3251410fe4eSTobin C. Harding return $page_offset_32bit; 3261410fe4eSTobin C. Harding } 3271410fe4eSTobin C. Harding 3281410fe4eSTobin C. Harding $page_offset = get_kernel_config_option('CONFIG_PAGE_OFFSET'); 3291410fe4eSTobin C. Harding if (!$page_offset) { 3301410fe4eSTobin C. Harding return $default_offset; 3311410fe4eSTobin C. Harding } 3321410fe4eSTobin C. Harding return $page_offset; 3331410fe4eSTobin C. Harding} 3341410fe4eSTobin C. Harding 33587e37588STobin C. Hardingsub is_in_vsyscall_memory_region 33687e37588STobin C. Harding{ 33787e37588STobin C. Harding my ($match) = @_; 33887e37588STobin C. Harding 33987e37588STobin C. Harding my $hex = hex($match); 34087e37588STobin C. Harding my $region_min = hex("0xffffffffff600000"); 34187e37588STobin C. Harding my $region_max = hex("0xffffffffff601000"); 34287e37588STobin C. Harding 34387e37588STobin C. Harding return ($hex >= $region_min and $hex <= $region_max); 34487e37588STobin C. Harding} 34587e37588STobin C. Harding 346136fc5c4STobin C. Harding# True if argument potentially contains a kernel address. 347136fc5c4STobin C. Hardingsub may_leak_address 348136fc5c4STobin C. Harding{ 349136fc5c4STobin C. Harding my ($line) = @_; 35062139c12STobin C. Harding my $address_re; 351136fc5c4STobin C. Harding 352136fc5c4STobin C. Harding # Signal masks. 353136fc5c4STobin C. Harding if ($line =~ '^SigBlk:' or 354a11949ecSTobin C. Harding $line =~ '^SigIgn:' or 355136fc5c4STobin C. Harding $line =~ '^SigCgt:') { 356136fc5c4STobin C. Harding return 0; 357136fc5c4STobin C. Harding } 358136fc5c4STobin C. Harding 359136fc5c4STobin C. Harding if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or 360136fc5c4STobin C. Harding $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') { 361136fc5c4STobin C. Harding return 0; 362136fc5c4STobin C. Harding } 363136fc5c4STobin C. Harding 3642f042c93STobin C. Harding $address_re = get_address_re(); 3652306a677STobin C. Harding while ($line =~ /($address_re)/g) { 366136fc5c4STobin C. Harding if (!is_false_positive($1)) { 367136fc5c4STobin C. Harding return 1; 368136fc5c4STobin C. Harding } 369136fc5c4STobin C. Harding } 370136fc5c4STobin C. Harding 371136fc5c4STobin C. Harding return 0; 372136fc5c4STobin C. Harding} 373136fc5c4STobin C. Harding 3742f042c93STobin C. Hardingsub get_address_re 3752f042c93STobin C. Harding{ 3761410fe4eSTobin C. Harding if (is_ppc64()) { 3772f042c93STobin C. Harding return '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b'; 3781410fe4eSTobin C. Harding } elsif (is_32bit()) { 3791410fe4eSTobin C. Harding return '\b(0x)?[[:xdigit:]]{8}\b'; 3802f042c93STobin C. Harding } 3811410fe4eSTobin C. Harding 3821410fe4eSTobin C. Harding return get_x86_64_re(); 3832f042c93STobin C. Harding} 3842f042c93STobin C. Harding 3852f042c93STobin C. Hardingsub get_x86_64_re 3862f042c93STobin C. Harding{ 3872f042c93STobin C. Harding # We handle page table levels but only if explicitly configured using 3882f042c93STobin C. Harding # CONFIG_PGTABLE_LEVELS. If config file parsing fails or config option 3892f042c93STobin C. Harding # is not found we default to using address regular expression suitable 3902f042c93STobin C. Harding # for 4 page table levels. 3912f042c93STobin C. Harding state $ptl = get_kernel_config_option('CONFIG_PGTABLE_LEVELS'); 3922f042c93STobin C. Harding 3932f042c93STobin C. Harding if ($ptl == 5) { 3942f042c93STobin C. Harding return '\b(0x)?ff[[:xdigit:]]{14}\b'; 3952f042c93STobin C. Harding } 3962f042c93STobin C. Harding return '\b(0x)?ffff[[:xdigit:]]{12}\b'; 3972f042c93STobin C. Harding} 3982f042c93STobin C. Harding 399136fc5c4STobin C. Hardingsub parse_dmesg 400136fc5c4STobin C. Harding{ 401136fc5c4STobin C. Harding open my $cmd, '-|', 'dmesg'; 402136fc5c4STobin C. Harding while (<$cmd>) { 403136fc5c4STobin C. Harding if (may_leak_address($_)) { 404136fc5c4STobin C. Harding print 'dmesg: ' . $_; 405136fc5c4STobin C. Harding } 406136fc5c4STobin C. Harding } 407136fc5c4STobin C. Harding close $cmd; 408136fc5c4STobin C. Harding} 409136fc5c4STobin C. Harding 410136fc5c4STobin C. Harding# True if we should skip this path. 411136fc5c4STobin C. Hardingsub skip 412136fc5c4STobin C. Harding{ 413b401f56fSTobin C. Harding my ($path) = @_; 414136fc5c4STobin C. Harding 415b401f56fSTobin C. Harding foreach (@skip_abs) { 416136fc5c4STobin C. Harding return 1 if (/^$path$/); 417136fc5c4STobin C. Harding } 418136fc5c4STobin C. Harding 419136fc5c4STobin C. Harding my($filename, $dirs, $suffix) = fileparse($path); 420b401f56fSTobin C. Harding foreach (@skip_any) { 421136fc5c4STobin C. Harding return 1 if (/^$filename$/); 422136fc5c4STobin C. Harding } 423136fc5c4STobin C. Harding 424136fc5c4STobin C. Harding return 0; 425136fc5c4STobin C. Harding} 426136fc5c4STobin C. Harding 427dd98c252STobin C. Hardingsub timed_parse_file 428dd98c252STobin C. Harding{ 429dd98c252STobin C. Harding my ($file) = @_; 430dd98c252STobin C. Harding 431dd98c252STobin C. Harding eval { 432dd98c252STobin C. Harding local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required. 433dd98c252STobin C. Harding alarm $TIMEOUT; 434dd98c252STobin C. Harding parse_file($file); 435dd98c252STobin C. Harding alarm 0; 436dd98c252STobin C. Harding }; 437dd98c252STobin C. Harding 438dd98c252STobin C. Harding if ($@) { 439dd98c252STobin C. Harding die unless $@ eq "alarm\n"; # Propagate unexpected errors. 440dd98c252STobin C. Harding printf STDERR "timed out parsing: %s\n", $file; 441dd98c252STobin C. Harding } 442dd98c252STobin C. Harding} 443dd98c252STobin C. Harding 444136fc5c4STobin C. Hardingsub parse_file 445136fc5c4STobin C. Harding{ 446136fc5c4STobin C. Harding my ($file) = @_; 447136fc5c4STobin C. Harding 448136fc5c4STobin C. Harding if (! -R $file) { 449136fc5c4STobin C. Harding return; 450136fc5c4STobin C. Harding } 451136fc5c4STobin C. Harding 452e2858cadSTobin C. Harding if (! -T $file) { 453e2858cadSTobin C. Harding return; 454e2858cadSTobin C. Harding } 455e2858cadSTobin C. Harding 456136fc5c4STobin C. Harding open my $fh, "<", $file or return; 457136fc5c4STobin C. Harding while ( <$fh> ) { 458*cf2a85efSKees Cook chomp; 459136fc5c4STobin C. Harding if (may_leak_address($_)) { 460*cf2a85efSKees Cook printf("$file: $_\n"); 461136fc5c4STobin C. Harding } 462136fc5c4STobin C. Harding } 463136fc5c4STobin C. Harding close $fh; 464136fc5c4STobin C. Harding} 465136fc5c4STobin C. Harding 466c73dff59STobin C. Harding# Checks if the actual path name is leaking a kernel address. 467c73dff59STobin C. Hardingsub check_path_for_leaks 468c73dff59STobin C. Harding{ 469c73dff59STobin C. Harding my ($path) = @_; 470c73dff59STobin C. Harding 471c73dff59STobin C. Harding if (may_leak_address($path)) { 472c73dff59STobin C. Harding printf("Path name may contain address: $path\n"); 473c73dff59STobin C. Harding } 474c73dff59STobin C. Harding} 475c73dff59STobin C. Harding 476136fc5c4STobin C. Harding# Recursively walk directory tree. 477136fc5c4STobin C. Hardingsub walk 478136fc5c4STobin C. Harding{ 479136fc5c4STobin C. Harding my @dirs = @_; 480136fc5c4STobin C. Harding 481136fc5c4STobin C. Harding while (my $pwd = shift @dirs) { 482136fc5c4STobin C. Harding next if (!opendir(DIR, $pwd)); 483136fc5c4STobin C. Harding my @files = readdir(DIR); 484136fc5c4STobin C. Harding closedir(DIR); 485136fc5c4STobin C. Harding 486136fc5c4STobin C. Harding foreach my $file (@files) { 487136fc5c4STobin C. Harding next if ($file eq '.' or $file eq '..'); 488136fc5c4STobin C. Harding 489136fc5c4STobin C. Harding my $path = "$pwd/$file"; 490136fc5c4STobin C. Harding next if (-l $path); 491136fc5c4STobin C. Harding 492472c9e10STobin C. Harding # skip /proc/PID except /proc/1 493472c9e10STobin C. Harding next if (($path =~ /^\/proc\/[0-9]+$/) && 494472c9e10STobin C. Harding ($path !~ /^\/proc\/1$/)); 495472c9e10STobin C. Harding 496b401f56fSTobin C. Harding next if (skip($path)); 497b401f56fSTobin C. Harding 498c73dff59STobin C. Harding check_path_for_leaks($path); 499c73dff59STobin C. Harding 500136fc5c4STobin C. Harding if (-d $path) { 501136fc5c4STobin C. Harding push @dirs, $path; 502b401f56fSTobin C. Harding next; 503136fc5c4STobin C. Harding } 504b401f56fSTobin C. Harding 5050f299433STobin C. Harding dprint("parsing: $path\n"); 506b401f56fSTobin C. Harding timed_parse_file($path); 507136fc5c4STobin C. Harding } 508136fc5c4STobin C. Harding } 509136fc5c4STobin C. Harding} 510d09bd8daSTobin C. Harding 511d09bd8daSTobin C. Hardingsub format_output 512d09bd8daSTobin C. Harding{ 513d09bd8daSTobin C. Harding my ($file) = @_; 514d09bd8daSTobin C. Harding 515d09bd8daSTobin C. Harding # Default is to show raw results. 516d09bd8daSTobin C. Harding if ($raw or (!$squash_by_path and !$squash_by_filename)) { 517d09bd8daSTobin C. Harding dump_raw_output($file); 518d09bd8daSTobin C. Harding return; 519d09bd8daSTobin C. Harding } 520d09bd8daSTobin C. Harding 521d09bd8daSTobin C. Harding my ($total, $dmesg, $paths, $files) = parse_raw_file($file); 522d09bd8daSTobin C. Harding 523d09bd8daSTobin C. Harding printf "\nTotal number of results from scan (incl dmesg): %d\n", $total; 524d09bd8daSTobin C. Harding 525d09bd8daSTobin C. Harding if (!$suppress_dmesg) { 526d09bd8daSTobin C. Harding print_dmesg($dmesg); 527d09bd8daSTobin C. Harding } 528d09bd8daSTobin C. Harding 529d09bd8daSTobin C. Harding if ($squash_by_filename) { 530d09bd8daSTobin C. Harding squash_by($files, 'filename'); 531d09bd8daSTobin C. Harding } 532d09bd8daSTobin C. Harding 533d09bd8daSTobin C. Harding if ($squash_by_path) { 534d09bd8daSTobin C. Harding squash_by($paths, 'path'); 535d09bd8daSTobin C. Harding } 536d09bd8daSTobin C. Harding} 537d09bd8daSTobin C. Harding 538d09bd8daSTobin C. Hardingsub dump_raw_output 539d09bd8daSTobin C. Harding{ 540d09bd8daSTobin C. Harding my ($file) = @_; 541d09bd8daSTobin C. Harding 542d09bd8daSTobin C. Harding open (my $fh, '<', $file) or die "$0: $file: $!\n"; 543d09bd8daSTobin C. Harding while (<$fh>) { 544d09bd8daSTobin C. Harding if ($suppress_dmesg) { 545d09bd8daSTobin C. Harding if ("dmesg:" eq substr($_, 0, 6)) { 546d09bd8daSTobin C. Harding next; 547d09bd8daSTobin C. Harding } 548d09bd8daSTobin C. Harding } 549d09bd8daSTobin C. Harding print $_; 550d09bd8daSTobin C. Harding } 551d09bd8daSTobin C. Harding close $fh; 552d09bd8daSTobin C. Harding} 553d09bd8daSTobin C. Harding 554d09bd8daSTobin C. Hardingsub parse_raw_file 555d09bd8daSTobin C. Harding{ 556d09bd8daSTobin C. Harding my ($file) = @_; 557d09bd8daSTobin C. Harding 558d09bd8daSTobin C. Harding my $total = 0; # Total number of lines parsed. 559d09bd8daSTobin C. Harding my @dmesg; # dmesg output. 560d09bd8daSTobin C. Harding my %files; # Unique filenames containing leaks. 561d09bd8daSTobin C. Harding my %paths; # Unique paths containing leaks. 562d09bd8daSTobin C. Harding 563d09bd8daSTobin C. Harding open (my $fh, '<', $file) or die "$0: $file: $!\n"; 564d09bd8daSTobin C. Harding while (my $line = <$fh>) { 565d09bd8daSTobin C. Harding $total++; 566d09bd8daSTobin C. Harding 567d09bd8daSTobin C. Harding if ("dmesg:" eq substr($line, 0, 6)) { 568d09bd8daSTobin C. Harding push @dmesg, $line; 569d09bd8daSTobin C. Harding next; 570d09bd8daSTobin C. Harding } 571d09bd8daSTobin C. Harding 572d09bd8daSTobin C. Harding cache_path(\%paths, $line); 573d09bd8daSTobin C. Harding cache_filename(\%files, $line); 574d09bd8daSTobin C. Harding } 575d09bd8daSTobin C. Harding 576d09bd8daSTobin C. Harding return $total, \@dmesg, \%paths, \%files; 577d09bd8daSTobin C. Harding} 578d09bd8daSTobin C. Harding 579d09bd8daSTobin C. Hardingsub print_dmesg 580d09bd8daSTobin C. Harding{ 581d09bd8daSTobin C. Harding my ($dmesg) = @_; 582d09bd8daSTobin C. Harding 583d09bd8daSTobin C. Harding print "\ndmesg output:\n"; 584d09bd8daSTobin C. Harding 585d09bd8daSTobin C. Harding if (@$dmesg == 0) { 586d09bd8daSTobin C. Harding print "<no results>\n"; 587d09bd8daSTobin C. Harding return; 588d09bd8daSTobin C. Harding } 589d09bd8daSTobin C. Harding 590d09bd8daSTobin C. Harding foreach(@$dmesg) { 591d09bd8daSTobin C. Harding my $index = index($_, ': '); 592d09bd8daSTobin C. Harding $index += 2; # skid ': ' 593d09bd8daSTobin C. Harding print substr($_, $index); 594d09bd8daSTobin C. Harding } 595d09bd8daSTobin C. Harding} 596d09bd8daSTobin C. Harding 597d09bd8daSTobin C. Hardingsub squash_by 598d09bd8daSTobin C. Harding{ 599d09bd8daSTobin C. Harding my ($ref, $desc) = @_; 600d09bd8daSTobin C. Harding 601d09bd8daSTobin C. Harding print "\nResults squashed by $desc (excl dmesg). "; 602d09bd8daSTobin C. Harding print "Displaying [<number of results> <$desc>], <example result>\n"; 603d09bd8daSTobin C. Harding 604d09bd8daSTobin C. Harding if (keys %$ref == 0) { 605d09bd8daSTobin C. Harding print "<no results>\n"; 606d09bd8daSTobin C. Harding return; 607d09bd8daSTobin C. Harding } 608d09bd8daSTobin C. Harding 609d09bd8daSTobin C. Harding foreach(keys %$ref) { 610d09bd8daSTobin C. Harding my $lines = $ref->{$_}; 611d09bd8daSTobin C. Harding my $length = @$lines; 612d09bd8daSTobin C. Harding printf "[%d %s] %s", $length, $_, @$lines[0]; 613d09bd8daSTobin C. Harding } 614d09bd8daSTobin C. Harding} 615d09bd8daSTobin C. Harding 616d09bd8daSTobin C. Hardingsub cache_path 617d09bd8daSTobin C. Harding{ 618d09bd8daSTobin C. Harding my ($paths, $line) = @_; 619d09bd8daSTobin C. Harding 620d09bd8daSTobin C. Harding my $index = index($line, ': '); 621d09bd8daSTobin C. Harding my $path = substr($line, 0, $index); 622d09bd8daSTobin C. Harding 623d09bd8daSTobin C. Harding $index += 2; # skip ': ' 624d09bd8daSTobin C. Harding add_to_cache($paths, $path, substr($line, $index)); 625d09bd8daSTobin C. Harding} 626d09bd8daSTobin C. Harding 627d09bd8daSTobin C. Hardingsub cache_filename 628d09bd8daSTobin C. Harding{ 629d09bd8daSTobin C. Harding my ($files, $line) = @_; 630d09bd8daSTobin C. Harding 631d09bd8daSTobin C. Harding my $index = index($line, ': '); 632d09bd8daSTobin C. Harding my $path = substr($line, 0, $index); 633d09bd8daSTobin C. Harding my $filename = basename($path); 634d09bd8daSTobin C. Harding 635d09bd8daSTobin C. Harding $index += 2; # skip ': ' 636d09bd8daSTobin C. Harding add_to_cache($files, $filename, substr($line, $index)); 637d09bd8daSTobin C. Harding} 638d09bd8daSTobin C. Harding 639d09bd8daSTobin C. Hardingsub add_to_cache 640d09bd8daSTobin C. Harding{ 641d09bd8daSTobin C. Harding my ($cache, $key, $value) = @_; 642d09bd8daSTobin C. Harding 643d09bd8daSTobin C. Harding if (!$cache->{$key}) { 644d09bd8daSTobin C. Harding $cache->{$key} = (); 645d09bd8daSTobin C. Harding } 646d09bd8daSTobin C. Harding push @{$cache->{$key}}, $value; 647d09bd8daSTobin C. Harding} 648