1*4882a593Smuzhiyun#!/usr/bin/env perl 2*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun# (c) 2017 Tobin C. Harding <me@tobin.cc> 5*4882a593Smuzhiyun# 6*4882a593Smuzhiyun# leaking_addresses.pl: Scan the kernel for potential leaking addresses. 7*4882a593Smuzhiyun# - Scans dmesg output. 8*4882a593Smuzhiyun# - Walks directory tree and parses each file (for each directory in @DIRS). 9*4882a593Smuzhiyun# 10*4882a593Smuzhiyun# Use --debug to output path before parsing, this is useful to find files that 11*4882a593Smuzhiyun# cause the script to choke. 12*4882a593Smuzhiyun 13*4882a593Smuzhiyun# 14*4882a593Smuzhiyun# When the system is idle it is likely that most files under /proc/PID will be 15*4882a593Smuzhiyun# identical for various processes. Scanning _all_ the PIDs under /proc is 16*4882a593Smuzhiyun# unnecessary and implies that we are thoroughly scanning /proc. This is _not_ 17*4882a593Smuzhiyun# the case because there may be ways userspace can trigger creation of /proc 18*4882a593Smuzhiyun# files that leak addresses but were not present during a scan. For these two 19*4882a593Smuzhiyun# reasons we exclude all PID directories under /proc except '1/' 20*4882a593Smuzhiyun 21*4882a593Smuzhiyunuse warnings; 22*4882a593Smuzhiyunuse strict; 23*4882a593Smuzhiyunuse POSIX; 24*4882a593Smuzhiyunuse File::Basename; 25*4882a593Smuzhiyunuse File::Spec; 26*4882a593Smuzhiyunuse Cwd 'abs_path'; 27*4882a593Smuzhiyunuse Term::ANSIColor qw(:constants); 28*4882a593Smuzhiyunuse Getopt::Long qw(:config no_auto_abbrev); 29*4882a593Smuzhiyunuse Config; 30*4882a593Smuzhiyunuse bigint qw/hex/; 31*4882a593Smuzhiyunuse feature 'state'; 32*4882a593Smuzhiyun 33*4882a593Smuzhiyunmy $P = $0; 34*4882a593Smuzhiyun 35*4882a593Smuzhiyun# Directories to scan. 36*4882a593Smuzhiyunmy @DIRS = ('/proc', '/sys'); 37*4882a593Smuzhiyun 38*4882a593Smuzhiyun# Timer for parsing each file, in seconds. 39*4882a593Smuzhiyunmy $TIMEOUT = 10; 40*4882a593Smuzhiyun 41*4882a593Smuzhiyun# Kernel addresses vary by architecture. We can only auto-detect the following 42*4882a593Smuzhiyun# architectures (using `uname -m`). (flag --32-bit overrides auto-detection.) 43*4882a593Smuzhiyunmy @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64', 'x86'); 44*4882a593Smuzhiyun 45*4882a593Smuzhiyun# Command line options. 46*4882a593Smuzhiyunmy $help = 0; 47*4882a593Smuzhiyunmy $debug = 0; 48*4882a593Smuzhiyunmy $raw = 0; 49*4882a593Smuzhiyunmy $output_raw = ""; # Write raw results to file. 50*4882a593Smuzhiyunmy $input_raw = ""; # Read raw results from file instead of scanning. 51*4882a593Smuzhiyunmy $suppress_dmesg = 0; # Don't show dmesg in output. 52*4882a593Smuzhiyunmy $squash_by_path = 0; # Summary report grouped by absolute path. 53*4882a593Smuzhiyunmy $squash_by_filename = 0; # Summary report grouped by filename. 54*4882a593Smuzhiyunmy $kernel_config_file = ""; # Kernel configuration file. 55*4882a593Smuzhiyunmy $opt_32bit = 0; # Scan 32-bit kernel. 56*4882a593Smuzhiyunmy $page_offset_32bit = 0; # Page offset for 32-bit kernel. 57*4882a593Smuzhiyun 58*4882a593Smuzhiyun# Skip these absolute paths. 59*4882a593Smuzhiyunmy @skip_abs = ( 60*4882a593Smuzhiyun '/proc/kmsg', 61*4882a593Smuzhiyun '/proc/device-tree', 62*4882a593Smuzhiyun '/proc/1/syscall', 63*4882a593Smuzhiyun '/sys/firmware/devicetree', 64*4882a593Smuzhiyun '/sys/kernel/debug/tracing/trace_pipe', 65*4882a593Smuzhiyun '/sys/kernel/security/apparmor/revision'); 66*4882a593Smuzhiyun 67*4882a593Smuzhiyun# Skip these under any subdirectory. 68*4882a593Smuzhiyunmy @skip_any = ( 69*4882a593Smuzhiyun 'pagemap', 70*4882a593Smuzhiyun 'events', 71*4882a593Smuzhiyun 'access', 72*4882a593Smuzhiyun 'registers', 73*4882a593Smuzhiyun 'snapshot_raw', 74*4882a593Smuzhiyun 'trace_pipe_raw', 75*4882a593Smuzhiyun 'ptmx', 76*4882a593Smuzhiyun 'trace_pipe', 77*4882a593Smuzhiyun 'fd', 78*4882a593Smuzhiyun 'usbmon'); 79*4882a593Smuzhiyun 80*4882a593Smuzhiyunsub help 81*4882a593Smuzhiyun{ 82*4882a593Smuzhiyun my ($exitcode) = @_; 83*4882a593Smuzhiyun 84*4882a593Smuzhiyun print << "EOM"; 85*4882a593Smuzhiyun 86*4882a593SmuzhiyunUsage: $P [OPTIONS] 87*4882a593Smuzhiyun 88*4882a593SmuzhiyunOptions: 89*4882a593Smuzhiyun 90*4882a593Smuzhiyun -o, --output-raw=<file> Save results for future processing. 91*4882a593Smuzhiyun -i, --input-raw=<file> Read results from file instead of scanning. 92*4882a593Smuzhiyun --raw Show raw results (default). 93*4882a593Smuzhiyun --suppress-dmesg Do not show dmesg results. 94*4882a593Smuzhiyun --squash-by-path Show one result per unique path. 95*4882a593Smuzhiyun --squash-by-filename Show one result per unique filename. 96*4882a593Smuzhiyun --kernel-config-file=<file> Kernel configuration file (e.g /boot/config) 97*4882a593Smuzhiyun --32-bit Scan 32-bit kernel. 98*4882a593Smuzhiyun --page-offset-32-bit=o Page offset (for 32-bit kernel 0xABCD1234). 99*4882a593Smuzhiyun -d, --debug Display debugging output. 100*4882a593Smuzhiyun -h, --help Display this help and exit. 101*4882a593Smuzhiyun 102*4882a593SmuzhiyunScans the running kernel for potential leaking addresses. 103*4882a593Smuzhiyun 104*4882a593SmuzhiyunEOM 105*4882a593Smuzhiyun exit($exitcode); 106*4882a593Smuzhiyun} 107*4882a593Smuzhiyun 108*4882a593SmuzhiyunGetOptions( 109*4882a593Smuzhiyun 'd|debug' => \$debug, 110*4882a593Smuzhiyun 'h|help' => \$help, 111*4882a593Smuzhiyun 'o|output-raw=s' => \$output_raw, 112*4882a593Smuzhiyun 'i|input-raw=s' => \$input_raw, 113*4882a593Smuzhiyun 'suppress-dmesg' => \$suppress_dmesg, 114*4882a593Smuzhiyun 'squash-by-path' => \$squash_by_path, 115*4882a593Smuzhiyun 'squash-by-filename' => \$squash_by_filename, 116*4882a593Smuzhiyun 'raw' => \$raw, 117*4882a593Smuzhiyun 'kernel-config-file=s' => \$kernel_config_file, 118*4882a593Smuzhiyun '32-bit' => \$opt_32bit, 119*4882a593Smuzhiyun 'page-offset-32-bit=o' => \$page_offset_32bit, 120*4882a593Smuzhiyun) or help(1); 121*4882a593Smuzhiyun 122*4882a593Smuzhiyunhelp(0) if ($help); 123*4882a593Smuzhiyun 124*4882a593Smuzhiyunif ($input_raw) { 125*4882a593Smuzhiyun format_output($input_raw); 126*4882a593Smuzhiyun exit(0); 127*4882a593Smuzhiyun} 128*4882a593Smuzhiyun 129*4882a593Smuzhiyunif (!$input_raw and ($squash_by_path or $squash_by_filename)) { 130*4882a593Smuzhiyun printf "\nSummary reporting only available with --input-raw=<file>\n"; 131*4882a593Smuzhiyun printf "(First run scan with --output-raw=<file>.)\n"; 132*4882a593Smuzhiyun exit(128); 133*4882a593Smuzhiyun} 134*4882a593Smuzhiyun 135*4882a593Smuzhiyunif (!(is_supported_architecture() or $opt_32bit or $page_offset_32bit)) { 136*4882a593Smuzhiyun printf "\nScript does not support your architecture, sorry.\n"; 137*4882a593Smuzhiyun printf "\nCurrently we support: \n\n"; 138*4882a593Smuzhiyun foreach(@SUPPORTED_ARCHITECTURES) { 139*4882a593Smuzhiyun printf "\t%s\n", $_; 140*4882a593Smuzhiyun } 141*4882a593Smuzhiyun printf("\n"); 142*4882a593Smuzhiyun 143*4882a593Smuzhiyun printf("If you are running a 32-bit architecture you may use:\n"); 144*4882a593Smuzhiyun printf("\n\t--32-bit or --page-offset-32-bit=<page offset>\n\n"); 145*4882a593Smuzhiyun 146*4882a593Smuzhiyun my $archname = `uname -m`; 147*4882a593Smuzhiyun printf("Machine hardware name (`uname -m`): %s\n", $archname); 148*4882a593Smuzhiyun 149*4882a593Smuzhiyun exit(129); 150*4882a593Smuzhiyun} 151*4882a593Smuzhiyun 152*4882a593Smuzhiyunif ($output_raw) { 153*4882a593Smuzhiyun open my $fh, '>', $output_raw or die "$0: $output_raw: $!\n"; 154*4882a593Smuzhiyun select $fh; 155*4882a593Smuzhiyun} 156*4882a593Smuzhiyun 157*4882a593Smuzhiyunparse_dmesg(); 158*4882a593Smuzhiyunwalk(@DIRS); 159*4882a593Smuzhiyun 160*4882a593Smuzhiyunexit 0; 161*4882a593Smuzhiyun 162*4882a593Smuzhiyunsub dprint 163*4882a593Smuzhiyun{ 164*4882a593Smuzhiyun printf(STDERR @_) if $debug; 165*4882a593Smuzhiyun} 166*4882a593Smuzhiyun 167*4882a593Smuzhiyunsub is_supported_architecture 168*4882a593Smuzhiyun{ 169*4882a593Smuzhiyun return (is_x86_64() or is_ppc64() or is_ix86_32()); 170*4882a593Smuzhiyun} 171*4882a593Smuzhiyun 172*4882a593Smuzhiyunsub is_32bit 173*4882a593Smuzhiyun{ 174*4882a593Smuzhiyun # Allow --32-bit or --page-offset-32-bit to override 175*4882a593Smuzhiyun if ($opt_32bit or $page_offset_32bit) { 176*4882a593Smuzhiyun return 1; 177*4882a593Smuzhiyun } 178*4882a593Smuzhiyun 179*4882a593Smuzhiyun return is_ix86_32(); 180*4882a593Smuzhiyun} 181*4882a593Smuzhiyun 182*4882a593Smuzhiyunsub is_ix86_32 183*4882a593Smuzhiyun{ 184*4882a593Smuzhiyun state $arch = `uname -m`; 185*4882a593Smuzhiyun 186*4882a593Smuzhiyun chomp $arch; 187*4882a593Smuzhiyun if ($arch =~ m/i[3456]86/) { 188*4882a593Smuzhiyun return 1; 189*4882a593Smuzhiyun } 190*4882a593Smuzhiyun return 0; 191*4882a593Smuzhiyun} 192*4882a593Smuzhiyun 193*4882a593Smuzhiyunsub is_arch 194*4882a593Smuzhiyun{ 195*4882a593Smuzhiyun my ($desc) = @_; 196*4882a593Smuzhiyun my $arch = `uname -m`; 197*4882a593Smuzhiyun 198*4882a593Smuzhiyun chomp $arch; 199*4882a593Smuzhiyun if ($arch eq $desc) { 200*4882a593Smuzhiyun return 1; 201*4882a593Smuzhiyun } 202*4882a593Smuzhiyun return 0; 203*4882a593Smuzhiyun} 204*4882a593Smuzhiyun 205*4882a593Smuzhiyunsub is_x86_64 206*4882a593Smuzhiyun{ 207*4882a593Smuzhiyun state $is = is_arch('x86_64'); 208*4882a593Smuzhiyun return $is; 209*4882a593Smuzhiyun} 210*4882a593Smuzhiyun 211*4882a593Smuzhiyunsub is_ppc64 212*4882a593Smuzhiyun{ 213*4882a593Smuzhiyun state $is = is_arch('ppc64'); 214*4882a593Smuzhiyun return $is; 215*4882a593Smuzhiyun} 216*4882a593Smuzhiyun 217*4882a593Smuzhiyun# Gets config option value from kernel config file. 218*4882a593Smuzhiyun# Returns "" on error or if config option not found. 219*4882a593Smuzhiyunsub get_kernel_config_option 220*4882a593Smuzhiyun{ 221*4882a593Smuzhiyun my ($option) = @_; 222*4882a593Smuzhiyun my $value = ""; 223*4882a593Smuzhiyun my $tmp_file = ""; 224*4882a593Smuzhiyun my @config_files; 225*4882a593Smuzhiyun 226*4882a593Smuzhiyun # Allow --kernel-config-file to override. 227*4882a593Smuzhiyun if ($kernel_config_file ne "") { 228*4882a593Smuzhiyun @config_files = ($kernel_config_file); 229*4882a593Smuzhiyun } elsif (-R "/proc/config.gz") { 230*4882a593Smuzhiyun my $tmp_file = "/tmp/tmpkconf"; 231*4882a593Smuzhiyun 232*4882a593Smuzhiyun if (system("gunzip < /proc/config.gz > $tmp_file")) { 233*4882a593Smuzhiyun dprint("system(gunzip < /proc/config.gz) failed\n"); 234*4882a593Smuzhiyun return ""; 235*4882a593Smuzhiyun } else { 236*4882a593Smuzhiyun @config_files = ($tmp_file); 237*4882a593Smuzhiyun } 238*4882a593Smuzhiyun } else { 239*4882a593Smuzhiyun my $file = '/boot/config-' . `uname -r`; 240*4882a593Smuzhiyun chomp $file; 241*4882a593Smuzhiyun @config_files = ($file, '/boot/config'); 242*4882a593Smuzhiyun } 243*4882a593Smuzhiyun 244*4882a593Smuzhiyun foreach my $file (@config_files) { 245*4882a593Smuzhiyun dprint("parsing config file: $file\n"); 246*4882a593Smuzhiyun $value = option_from_file($option, $file); 247*4882a593Smuzhiyun if ($value ne "") { 248*4882a593Smuzhiyun last; 249*4882a593Smuzhiyun } 250*4882a593Smuzhiyun } 251*4882a593Smuzhiyun 252*4882a593Smuzhiyun if ($tmp_file ne "") { 253*4882a593Smuzhiyun system("rm -f $tmp_file"); 254*4882a593Smuzhiyun } 255*4882a593Smuzhiyun 256*4882a593Smuzhiyun return $value; 257*4882a593Smuzhiyun} 258*4882a593Smuzhiyun 259*4882a593Smuzhiyun# Parses $file and returns kernel configuration option value. 260*4882a593Smuzhiyunsub option_from_file 261*4882a593Smuzhiyun{ 262*4882a593Smuzhiyun my ($option, $file) = @_; 263*4882a593Smuzhiyun my $str = ""; 264*4882a593Smuzhiyun my $val = ""; 265*4882a593Smuzhiyun 266*4882a593Smuzhiyun open(my $fh, "<", $file) or return ""; 267*4882a593Smuzhiyun while (my $line = <$fh> ) { 268*4882a593Smuzhiyun if ($line =~ /^$option/) { 269*4882a593Smuzhiyun ($str, $val) = split /=/, $line; 270*4882a593Smuzhiyun chomp $val; 271*4882a593Smuzhiyun last; 272*4882a593Smuzhiyun } 273*4882a593Smuzhiyun } 274*4882a593Smuzhiyun 275*4882a593Smuzhiyun close $fh; 276*4882a593Smuzhiyun return $val; 277*4882a593Smuzhiyun} 278*4882a593Smuzhiyun 279*4882a593Smuzhiyunsub is_false_positive 280*4882a593Smuzhiyun{ 281*4882a593Smuzhiyun my ($match) = @_; 282*4882a593Smuzhiyun 283*4882a593Smuzhiyun if (is_32bit()) { 284*4882a593Smuzhiyun return is_false_positive_32bit($match); 285*4882a593Smuzhiyun } 286*4882a593Smuzhiyun 287*4882a593Smuzhiyun # 64 bit false positives. 288*4882a593Smuzhiyun 289*4882a593Smuzhiyun if ($match =~ '\b(0x)?(f|F){16}\b' or 290*4882a593Smuzhiyun $match =~ '\b(0x)?0{16}\b') { 291*4882a593Smuzhiyun return 1; 292*4882a593Smuzhiyun } 293*4882a593Smuzhiyun 294*4882a593Smuzhiyun if (is_x86_64() and is_in_vsyscall_memory_region($match)) { 295*4882a593Smuzhiyun return 1; 296*4882a593Smuzhiyun } 297*4882a593Smuzhiyun 298*4882a593Smuzhiyun return 0; 299*4882a593Smuzhiyun} 300*4882a593Smuzhiyun 301*4882a593Smuzhiyunsub is_false_positive_32bit 302*4882a593Smuzhiyun{ 303*4882a593Smuzhiyun my ($match) = @_; 304*4882a593Smuzhiyun state $page_offset = get_page_offset(); 305*4882a593Smuzhiyun 306*4882a593Smuzhiyun if ($match =~ '\b(0x)?(f|F){8}\b') { 307*4882a593Smuzhiyun return 1; 308*4882a593Smuzhiyun } 309*4882a593Smuzhiyun 310*4882a593Smuzhiyun if (hex($match) < $page_offset) { 311*4882a593Smuzhiyun return 1; 312*4882a593Smuzhiyun } 313*4882a593Smuzhiyun 314*4882a593Smuzhiyun return 0; 315*4882a593Smuzhiyun} 316*4882a593Smuzhiyun 317*4882a593Smuzhiyun# returns integer value 318*4882a593Smuzhiyunsub get_page_offset 319*4882a593Smuzhiyun{ 320*4882a593Smuzhiyun my $page_offset; 321*4882a593Smuzhiyun my $default_offset = 0xc0000000; 322*4882a593Smuzhiyun 323*4882a593Smuzhiyun # Allow --page-offset-32bit to override. 324*4882a593Smuzhiyun if ($page_offset_32bit != 0) { 325*4882a593Smuzhiyun return $page_offset_32bit; 326*4882a593Smuzhiyun } 327*4882a593Smuzhiyun 328*4882a593Smuzhiyun $page_offset = get_kernel_config_option('CONFIG_PAGE_OFFSET'); 329*4882a593Smuzhiyun if (!$page_offset) { 330*4882a593Smuzhiyun return $default_offset; 331*4882a593Smuzhiyun } 332*4882a593Smuzhiyun return $page_offset; 333*4882a593Smuzhiyun} 334*4882a593Smuzhiyun 335*4882a593Smuzhiyunsub is_in_vsyscall_memory_region 336*4882a593Smuzhiyun{ 337*4882a593Smuzhiyun my ($match) = @_; 338*4882a593Smuzhiyun 339*4882a593Smuzhiyun my $hex = hex($match); 340*4882a593Smuzhiyun my $region_min = hex("0xffffffffff600000"); 341*4882a593Smuzhiyun my $region_max = hex("0xffffffffff601000"); 342*4882a593Smuzhiyun 343*4882a593Smuzhiyun return ($hex >= $region_min and $hex <= $region_max); 344*4882a593Smuzhiyun} 345*4882a593Smuzhiyun 346*4882a593Smuzhiyun# True if argument potentially contains a kernel address. 347*4882a593Smuzhiyunsub may_leak_address 348*4882a593Smuzhiyun{ 349*4882a593Smuzhiyun my ($line) = @_; 350*4882a593Smuzhiyun my $address_re; 351*4882a593Smuzhiyun 352*4882a593Smuzhiyun # Signal masks. 353*4882a593Smuzhiyun if ($line =~ '^SigBlk:' or 354*4882a593Smuzhiyun $line =~ '^SigIgn:' or 355*4882a593Smuzhiyun $line =~ '^SigCgt:') { 356*4882a593Smuzhiyun return 0; 357*4882a593Smuzhiyun } 358*4882a593Smuzhiyun 359*4882a593Smuzhiyun if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or 360*4882a593Smuzhiyun $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') { 361*4882a593Smuzhiyun return 0; 362*4882a593Smuzhiyun } 363*4882a593Smuzhiyun 364*4882a593Smuzhiyun $address_re = get_address_re(); 365*4882a593Smuzhiyun while ($line =~ /($address_re)/g) { 366*4882a593Smuzhiyun if (!is_false_positive($1)) { 367*4882a593Smuzhiyun return 1; 368*4882a593Smuzhiyun } 369*4882a593Smuzhiyun } 370*4882a593Smuzhiyun 371*4882a593Smuzhiyun return 0; 372*4882a593Smuzhiyun} 373*4882a593Smuzhiyun 374*4882a593Smuzhiyunsub get_address_re 375*4882a593Smuzhiyun{ 376*4882a593Smuzhiyun if (is_ppc64()) { 377*4882a593Smuzhiyun return '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b'; 378*4882a593Smuzhiyun } elsif (is_32bit()) { 379*4882a593Smuzhiyun return '\b(0x)?[[:xdigit:]]{8}\b'; 380*4882a593Smuzhiyun } 381*4882a593Smuzhiyun 382*4882a593Smuzhiyun return get_x86_64_re(); 383*4882a593Smuzhiyun} 384*4882a593Smuzhiyun 385*4882a593Smuzhiyunsub get_x86_64_re 386*4882a593Smuzhiyun{ 387*4882a593Smuzhiyun # We handle page table levels but only if explicitly configured using 388*4882a593Smuzhiyun # CONFIG_PGTABLE_LEVELS. If config file parsing fails or config option 389*4882a593Smuzhiyun # is not found we default to using address regular expression suitable 390*4882a593Smuzhiyun # for 4 page table levels. 391*4882a593Smuzhiyun state $ptl = get_kernel_config_option('CONFIG_PGTABLE_LEVELS'); 392*4882a593Smuzhiyun 393*4882a593Smuzhiyun if ($ptl == 5) { 394*4882a593Smuzhiyun return '\b(0x)?ff[[:xdigit:]]{14}\b'; 395*4882a593Smuzhiyun } 396*4882a593Smuzhiyun return '\b(0x)?ffff[[:xdigit:]]{12}\b'; 397*4882a593Smuzhiyun} 398*4882a593Smuzhiyun 399*4882a593Smuzhiyunsub parse_dmesg 400*4882a593Smuzhiyun{ 401*4882a593Smuzhiyun open my $cmd, '-|', 'dmesg'; 402*4882a593Smuzhiyun while (<$cmd>) { 403*4882a593Smuzhiyun if (may_leak_address($_)) { 404*4882a593Smuzhiyun print 'dmesg: ' . $_; 405*4882a593Smuzhiyun } 406*4882a593Smuzhiyun } 407*4882a593Smuzhiyun close $cmd; 408*4882a593Smuzhiyun} 409*4882a593Smuzhiyun 410*4882a593Smuzhiyun# True if we should skip this path. 411*4882a593Smuzhiyunsub skip 412*4882a593Smuzhiyun{ 413*4882a593Smuzhiyun my ($path) = @_; 414*4882a593Smuzhiyun 415*4882a593Smuzhiyun foreach (@skip_abs) { 416*4882a593Smuzhiyun return 1 if (/^$path$/); 417*4882a593Smuzhiyun } 418*4882a593Smuzhiyun 419*4882a593Smuzhiyun my($filename, $dirs, $suffix) = fileparse($path); 420*4882a593Smuzhiyun foreach (@skip_any) { 421*4882a593Smuzhiyun return 1 if (/^$filename$/); 422*4882a593Smuzhiyun } 423*4882a593Smuzhiyun 424*4882a593Smuzhiyun return 0; 425*4882a593Smuzhiyun} 426*4882a593Smuzhiyun 427*4882a593Smuzhiyunsub timed_parse_file 428*4882a593Smuzhiyun{ 429*4882a593Smuzhiyun my ($file) = @_; 430*4882a593Smuzhiyun 431*4882a593Smuzhiyun eval { 432*4882a593Smuzhiyun local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required. 433*4882a593Smuzhiyun alarm $TIMEOUT; 434*4882a593Smuzhiyun parse_file($file); 435*4882a593Smuzhiyun alarm 0; 436*4882a593Smuzhiyun }; 437*4882a593Smuzhiyun 438*4882a593Smuzhiyun if ($@) { 439*4882a593Smuzhiyun die unless $@ eq "alarm\n"; # Propagate unexpected errors. 440*4882a593Smuzhiyun printf STDERR "timed out parsing: %s\n", $file; 441*4882a593Smuzhiyun } 442*4882a593Smuzhiyun} 443*4882a593Smuzhiyun 444*4882a593Smuzhiyunsub parse_file 445*4882a593Smuzhiyun{ 446*4882a593Smuzhiyun my ($file) = @_; 447*4882a593Smuzhiyun 448*4882a593Smuzhiyun if (! -R $file) { 449*4882a593Smuzhiyun return; 450*4882a593Smuzhiyun } 451*4882a593Smuzhiyun 452*4882a593Smuzhiyun if (! -T $file) { 453*4882a593Smuzhiyun return; 454*4882a593Smuzhiyun } 455*4882a593Smuzhiyun 456*4882a593Smuzhiyun open my $fh, "<", $file or return; 457*4882a593Smuzhiyun while ( <$fh> ) { 458*4882a593Smuzhiyun chomp; 459*4882a593Smuzhiyun if (may_leak_address($_)) { 460*4882a593Smuzhiyun printf("$file: $_\n"); 461*4882a593Smuzhiyun } 462*4882a593Smuzhiyun } 463*4882a593Smuzhiyun close $fh; 464*4882a593Smuzhiyun} 465*4882a593Smuzhiyun 466*4882a593Smuzhiyun# Checks if the actual path name is leaking a kernel address. 467*4882a593Smuzhiyunsub check_path_for_leaks 468*4882a593Smuzhiyun{ 469*4882a593Smuzhiyun my ($path) = @_; 470*4882a593Smuzhiyun 471*4882a593Smuzhiyun if (may_leak_address($path)) { 472*4882a593Smuzhiyun printf("Path name may contain address: $path\n"); 473*4882a593Smuzhiyun } 474*4882a593Smuzhiyun} 475*4882a593Smuzhiyun 476*4882a593Smuzhiyun# Recursively walk directory tree. 477*4882a593Smuzhiyunsub walk 478*4882a593Smuzhiyun{ 479*4882a593Smuzhiyun my @dirs = @_; 480*4882a593Smuzhiyun 481*4882a593Smuzhiyun while (my $pwd = shift @dirs) { 482*4882a593Smuzhiyun next if (!opendir(DIR, $pwd)); 483*4882a593Smuzhiyun my @files = readdir(DIR); 484*4882a593Smuzhiyun closedir(DIR); 485*4882a593Smuzhiyun 486*4882a593Smuzhiyun foreach my $file (@files) { 487*4882a593Smuzhiyun next if ($file eq '.' or $file eq '..'); 488*4882a593Smuzhiyun 489*4882a593Smuzhiyun my $path = "$pwd/$file"; 490*4882a593Smuzhiyun next if (-l $path); 491*4882a593Smuzhiyun 492*4882a593Smuzhiyun # skip /proc/PID except /proc/1 493*4882a593Smuzhiyun next if (($path =~ /^\/proc\/[0-9]+$/) && 494*4882a593Smuzhiyun ($path !~ /^\/proc\/1$/)); 495*4882a593Smuzhiyun 496*4882a593Smuzhiyun next if (skip($path)); 497*4882a593Smuzhiyun 498*4882a593Smuzhiyun check_path_for_leaks($path); 499*4882a593Smuzhiyun 500*4882a593Smuzhiyun if (-d $path) { 501*4882a593Smuzhiyun push @dirs, $path; 502*4882a593Smuzhiyun next; 503*4882a593Smuzhiyun } 504*4882a593Smuzhiyun 505*4882a593Smuzhiyun dprint("parsing: $path\n"); 506*4882a593Smuzhiyun timed_parse_file($path); 507*4882a593Smuzhiyun } 508*4882a593Smuzhiyun } 509*4882a593Smuzhiyun} 510*4882a593Smuzhiyun 511*4882a593Smuzhiyunsub format_output 512*4882a593Smuzhiyun{ 513*4882a593Smuzhiyun my ($file) = @_; 514*4882a593Smuzhiyun 515*4882a593Smuzhiyun # Default is to show raw results. 516*4882a593Smuzhiyun if ($raw or (!$squash_by_path and !$squash_by_filename)) { 517*4882a593Smuzhiyun dump_raw_output($file); 518*4882a593Smuzhiyun return; 519*4882a593Smuzhiyun } 520*4882a593Smuzhiyun 521*4882a593Smuzhiyun my ($total, $dmesg, $paths, $files) = parse_raw_file($file); 522*4882a593Smuzhiyun 523*4882a593Smuzhiyun printf "\nTotal number of results from scan (incl dmesg): %d\n", $total; 524*4882a593Smuzhiyun 525*4882a593Smuzhiyun if (!$suppress_dmesg) { 526*4882a593Smuzhiyun print_dmesg($dmesg); 527*4882a593Smuzhiyun } 528*4882a593Smuzhiyun 529*4882a593Smuzhiyun if ($squash_by_filename) { 530*4882a593Smuzhiyun squash_by($files, 'filename'); 531*4882a593Smuzhiyun } 532*4882a593Smuzhiyun 533*4882a593Smuzhiyun if ($squash_by_path) { 534*4882a593Smuzhiyun squash_by($paths, 'path'); 535*4882a593Smuzhiyun } 536*4882a593Smuzhiyun} 537*4882a593Smuzhiyun 538*4882a593Smuzhiyunsub dump_raw_output 539*4882a593Smuzhiyun{ 540*4882a593Smuzhiyun my ($file) = @_; 541*4882a593Smuzhiyun 542*4882a593Smuzhiyun open (my $fh, '<', $file) or die "$0: $file: $!\n"; 543*4882a593Smuzhiyun while (<$fh>) { 544*4882a593Smuzhiyun if ($suppress_dmesg) { 545*4882a593Smuzhiyun if ("dmesg:" eq substr($_, 0, 6)) { 546*4882a593Smuzhiyun next; 547*4882a593Smuzhiyun } 548*4882a593Smuzhiyun } 549*4882a593Smuzhiyun print $_; 550*4882a593Smuzhiyun } 551*4882a593Smuzhiyun close $fh; 552*4882a593Smuzhiyun} 553*4882a593Smuzhiyun 554*4882a593Smuzhiyunsub parse_raw_file 555*4882a593Smuzhiyun{ 556*4882a593Smuzhiyun my ($file) = @_; 557*4882a593Smuzhiyun 558*4882a593Smuzhiyun my $total = 0; # Total number of lines parsed. 559*4882a593Smuzhiyun my @dmesg; # dmesg output. 560*4882a593Smuzhiyun my %files; # Unique filenames containing leaks. 561*4882a593Smuzhiyun my %paths; # Unique paths containing leaks. 562*4882a593Smuzhiyun 563*4882a593Smuzhiyun open (my $fh, '<', $file) or die "$0: $file: $!\n"; 564*4882a593Smuzhiyun while (my $line = <$fh>) { 565*4882a593Smuzhiyun $total++; 566*4882a593Smuzhiyun 567*4882a593Smuzhiyun if ("dmesg:" eq substr($line, 0, 6)) { 568*4882a593Smuzhiyun push @dmesg, $line; 569*4882a593Smuzhiyun next; 570*4882a593Smuzhiyun } 571*4882a593Smuzhiyun 572*4882a593Smuzhiyun cache_path(\%paths, $line); 573*4882a593Smuzhiyun cache_filename(\%files, $line); 574*4882a593Smuzhiyun } 575*4882a593Smuzhiyun 576*4882a593Smuzhiyun return $total, \@dmesg, \%paths, \%files; 577*4882a593Smuzhiyun} 578*4882a593Smuzhiyun 579*4882a593Smuzhiyunsub print_dmesg 580*4882a593Smuzhiyun{ 581*4882a593Smuzhiyun my ($dmesg) = @_; 582*4882a593Smuzhiyun 583*4882a593Smuzhiyun print "\ndmesg output:\n"; 584*4882a593Smuzhiyun 585*4882a593Smuzhiyun if (@$dmesg == 0) { 586*4882a593Smuzhiyun print "<no results>\n"; 587*4882a593Smuzhiyun return; 588*4882a593Smuzhiyun } 589*4882a593Smuzhiyun 590*4882a593Smuzhiyun foreach(@$dmesg) { 591*4882a593Smuzhiyun my $index = index($_, ': '); 592*4882a593Smuzhiyun $index += 2; # skid ': ' 593*4882a593Smuzhiyun print substr($_, $index); 594*4882a593Smuzhiyun } 595*4882a593Smuzhiyun} 596*4882a593Smuzhiyun 597*4882a593Smuzhiyunsub squash_by 598*4882a593Smuzhiyun{ 599*4882a593Smuzhiyun my ($ref, $desc) = @_; 600*4882a593Smuzhiyun 601*4882a593Smuzhiyun print "\nResults squashed by $desc (excl dmesg). "; 602*4882a593Smuzhiyun print "Displaying [<number of results> <$desc>], <example result>\n"; 603*4882a593Smuzhiyun 604*4882a593Smuzhiyun if (keys %$ref == 0) { 605*4882a593Smuzhiyun print "<no results>\n"; 606*4882a593Smuzhiyun return; 607*4882a593Smuzhiyun } 608*4882a593Smuzhiyun 609*4882a593Smuzhiyun foreach(keys %$ref) { 610*4882a593Smuzhiyun my $lines = $ref->{$_}; 611*4882a593Smuzhiyun my $length = @$lines; 612*4882a593Smuzhiyun printf "[%d %s] %s", $length, $_, @$lines[0]; 613*4882a593Smuzhiyun } 614*4882a593Smuzhiyun} 615*4882a593Smuzhiyun 616*4882a593Smuzhiyunsub cache_path 617*4882a593Smuzhiyun{ 618*4882a593Smuzhiyun my ($paths, $line) = @_; 619*4882a593Smuzhiyun 620*4882a593Smuzhiyun my $index = index($line, ': '); 621*4882a593Smuzhiyun my $path = substr($line, 0, $index); 622*4882a593Smuzhiyun 623*4882a593Smuzhiyun $index += 2; # skip ': ' 624*4882a593Smuzhiyun add_to_cache($paths, $path, substr($line, $index)); 625*4882a593Smuzhiyun} 626*4882a593Smuzhiyun 627*4882a593Smuzhiyunsub cache_filename 628*4882a593Smuzhiyun{ 629*4882a593Smuzhiyun my ($files, $line) = @_; 630*4882a593Smuzhiyun 631*4882a593Smuzhiyun my $index = index($line, ': '); 632*4882a593Smuzhiyun my $path = substr($line, 0, $index); 633*4882a593Smuzhiyun my $filename = basename($path); 634*4882a593Smuzhiyun 635*4882a593Smuzhiyun $index += 2; # skip ': ' 636*4882a593Smuzhiyun add_to_cache($files, $filename, substr($line, $index)); 637*4882a593Smuzhiyun} 638*4882a593Smuzhiyun 639*4882a593Smuzhiyunsub add_to_cache 640*4882a593Smuzhiyun{ 641*4882a593Smuzhiyun my ($cache, $key, $value) = @_; 642*4882a593Smuzhiyun 643*4882a593Smuzhiyun if (!$cache->{$key}) { 644*4882a593Smuzhiyun $cache->{$key} = (); 645*4882a593Smuzhiyun } 646*4882a593Smuzhiyun push @{$cache->{$key}}, $value; 647*4882a593Smuzhiyun} 648