aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2017-11-15 23:11:56 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2017-11-15 23:11:56 -0500
commit93ea0eb7d77afab34657715630d692a78b8cea6a (patch)
tree331658a4af9daeab19034111692ee28d19288831
parent7c225c69f86c934e3be9be63ecde754e286838d7 (diff)
parenta11949ec20635b43d82ee229315fd2e3c80c22a3 (diff)
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
-rwxr-xr-xscripts/leaking_addresses.pl370
1 files changed, 283 insertions, 87 deletions
diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl
index 2977371b2956..bc5788000018 100755
--- a/scripts/leaking_addresses.pl
+++ b/scripts/leaking_addresses.pl
@@ -7,25 +7,6 @@
7# - Scans dmesg output. 7# - Scans dmesg output.
8# - Walks directory tree and parses each file (for each directory in @DIRS). 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# Use --debug to output path before parsing, this is useful to find files that 10# Use --debug to output path before parsing, this is useful to find files that
30# cause the script to choke. 11# cause the script to choke.
31# 12#
@@ -40,6 +21,7 @@ use File::Spec;
40use Cwd 'abs_path'; 21use Cwd 'abs_path';
41use Term::ANSIColor qw(:constants); 22use Term::ANSIColor qw(:constants);
42use Getopt::Long qw(:config no_auto_abbrev); 23use Getopt::Long qw(:config no_auto_abbrev);
24use Config;
43 25
44my $P = $0; 26my $P = $0;
45my $V = '0.01'; 27my $V = '0.01';
@@ -47,21 +29,36 @@ my $V = '0.01';
47# Directories to scan. 29# Directories to scan.
48my @DIRS = ('/proc', '/sys'); 30my @DIRS = ('/proc', '/sys');
49 31
32# Timer for parsing each file, in seconds.
33my $TIMEOUT = 10;
34
35# Script can only grep for kernel addresses on the following architectures. If
36# your architecture is not listed here and has a grep'able kernel address please
37# consider submitting a patch.
38my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64');
39
50# Command line options. 40# Command line options.
51my $help = 0; 41my $help = 0;
52my $debug = 0; 42my $debug = 0;
53my @dont_walk = (); 43my $raw = 0;
54my @dont_parse = (); 44my $output_raw = ""; # Write raw results to file.
45my $input_raw = ""; # Read raw results from file instead of scanning.
46
47my $suppress_dmesg = 0; # Don't show dmesg in output.
48my $squash_by_path = 0; # Summary report grouped by absolute path.
49my $squash_by_filename = 0; # Summary report grouped by filename.
55 50
56# Do not parse these files (absolute path). 51# Do not parse these files (absolute path).
57my @skip_parse_files_abs = ('/proc/kmsg', 52my @skip_parse_files_abs = ('/proc/kmsg',
58 '/proc/kcore', 53 '/proc/kcore',
59 '/proc/fs/ext4/sdb1/mb_groups', 54 '/proc/fs/ext4/sdb1/mb_groups',
60 '/proc/1/fd/3', 55 '/proc/1/fd/3',
56 '/sys/firmware/devicetree',
57 '/proc/device-tree',
61 '/sys/kernel/debug/tracing/trace_pipe', 58 '/sys/kernel/debug/tracing/trace_pipe',
62 '/sys/kernel/security/apparmor/revision'); 59 '/sys/kernel/security/apparmor/revision');
63 60
64# Do not parse thes files under any subdirectory. 61# Do not parse these files under any subdirectory.
65my @skip_parse_files_any = ('0', 62my @skip_parse_files_any = ('0',
66 '1', 63 '1',
67 '2', 64 '2',
@@ -82,6 +79,7 @@ my @skip_walk_dirs_any = ('self',
82 'thread-self', 79 'thread-self',
83 'cwd', 80 'cwd',
84 'fd', 81 'fd',
82 'usbmon',
85 'stderr', 83 'stderr',
86 'stdin', 84 'stdin',
87 'stdout'); 85 'stdout');
@@ -91,24 +89,31 @@ sub help
91 my ($exitcode) = @_; 89 my ($exitcode) = @_;
92 90
93 print << "EOM"; 91 print << "EOM";
92
94Usage: $P [OPTIONS] 93Usage: $P [OPTIONS]
95Version: $V 94Version: $V
96 95
97Options: 96Options:
98 97
99 --dont-walk=<dir> Don't walk tree starting at <dir>. 98 -o, --output-raw=<file> Save results for future processing.
100 --dont-parse=<file> Don't parse <file>. 99 -i, --input-raw=<file> Read results from file instead of scanning.
101 -d, --debug Display debugging output. 100 --raw Show raw results (default).
102 -h, --help, --version Display this help and exit. 101 --suppress-dmesg Do not show dmesg results.
102 --squash-by-path Show one result per unique path.
103 --squash-by-filename Show one result per unique filename.
104 -d, --debug Display debugging output.
105 -h, --help, --version Display this help and exit.
106
107Examples:
103 108
104If an absolute path is passed to --dont_XXX then this path is skipped. If a 109 # Scan kernel and dump raw results.
105single filename is passed then this file/directory will be skipped when 110 $0
106appearing under any subdirectory.
107 111
108Example: 112 # Scan kernel and save results to file.
113 $0 --output-raw scan.out
109 114
110 # Just scan dmesg output. 115 # View summary report.
111 scripts/leaking_addresses.pl --dont_walk_abs /proc --dont_walk_abs /sys 116 $0 --input-raw scan.out --squash-by-filename
112 117
113Scans the running (64 bit) kernel for potential leaking addresses. 118Scans the running (64 bit) kernel for potential leaking addresses.
114 119
@@ -117,99 +122,136 @@ EOM
117} 122}
118 123
119GetOptions( 124GetOptions(
120 'dont-walk=s' => \@dont_walk,
121 'dont-parse=s' => \@dont_parse,
122 'd|debug' => \$debug, 125 'd|debug' => \$debug,
123 'h|help' => \$help, 126 'h|help' => \$help,
124 'version' => \$help 127 'version' => \$help,
128 'o|output-raw=s' => \$output_raw,
129 'i|input-raw=s' => \$input_raw,
130 'suppress-dmesg' => \$suppress_dmesg,
131 'squash-by-path' => \$squash_by_path,
132 'squash-by-filename' => \$squash_by_filename,
133 'raw' => \$raw,
125) or help(1); 134) or help(1);
126 135
127help(0) if ($help); 136help(0) if ($help);
128 137
129push_to_global(); 138if ($input_raw) {
139 format_output($input_raw);
140 exit(0);
141}
142
143if (!$input_raw and ($squash_by_path or $squash_by_filename)) {
144 printf "\nSummary reporting only available with --input-raw=<file>\n";
145 printf "(First run scan with --output-raw=<file>.)\n";
146 exit(128);
147}
148
149if (!is_supported_architecture()) {
150 printf "\nScript does not support your architecture, sorry.\n";
151 printf "\nCurrently we support: \n\n";
152 foreach(@SUPPORTED_ARCHITECTURES) {
153 printf "\t%s\n", $_;
154 }
155
156 my $archname = $Config{archname};
157 printf "\n\$ perl -MConfig -e \'print \"\$Config{archname}\\n\"\'\n";
158 printf "%s\n", $archname;
159
160 exit(129);
161}
162
163if ($output_raw) {
164 open my $fh, '>', $output_raw or die "$0: $output_raw: $!\n";
165 select $fh;
166}
130 167
131parse_dmesg(); 168parse_dmesg();
132walk(@DIRS); 169walk(@DIRS);
133 170
134exit 0; 171exit 0;
135 172
136sub debug_arrays 173sub dprint
137{ 174{
138 print 'dirs_any: ' . join(", ", @skip_walk_dirs_any) . "\n"; 175 printf(STDERR @_) if $debug;
139 print 'dirs_abs: ' . join(", ", @skip_walk_dirs_abs) . "\n";
140 print 'parse_any: ' . join(", ", @skip_parse_files_any) . "\n";
141 print 'parse_abs: ' . join(", ", @skip_parse_files_abs) . "\n";
142} 176}
143 177
144sub dprint 178sub is_supported_architecture
145{ 179{
146 printf(STDERR @_) if $debug; 180 return (is_x86_64() or is_ppc64());
147} 181}
148 182
149sub push_in_abs_any 183sub is_x86_64
150{ 184{
151 my ($in, $abs, $any) = @_; 185 my $archname = $Config{archname};
152 186
153 foreach my $path (@$in) { 187 if ($archname =~ m/x86_64/) {
154 if (File::Spec->file_name_is_absolute($path)) { 188 return 1;
155 push @$abs, $path;
156 } elsif (index($path,'/') == -1) {
157 push @$any, $path;
158 } else {
159 print 'path error: ' . $path;
160 }
161 } 189 }
190 return 0;
162} 191}
163 192
164# Push command line options to global arrays. 193sub is_ppc64
165sub push_to_global
166{ 194{
167 push_in_abs_any(\@dont_walk, \@skip_walk_dirs_abs, \@skip_walk_dirs_any); 195 my $archname = $Config{archname};
168 push_in_abs_any(\@dont_parse, \@skip_parse_files_abs, \@skip_parse_files_any); 196
197 if ($archname =~ m/powerpc/ and $archname =~ m/64/) {
198 return 1;
199 }
200 return 0;
169} 201}
170 202
171sub is_false_positive 203sub is_false_positive
172{ 204{
173 my ($match) = @_; 205 my ($match) = @_;
174 206
175 if ($match =~ '\b(0x)?(f|F){16}\b' or 207 if ($match =~ '\b(0x)?(f|F){16}\b' or
176 $match =~ '\b(0x)?0{16}\b') { 208 $match =~ '\b(0x)?0{16}\b') {
177 return 1; 209 return 1;
178 } 210 }
179 211
180 # vsyscall memory region, we should probably check against a range here. 212 if (is_x86_64) {
181 if ($match =~ '\bf{10}600000\b' or 213 # vsyscall memory region, we should probably check against a range here.
182 $match =~ '\bf{10}601000\b') { 214 if ($match =~ '\bf{10}600000\b' or
183 return 1; 215 $match =~ '\bf{10}601000\b') {
184 } 216 return 1;
217 }
218 }
185 219
186 return 0; 220 return 0;
187} 221}
188 222
189# True if argument potentially contains a kernel address. 223# True if argument potentially contains a kernel address.
190sub may_leak_address 224sub may_leak_address
191{ 225{
192 my ($line) = @_; 226 my ($line) = @_;
193 my $address = '\b(0x)?ffff[[:xdigit:]]{12}\b'; 227 my $address_re;
194 228
195 # Signal masks. 229 # Signal masks.
196 if ($line =~ '^SigBlk:' or 230 if ($line =~ '^SigBlk:' or
197 $line =~ '^SigCgt:') { 231 $line =~ '^SigIgn:' or
198 return 0; 232 $line =~ '^SigCgt:') {
199 } 233 return 0;
234 }
200 235
201 if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or 236 if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or
202 $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') { 237 $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') {
203 return 0; 238 return 0;
204 } 239 }
205 240
206 while (/($address)/g) { 241 # One of these is guaranteed to be true.
207 if (!is_false_positive($1)) { 242 if (is_x86_64()) {
208 return 1; 243 $address_re = '\b(0x)?ffff[[:xdigit:]]{12}\b';
209 } 244 } elsif (is_ppc64()) {
210 } 245 $address_re = '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b';
246 }
211 247
212 return 0; 248 while (/($address_re)/g) {
249 if (!is_false_positive($1)) {
250 return 1;
251 }
252 }
253
254 return 0;
213} 255}
214 256
215sub parse_dmesg 257sub parse_dmesg
@@ -246,6 +288,23 @@ sub skip_parse
246 return skip($path, \@skip_parse_files_abs, \@skip_parse_files_any); 288 return skip($path, \@skip_parse_files_abs, \@skip_parse_files_any);
247} 289}
248 290
291sub timed_parse_file
292{
293 my ($file) = @_;
294
295 eval {
296 local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required.
297 alarm $TIMEOUT;
298 parse_file($file);
299 alarm 0;
300 };
301
302 if ($@) {
303 die unless $@ eq "alarm\n"; # Propagate unexpected errors.
304 printf STDERR "timed out parsing: %s\n", $file;
305 }
306}
307
249sub parse_file 308sub parse_file
250{ 309{
251 my ($file) = @_; 310 my ($file) = @_;
@@ -281,7 +340,6 @@ sub skip_walk
281sub walk 340sub walk
282{ 341{
283 my @dirs = @_; 342 my @dirs = @_;
284 my %seen;
285 343
286 while (my $pwd = shift @dirs) { 344 while (my $pwd = shift @dirs) {
287 next if (skip_walk($pwd)); 345 next if (skip_walk($pwd));
@@ -298,8 +356,146 @@ sub walk
298 if (-d $path) { 356 if (-d $path) {
299 push @dirs, $path; 357 push @dirs, $path;
300 } else { 358 } else {
301 parse_file($path); 359 timed_parse_file($path);
302 } 360 }
303 } 361 }
304 } 362 }
305} 363}
364
365sub format_output
366{
367 my ($file) = @_;
368
369 # Default is to show raw results.
370 if ($raw or (!$squash_by_path and !$squash_by_filename)) {
371 dump_raw_output($file);
372 return;
373 }
374
375 my ($total, $dmesg, $paths, $files) = parse_raw_file($file);
376
377 printf "\nTotal number of results from scan (incl dmesg): %d\n", $total;
378
379 if (!$suppress_dmesg) {
380 print_dmesg($dmesg);
381 }
382
383 if ($squash_by_filename) {
384 squash_by($files, 'filename');
385 }
386
387 if ($squash_by_path) {
388 squash_by($paths, 'path');
389 }
390}
391
392sub dump_raw_output
393{
394 my ($file) = @_;
395
396 open (my $fh, '<', $file) or die "$0: $file: $!\n";
397 while (<$fh>) {
398 if ($suppress_dmesg) {
399 if ("dmesg:" eq substr($_, 0, 6)) {
400 next;
401 }
402 }
403 print $_;
404 }
405 close $fh;
406}
407
408sub parse_raw_file
409{
410 my ($file) = @_;
411
412 my $total = 0; # Total number of lines parsed.
413 my @dmesg; # dmesg output.
414 my %files; # Unique filenames containing leaks.
415 my %paths; # Unique paths containing leaks.
416
417 open (my $fh, '<', $file) or die "$0: $file: $!\n";
418 while (my $line = <$fh>) {
419 $total++;
420
421 if ("dmesg:" eq substr($line, 0, 6)) {
422 push @dmesg, $line;
423 next;
424 }
425
426 cache_path(\%paths, $line);
427 cache_filename(\%files, $line);
428 }
429
430 return $total, \@dmesg, \%paths, \%files;
431}
432
433sub print_dmesg
434{
435 my ($dmesg) = @_;
436
437 print "\ndmesg output:\n";
438
439 if (@$dmesg == 0) {
440 print "<no results>\n";
441 return;
442 }
443
444 foreach(@$dmesg) {
445 my $index = index($_, ': ');
446 $index += 2; # skid ': '
447 print substr($_, $index);
448 }
449}
450
451sub squash_by
452{
453 my ($ref, $desc) = @_;
454
455 print "\nResults squashed by $desc (excl dmesg). ";
456 print "Displaying [<number of results> <$desc>], <example result>\n";
457
458 if (keys %$ref == 0) {
459 print "<no results>\n";
460 return;
461 }
462
463 foreach(keys %$ref) {
464 my $lines = $ref->{$_};
465 my $length = @$lines;
466 printf "[%d %s] %s", $length, $_, @$lines[0];
467 }
468}
469
470sub cache_path
471{
472 my ($paths, $line) = @_;
473
474 my $index = index($line, ': ');
475 my $path = substr($line, 0, $index);
476
477 $index += 2; # skip ': '
478 add_to_cache($paths, $path, substr($line, $index));
479}
480
481sub cache_filename
482{
483 my ($files, $line) = @_;
484
485 my $index = index($line, ': ');
486 my $path = substr($line, 0, $index);
487 my $filename = basename($path);
488
489 $index += 2; # skip ': '
490 add_to_cache($files, $filename, substr($line, $index));
491}
492
493sub add_to_cache
494{
495 my ($cache, $key, $value) = @_;
496
497 if (!$cache->{$key}) {
498 $cache->{$key} = ();
499 }
500 push @{$cache->{$key}}, $value;
501}