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

Merge tag 'leaks-4.15-rc1' of git://github.com/tcharding/linux

Pull leaking_addresses script updates from Tobin Harding:
"Here are development patches for the leaking_addresses.pl script.

Changes include:

- add summary reporting to the script

- add 'SigIgn' to false positives

- add a file read timeout so the script doesn't block indefinitely

- add infrastructure to enable multi-arch support and add support for ppc

- add some exclude files/paths suggested by various people

- code clean up and refactoring

- overhaul command line options"

* tag 'leaks-4.15-rc1' of git://github.com/tcharding/linux:
leaking_addresses: add SigIgn to false positives
leaking_addresses: add timeout on file read
leaking_addresses: add support for ppc64
leaking_addresses: add summary reporting options
leaking_addresses: add to exclude files/paths list
leaking_addresses: fix comment string typo
leaking_addresses: remove command line options
leaking_addresses: remove dead/unused code
leaking_addresses: use tabs instead of spaces

+286 -90
+286 -90
scripts/leaking_addresses.pl
··· 7 7 # - Scans dmesg output. 8 8 # - Walks directory tree and parses each file (for each directory in @DIRS). 9 9 # 10 - # You can configure the behaviour of the script; 11 - # 12 - # - By adding paths, for directories you do not want to walk; 13 - # absolute paths: @skip_walk_dirs_abs 14 - # directory names: @skip_walk_dirs_any 15 - # 16 - # - By adding paths, for files you do not want to parse; 17 - # absolute paths: @skip_parse_files_abs 18 - # file names: @skip_parse_files_any 19 - # 20 - # The use of @skip_xxx_xxx_any causes files to be skipped where ever they occur. 21 - # For example adding 'fd' to @skip_walk_dirs_any causes the fd/ directory to be 22 - # skipped for all PID sub-directories of /proc 23 - # 24 - # The same thing can be achieved by passing command line options to --dont-walk 25 - # and --dont-parse. If absolute paths are supplied to these options they are 26 - # appended to the @skip_xxx_xxx_abs arrays. If file names are supplied to these 27 - # options, they are appended to the @skip_xxx_xxx_any arrays. 28 - # 29 10 # Use --debug to output path before parsing, this is useful to find files that 30 11 # cause the script to choke. 31 12 # ··· 21 40 use Cwd 'abs_path'; 22 41 use Term::ANSIColor qw(:constants); 23 42 use Getopt::Long qw(:config no_auto_abbrev); 43 + use Config; 24 44 25 45 my $P = $0; 26 46 my $V = '0.01'; ··· 29 47 # Directories to scan. 30 48 my @DIRS = ('/proc', '/sys'); 31 49 50 + # Timer for parsing each file, in seconds. 51 + my $TIMEOUT = 10; 52 + 53 + # Script can only grep for kernel addresses on the following architectures. If 54 + # your architecture is not listed here and has a grep'able kernel address please 55 + # consider submitting a patch. 56 + my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64'); 57 + 32 58 # Command line options. 33 59 my $help = 0; 34 60 my $debug = 0; 35 - my @dont_walk = (); 36 - my @dont_parse = (); 61 + my $raw = 0; 62 + my $output_raw = ""; # Write raw results to file. 63 + my $input_raw = ""; # Read raw results from file instead of scanning. 64 + 65 + my $suppress_dmesg = 0; # Don't show dmesg in output. 66 + my $squash_by_path = 0; # Summary report grouped by absolute path. 67 + my $squash_by_filename = 0; # Summary report grouped by filename. 37 68 38 69 # Do not parse these files (absolute path). 39 70 my @skip_parse_files_abs = ('/proc/kmsg', 40 71 '/proc/kcore', 41 72 '/proc/fs/ext4/sdb1/mb_groups', 42 73 '/proc/1/fd/3', 74 + '/sys/firmware/devicetree', 75 + '/proc/device-tree', 43 76 '/sys/kernel/debug/tracing/trace_pipe', 44 77 '/sys/kernel/security/apparmor/revision'); 45 78 46 - # Do not parse thes files under any subdirectory. 79 + # Do not parse these files under any subdirectory. 47 80 my @skip_parse_files_any = ('0', 48 81 '1', 49 82 '2', ··· 79 82 'thread-self', 80 83 'cwd', 81 84 'fd', 85 + 'usbmon', 82 86 'stderr', 83 87 'stdin', 84 88 'stdout'); ··· 89 91 my ($exitcode) = @_; 90 92 91 93 print << "EOM"; 94 + 92 95 Usage: $P [OPTIONS] 93 96 Version: $V 94 97 95 98 Options: 96 99 97 - --dont-walk=<dir> Don't walk tree starting at <dir>. 98 - --dont-parse=<file> Don't parse <file>. 99 - -d, --debug Display debugging output. 100 - -h, --help, --version Display this help and exit. 100 + -o, --output-raw=<file> Save results for future processing. 101 + -i, --input-raw=<file> Read results from file instead of scanning. 102 + --raw Show raw results (default). 103 + --suppress-dmesg Do not show dmesg results. 104 + --squash-by-path Show one result per unique path. 105 + --squash-by-filename Show one result per unique filename. 106 + -d, --debug Display debugging output. 107 + -h, --help, --version Display this help and exit. 101 108 102 - If an absolute path is passed to --dont_XXX then this path is skipped. If a 103 - single filename is passed then this file/directory will be skipped when 104 - appearing under any subdirectory. 109 + Examples: 105 110 106 - Example: 111 + # Scan kernel and dump raw results. 112 + $0 107 113 108 - # Just scan dmesg output. 109 - scripts/leaking_addresses.pl --dont_walk_abs /proc --dont_walk_abs /sys 114 + # Scan kernel and save results to file. 115 + $0 --output-raw scan.out 116 + 117 + # View summary report. 118 + $0 --input-raw scan.out --squash-by-filename 110 119 111 120 Scans the running (64 bit) kernel for potential leaking addresses. 112 121 ··· 122 117 } 123 118 124 119 GetOptions( 125 - 'dont-walk=s' => \@dont_walk, 126 - 'dont-parse=s' => \@dont_parse, 127 120 'd|debug' => \$debug, 128 121 'h|help' => \$help, 129 - 'version' => \$help 122 + 'version' => \$help, 123 + 'o|output-raw=s' => \$output_raw, 124 + 'i|input-raw=s' => \$input_raw, 125 + 'suppress-dmesg' => \$suppress_dmesg, 126 + 'squash-by-path' => \$squash_by_path, 127 + 'squash-by-filename' => \$squash_by_filename, 128 + 'raw' => \$raw, 130 129 ) or help(1); 131 130 132 131 help(0) if ($help); 133 132 134 - push_to_global(); 133 + if ($input_raw) { 134 + format_output($input_raw); 135 + exit(0); 136 + } 137 + 138 + if (!$input_raw and ($squash_by_path or $squash_by_filename)) { 139 + printf "\nSummary reporting only available with --input-raw=<file>\n"; 140 + printf "(First run scan with --output-raw=<file>.)\n"; 141 + exit(128); 142 + } 143 + 144 + if (!is_supported_architecture()) { 145 + printf "\nScript does not support your architecture, sorry.\n"; 146 + printf "\nCurrently we support: \n\n"; 147 + foreach(@SUPPORTED_ARCHITECTURES) { 148 + printf "\t%s\n", $_; 149 + } 150 + 151 + my $archname = $Config{archname}; 152 + printf "\n\$ perl -MConfig -e \'print \"\$Config{archname}\\n\"\'\n"; 153 + printf "%s\n", $archname; 154 + 155 + exit(129); 156 + } 157 + 158 + if ($output_raw) { 159 + open my $fh, '>', $output_raw or die "$0: $output_raw: $!\n"; 160 + select $fh; 161 + } 135 162 136 163 parse_dmesg(); 137 164 walk(@DIRS); 138 165 139 166 exit 0; 140 167 141 - sub debug_arrays 142 - { 143 - print 'dirs_any: ' . join(", ", @skip_walk_dirs_any) . "\n"; 144 - print 'dirs_abs: ' . join(", ", @skip_walk_dirs_abs) . "\n"; 145 - print 'parse_any: ' . join(", ", @skip_parse_files_any) . "\n"; 146 - print 'parse_abs: ' . join(", ", @skip_parse_files_abs) . "\n"; 147 - } 148 - 149 168 sub dprint 150 169 { 151 170 printf(STDERR @_) if $debug; 152 171 } 153 172 154 - sub push_in_abs_any 173 + sub is_supported_architecture 155 174 { 156 - my ($in, $abs, $any) = @_; 157 - 158 - foreach my $path (@$in) { 159 - if (File::Spec->file_name_is_absolute($path)) { 160 - push @$abs, $path; 161 - } elsif (index($path,'/') == -1) { 162 - push @$any, $path; 163 - } else { 164 - print 'path error: ' . $path; 165 - } 166 - } 175 + return (is_x86_64() or is_ppc64()); 167 176 } 168 177 169 - # Push command line options to global arrays. 170 - sub push_to_global 178 + sub is_x86_64 171 179 { 172 - push_in_abs_any(\@dont_walk, \@skip_walk_dirs_abs, \@skip_walk_dirs_any); 173 - push_in_abs_any(\@dont_parse, \@skip_parse_files_abs, \@skip_parse_files_any); 180 + my $archname = $Config{archname}; 181 + 182 + if ($archname =~ m/x86_64/) { 183 + return 1; 184 + } 185 + return 0; 186 + } 187 + 188 + sub is_ppc64 189 + { 190 + my $archname = $Config{archname}; 191 + 192 + if ($archname =~ m/powerpc/ and $archname =~ m/64/) { 193 + return 1; 194 + } 195 + return 0; 174 196 } 175 197 176 198 sub is_false_positive 177 199 { 178 - my ($match) = @_; 200 + my ($match) = @_; 179 201 180 - if ($match =~ '\b(0x)?(f|F){16}\b' or 181 - $match =~ '\b(0x)?0{16}\b') { 182 - return 1; 183 - } 202 + if ($match =~ '\b(0x)?(f|F){16}\b' or 203 + $match =~ '\b(0x)?0{16}\b') { 204 + return 1; 205 + } 184 206 185 - # vsyscall memory region, we should probably check against a range here. 186 - if ($match =~ '\bf{10}600000\b' or 187 - $match =~ '\bf{10}601000\b') { 188 - return 1; 189 - } 207 + if (is_x86_64) { 208 + # vsyscall memory region, we should probably check against a range here. 209 + if ($match =~ '\bf{10}600000\b' or 210 + $match =~ '\bf{10}601000\b') { 211 + return 1; 212 + } 213 + } 190 214 191 - return 0; 215 + return 0; 192 216 } 193 217 194 218 # True if argument potentially contains a kernel address. 195 219 sub may_leak_address 196 220 { 197 - my ($line) = @_; 198 - my $address = '\b(0x)?ffff[[:xdigit:]]{12}\b'; 221 + my ($line) = @_; 222 + my $address_re; 199 223 200 - # Signal masks. 201 - if ($line =~ '^SigBlk:' or 202 - $line =~ '^SigCgt:') { 203 - return 0; 204 - } 205 - 206 - if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or 207 - $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') { 224 + # Signal masks. 225 + if ($line =~ '^SigBlk:' or 226 + $line =~ '^SigIgn:' or 227 + $line =~ '^SigCgt:') { 208 228 return 0; 209 - } 229 + } 210 230 211 - while (/($address)/g) { 212 - if (!is_false_positive($1)) { 213 - return 1; 214 - } 215 - } 231 + if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or 232 + $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') { 233 + return 0; 234 + } 216 235 217 - return 0; 236 + # One of these is guaranteed to be true. 237 + if (is_x86_64()) { 238 + $address_re = '\b(0x)?ffff[[:xdigit:]]{12}\b'; 239 + } elsif (is_ppc64()) { 240 + $address_re = '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b'; 241 + } 242 + 243 + while (/($address_re)/g) { 244 + if (!is_false_positive($1)) { 245 + return 1; 246 + } 247 + } 248 + 249 + return 0; 218 250 } 219 251 220 252 sub parse_dmesg ··· 286 244 { 287 245 my ($path) = @_; 288 246 return skip($path, \@skip_parse_files_abs, \@skip_parse_files_any); 247 + } 248 + 249 + sub timed_parse_file 250 + { 251 + my ($file) = @_; 252 + 253 + eval { 254 + local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required. 255 + alarm $TIMEOUT; 256 + parse_file($file); 257 + alarm 0; 258 + }; 259 + 260 + if ($@) { 261 + die unless $@ eq "alarm\n"; # Propagate unexpected errors. 262 + printf STDERR "timed out parsing: %s\n", $file; 263 + } 289 264 } 290 265 291 266 sub parse_file ··· 340 281 sub walk 341 282 { 342 283 my @dirs = @_; 343 - my %seen; 344 284 345 285 while (my $pwd = shift @dirs) { 346 286 next if (skip_walk($pwd)); ··· 356 298 if (-d $path) { 357 299 push @dirs, $path; 358 300 } else { 359 - parse_file($path); 301 + timed_parse_file($path); 360 302 } 361 303 } 362 304 } 305 + } 306 + 307 + sub format_output 308 + { 309 + my ($file) = @_; 310 + 311 + # Default is to show raw results. 312 + if ($raw or (!$squash_by_path and !$squash_by_filename)) { 313 + dump_raw_output($file); 314 + return; 315 + } 316 + 317 + my ($total, $dmesg, $paths, $files) = parse_raw_file($file); 318 + 319 + printf "\nTotal number of results from scan (incl dmesg): %d\n", $total; 320 + 321 + if (!$suppress_dmesg) { 322 + print_dmesg($dmesg); 323 + } 324 + 325 + if ($squash_by_filename) { 326 + squash_by($files, 'filename'); 327 + } 328 + 329 + if ($squash_by_path) { 330 + squash_by($paths, 'path'); 331 + } 332 + } 333 + 334 + sub dump_raw_output 335 + { 336 + my ($file) = @_; 337 + 338 + open (my $fh, '<', $file) or die "$0: $file: $!\n"; 339 + while (<$fh>) { 340 + if ($suppress_dmesg) { 341 + if ("dmesg:" eq substr($_, 0, 6)) { 342 + next; 343 + } 344 + } 345 + print $_; 346 + } 347 + close $fh; 348 + } 349 + 350 + sub parse_raw_file 351 + { 352 + my ($file) = @_; 353 + 354 + my $total = 0; # Total number of lines parsed. 355 + my @dmesg; # dmesg output. 356 + my %files; # Unique filenames containing leaks. 357 + my %paths; # Unique paths containing leaks. 358 + 359 + open (my $fh, '<', $file) or die "$0: $file: $!\n"; 360 + while (my $line = <$fh>) { 361 + $total++; 362 + 363 + if ("dmesg:" eq substr($line, 0, 6)) { 364 + push @dmesg, $line; 365 + next; 366 + } 367 + 368 + cache_path(\%paths, $line); 369 + cache_filename(\%files, $line); 370 + } 371 + 372 + return $total, \@dmesg, \%paths, \%files; 373 + } 374 + 375 + sub print_dmesg 376 + { 377 + my ($dmesg) = @_; 378 + 379 + print "\ndmesg output:\n"; 380 + 381 + if (@$dmesg == 0) { 382 + print "<no results>\n"; 383 + return; 384 + } 385 + 386 + foreach(@$dmesg) { 387 + my $index = index($_, ': '); 388 + $index += 2; # skid ': ' 389 + print substr($_, $index); 390 + } 391 + } 392 + 393 + sub squash_by 394 + { 395 + my ($ref, $desc) = @_; 396 + 397 + print "\nResults squashed by $desc (excl dmesg). "; 398 + print "Displaying [<number of results> <$desc>], <example result>\n"; 399 + 400 + if (keys %$ref == 0) { 401 + print "<no results>\n"; 402 + return; 403 + } 404 + 405 + foreach(keys %$ref) { 406 + my $lines = $ref->{$_}; 407 + my $length = @$lines; 408 + printf "[%d %s] %s", $length, $_, @$lines[0]; 409 + } 410 + } 411 + 412 + sub cache_path 413 + { 414 + my ($paths, $line) = @_; 415 + 416 + my $index = index($line, ': '); 417 + my $path = substr($line, 0, $index); 418 + 419 + $index += 2; # skip ': ' 420 + add_to_cache($paths, $path, substr($line, $index)); 421 + } 422 + 423 + sub cache_filename 424 + { 425 + my ($files, $line) = @_; 426 + 427 + my $index = index($line, ': '); 428 + my $path = substr($line, 0, $index); 429 + my $filename = basename($path); 430 + 431 + $index += 2; # skip ': ' 432 + add_to_cache($files, $filename, substr($line, $index)); 433 + } 434 + 435 + sub add_to_cache 436 + { 437 + my ($cache, $key, $value) = @_; 438 + 439 + if (!$cache->{$key}) { 440 + $cache->{$key} = (); 441 + } 442 + push @{$cache->{$key}}, $value; 363 443 }