aboutsummaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2018-04-07 14:56:33 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2018-04-07 14:56:33 -0400
commit299f89d53e61c0b17479cc7d6f3b5382d5e83f28 (patch)
tree05ee7ec5e5fb6cc61144a7f6ac3123c3e341f607 /scripts
parentfc22e19a114f000da4db2ed0ed82023c44d38a8c (diff)
parente875d33d7f06d1107c057d12bb5aaba84738e418 (diff)
Merge tag 'leaks-4.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tobin/leaks
Pull leaking-addresses updates from Tobin Harding: "This set represents improvements to the scripts/leaking_addresses.pl script. The major improvement is that with this set applied the script actually runs in a reasonable amount of time (less than a minute on a standard stock Ubuntu user desktop). Also, we have a second maintainer now and a tree hosted on kernel.org We do a few code clean ups. We fix the command help output. Handling of the vsyscall address range is fixed to check the whole range instead of just the start/end addresses. We add support for 5 page table levels (suggested on LKML). We use a system command to get the machine architecture instead of using Perl. Calling this command for every regex comparison is what previously choked the script, caching the result of this call gave the major speed improvement. We add support for scanning 32-bit kernels using the user/kernel memory split. Path skipping code refactored and simplified (meaning easier script configuration). We remove version numbering. We add a variable name to improve readability of a regex and finally we check filenames for leaking addresses. Currently script scans /proc/PID for all PID. With this set applied we only scan for PID==1. It was observed that on an idle system files under /proc/PID are predominantly the same for all processes. Also it was noted that the script does not scan _all_ the kernel since it only scans active processes. Scanning only for PID==1 makes explicit the inherent flaw in the script that the scan is only partial and also speeds things up" * tag 'leaks-4.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tobin/leaks: MAINTAINERS: Update LEAKING_ADDRESSES leaking_addresses: check if file name contains address leaking_addresses: explicitly name variable used in regex leaking_addresses: remove version number leaking_addresses: skip '/proc/1/syscall' leaking_addresses: skip all /proc/PID except /proc/1 leaking_addresses: cache architecture name leaking_addresses: simplify path skipping leaking_addresses: do not parse binary files leaking_addresses: add 32-bit support leaking_addresses: add is_arch() wrapper subroutine leaking_addresses: use system command to get arch leaking_addresses: add support for 5 page table levels leaking_addresses: add support for kernel config file leaking_addresses: add range check for vsyscall memory leaking_addresses: indent dependant options leaking_addresses: remove command examples leaking_addresses: remove mention of kptr_restrict leaking_addresses: fix typo function not called
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/leaking_addresses.pl372
1 files changed, 259 insertions, 113 deletions
diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl
index bc5788000018..6a897788f5a7 100755
--- a/scripts/leaking_addresses.pl
+++ b/scripts/leaking_addresses.pl
@@ -3,15 +3,20 @@
3# (c) 2017 Tobin C. Harding <me@tobin.cc> 3# (c) 2017 Tobin C. Harding <me@tobin.cc>
4# Licensed under the terms of the GNU GPL License version 2 4# Licensed under the terms of the GNU GPL License version 2
5# 5#
6# leaking_addresses.pl: Scan 64 bit kernel for potential leaking addresses. 6# leaking_addresses.pl: Scan the kernel for potential leaking addresses.
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# 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
11# cause the script to choke. 11# cause the script to choke.
12
12# 13#
13# You may like to set kptr_restrict=2 before running script 14# When the system is idle it is likely that most files under /proc/PID will be
14# (see Documentation/sysctl/kernel.txt). 15# identical for various processes. Scanning _all_ the PIDs under /proc is
16# unnecessary and implies that we are thoroughly scanning /proc. This is _not_
17# the case because there may be ways userspace can trigger creation of /proc
18# files that leak addresses but were not present during a scan. For these two
19# reasons we exclude all PID directories under /proc except '1/'
15 20
16use warnings; 21use warnings;
17use strict; 22use strict;
@@ -22,9 +27,10 @@ use Cwd 'abs_path';
22use Term::ANSIColor qw(:constants); 27use Term::ANSIColor qw(:constants);
23use Getopt::Long qw(:config no_auto_abbrev); 28use Getopt::Long qw(:config no_auto_abbrev);
24use Config; 29use Config;
30use bigint qw/hex/;
31use feature 'state';
25 32
26my $P = $0; 33my $P = $0;
27my $V = '0.01';
28 34
29# Directories to scan. 35# Directories to scan.
30my @DIRS = ('/proc', '/sys'); 36my @DIRS = ('/proc', '/sys');
@@ -32,10 +38,9 @@ my @DIRS = ('/proc', '/sys');
32# Timer for parsing each file, in seconds. 38# Timer for parsing each file, in seconds.
33my $TIMEOUT = 10; 39my $TIMEOUT = 10;
34 40
35# Script can only grep for kernel addresses on the following architectures. If 41# Kernel addresses vary by architecture. We can only auto-detect the following
36# your architecture is not listed here and has a grep'able kernel address please 42# architectures (using `uname -m`). (flag --32-bit overrides auto-detection.)
37# consider submitting a patch. 43my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64', 'x86');
38my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64');
39 44
40# Command line options. 45# Command line options.
41my $help = 0; 46my $help = 0;
@@ -43,46 +48,34 @@ my $debug = 0;
43my $raw = 0; 48my $raw = 0;
44my $output_raw = ""; # Write raw results to file. 49my $output_raw = ""; # Write raw results to file.
45my $input_raw = ""; # Read raw results from file instead of scanning. 50my $input_raw = ""; # Read raw results from file instead of scanning.
46
47my $suppress_dmesg = 0; # Don't show dmesg in output. 51my $suppress_dmesg = 0; # Don't show dmesg in output.
48my $squash_by_path = 0; # Summary report grouped by absolute path. 52my $squash_by_path = 0; # Summary report grouped by absolute path.
49my $squash_by_filename = 0; # Summary report grouped by filename. 53my $squash_by_filename = 0; # Summary report grouped by filename.
50 54my $kernel_config_file = ""; # Kernel configuration file.
51# Do not parse these files (absolute path). 55my $opt_32bit = 0; # Scan 32-bit kernel.
52my @skip_parse_files_abs = ('/proc/kmsg', 56my $page_offset_32bit = 0; # Page offset for 32-bit kernel.
53 '/proc/kcore', 57
54 '/proc/fs/ext4/sdb1/mb_groups', 58# Skip these absolute paths.
55 '/proc/1/fd/3', 59my @skip_abs = (
56 '/sys/firmware/devicetree', 60 '/proc/kmsg',
57 '/proc/device-tree', 61 '/proc/device-tree',
58 '/sys/kernel/debug/tracing/trace_pipe', 62 '/proc/1/syscall',
59 '/sys/kernel/security/apparmor/revision'); 63 '/sys/firmware/devicetree',
60 64 '/sys/kernel/debug/tracing/trace_pipe',
61# Do not parse these files under any subdirectory. 65 '/sys/kernel/security/apparmor/revision');
62my @skip_parse_files_any = ('0', 66
63 '1', 67# Skip these under any subdirectory.
64 '2', 68my @skip_any = (
65 'pagemap', 69 'pagemap',
66 'events', 70 'events',
67 'access', 71 'access',
68 'registers', 72 'registers',
69 'snapshot_raw', 73 'snapshot_raw',
70 'trace_pipe_raw', 74 'trace_pipe_raw',
71 'ptmx', 75 'ptmx',
72 'trace_pipe'); 76 'trace_pipe',
73 77 'fd',
74# Do not walk these directories (absolute path). 78 'usbmon');
75my @skip_walk_dirs_abs = ();
76
77# Do not walk these directories under any subdirectory.
78my @skip_walk_dirs_any = ('self',
79 'thread-self',
80 'cwd',
81 'fd',
82 'usbmon',
83 'stderr',
84 'stdin',
85 'stdout');
86 79
87sub help 80sub help
88{ 81{
@@ -91,31 +84,22 @@ sub help
91 print << "EOM"; 84 print << "EOM";
92 85
93Usage: $P [OPTIONS] 86Usage: $P [OPTIONS]
94Version: $V
95 87
96Options: 88Options:
97 89
98 -o, --output-raw=<file> Save results for future processing. 90 -o, --output-raw=<file> Save results for future processing.
99 -i, --input-raw=<file> Read results from file instead of scanning. 91 -i, --input-raw=<file> Read results from file instead of scanning.
100 --raw Show raw results (default). 92 --raw Show raw results (default).
101 --suppress-dmesg Do not show dmesg results. 93 --suppress-dmesg Do not show dmesg results.
102 --squash-by-path Show one result per unique path. 94 --squash-by-path Show one result per unique path.
103 --squash-by-filename Show one result per unique filename. 95 --squash-by-filename Show one result per unique filename.
104 -d, --debug Display debugging output. 96 --kernel-config-file=<file> Kernel configuration file (e.g /boot/config)
105 -h, --help, --version Display this help and exit. 97 --32-bit Scan 32-bit kernel.
106 98 --page-offset-32-bit=o Page offset (for 32-bit kernel 0xABCD1234).
107Examples: 99 -d, --debug Display debugging output.
108 100 -h, --help, --version Display this help and exit.
109 # Scan kernel and dump raw results.
110 $0
111
112 # Scan kernel and save results to file.
113 $0 --output-raw scan.out
114 101
115 # View summary report. 102Scans the running kernel for potential leaking addresses.
116 $0 --input-raw scan.out --squash-by-filename
117
118Scans the running (64 bit) kernel for potential leaking addresses.
119 103
120EOM 104EOM
121 exit($exitcode); 105 exit($exitcode);
@@ -131,6 +115,9 @@ GetOptions(
131 'squash-by-path' => \$squash_by_path, 115 'squash-by-path' => \$squash_by_path,
132 'squash-by-filename' => \$squash_by_filename, 116 'squash-by-filename' => \$squash_by_filename,
133 'raw' => \$raw, 117 'raw' => \$raw,
118 'kernel-config-file=s' => \$kernel_config_file,
119 '32-bit' => \$opt_32bit,
120 'page-offset-32-bit=o' => \$page_offset_32bit,
134) or help(1); 121) or help(1);
135 122
136help(0) if ($help); 123help(0) if ($help);
@@ -146,16 +133,19 @@ if (!$input_raw and ($squash_by_path or $squash_by_filename)) {
146 exit(128); 133 exit(128);
147} 134}
148 135
149if (!is_supported_architecture()) { 136if (!(is_supported_architecture() or $opt_32bit or $page_offset_32bit)) {
150 printf "\nScript does not support your architecture, sorry.\n"; 137 printf "\nScript does not support your architecture, sorry.\n";
151 printf "\nCurrently we support: \n\n"; 138 printf "\nCurrently we support: \n\n";
152 foreach(@SUPPORTED_ARCHITECTURES) { 139 foreach(@SUPPORTED_ARCHITECTURES) {
153 printf "\t%s\n", $_; 140 printf "\t%s\n", $_;
154 } 141 }
142 printf("\n");
143
144 printf("If you are running a 32-bit architecture you may use:\n");
145 printf("\n\t--32-bit or --page-offset-32-bit=<page offset>\n\n");
155 146
156 my $archname = $Config{archname}; 147 my $archname = `uname -m`;
157 printf "\n\$ perl -MConfig -e \'print \"\$Config{archname}\\n\"\'\n"; 148 printf("Machine hardware name (`uname -m`): %s\n", $archname);
158 printf "%s\n", $archname;
159 149
160 exit(129); 150 exit(129);
161} 151}
@@ -177,49 +167,183 @@ sub dprint
177 167
178sub is_supported_architecture 168sub is_supported_architecture
179{ 169{
180 return (is_x86_64() or is_ppc64()); 170 return (is_x86_64() or is_ppc64() or is_ix86_32());
181} 171}
182 172
183sub is_x86_64 173sub is_32bit
184{ 174{
185 my $archname = $Config{archname}; 175 # Allow --32-bit or --page-offset-32-bit to override
186 176 if ($opt_32bit or $page_offset_32bit) {
187 if ($archname =~ m/x86_64/) {
188 return 1; 177 return 1;
189 } 178 }
190 return 0; 179
180 return is_ix86_32();
181}
182
183sub is_ix86_32
184{
185 state $arch = `uname -m`;
186
187 chomp $arch;
188 if ($arch =~ m/i[3456]86/) {
189 return 1;
190 }
191 return 0;
192}
193
194sub is_arch
195{
196 my ($desc) = @_;
197 my $arch = `uname -m`;
198
199 chomp $arch;
200 if ($arch eq $desc) {
201 return 1;
202 }
203 return 0;
204}
205
206sub is_x86_64
207{
208 state $is = is_arch('x86_64');
209 return $is;
191} 210}
192 211
193sub is_ppc64 212sub is_ppc64
194{ 213{
195 my $archname = $Config{archname}; 214 state $is = is_arch('ppc64');
215 return $is;
216}
196 217
197 if ($archname =~ m/powerpc/ and $archname =~ m/64/) { 218# Gets config option value from kernel config file.
198 return 1; 219# Returns "" on error or if config option not found.
220sub get_kernel_config_option
221{
222 my ($option) = @_;
223 my $value = "";
224 my $tmp_file = "";
225 my @config_files;
226
227 # Allow --kernel-config-file to override.
228 if ($kernel_config_file ne "") {
229 @config_files = ($kernel_config_file);
230 } elsif (-R "/proc/config.gz") {
231 my $tmp_file = "/tmp/tmpkconf";
232
233 if (system("gunzip < /proc/config.gz > $tmp_file")) {
234 dprint "$0: system(gunzip < /proc/config.gz) failed\n";
235 return "";
236 } else {
237 @config_files = ($tmp_file);
238 }
239 } else {
240 my $file = '/boot/config-' . `uname -r`;
241 chomp $file;
242 @config_files = ($file, '/boot/config');
199 } 243 }
200 return 0; 244
245 foreach my $file (@config_files) {
246 dprint("parsing config file: %s\n", $file);
247 $value = option_from_file($option, $file);
248 if ($value ne "") {
249 last;
250 }
251 }
252
253 if ($tmp_file ne "") {
254 system("rm -f $tmp_file");
255 }
256
257 return $value;
258}
259
260# Parses $file and returns kernel configuration option value.
261sub option_from_file
262{
263 my ($option, $file) = @_;
264 my $str = "";
265 my $val = "";
266
267 open(my $fh, "<", $file) or return "";
268 while (my $line = <$fh> ) {
269 if ($line =~ /^$option/) {
270 ($str, $val) = split /=/, $line;
271 chomp $val;
272 last;
273 }
274 }
275
276 close $fh;
277 return $val;
201} 278}
202 279
203sub is_false_positive 280sub is_false_positive
204{ 281{
205 my ($match) = @_; 282 my ($match) = @_;
206 283
284 if (is_32bit()) {
285 return is_false_positive_32bit($match);
286 }
287
288 # 64 bit false positives.
289
207 if ($match =~ '\b(0x)?(f|F){16}\b' or 290 if ($match =~ '\b(0x)?(f|F){16}\b' or
208 $match =~ '\b(0x)?0{16}\b') { 291 $match =~ '\b(0x)?0{16}\b') {
209 return 1; 292 return 1;
210 } 293 }
211 294
212 if (is_x86_64) { 295 if (is_x86_64() and is_in_vsyscall_memory_region($match)) {
213 # vsyscall memory region, we should probably check against a range here. 296 return 1;
214 if ($match =~ '\bf{10}600000\b' or
215 $match =~ '\bf{10}601000\b') {
216 return 1;
217 }
218 } 297 }
219 298
220 return 0; 299 return 0;
221} 300}
222 301
302sub is_false_positive_32bit
303{
304 my ($match) = @_;
305 state $page_offset = get_page_offset();
306
307 if ($match =~ '\b(0x)?(f|F){8}\b') {
308 return 1;
309 }
310
311 if (hex($match) < $page_offset) {
312 return 1;
313 }
314
315 return 0;
316}
317
318# returns integer value
319sub get_page_offset
320{
321 my $page_offset;
322 my $default_offset = 0xc0000000;
323
324 # Allow --page-offset-32bit to override.
325 if ($page_offset_32bit != 0) {
326 return $page_offset_32bit;
327 }
328
329 $page_offset = get_kernel_config_option('CONFIG_PAGE_OFFSET');
330 if (!$page_offset) {
331 return $default_offset;
332 }
333 return $page_offset;
334}
335
336sub is_in_vsyscall_memory_region
337{
338 my ($match) = @_;
339
340 my $hex = hex($match);
341 my $region_min = hex("0xffffffffff600000");
342 my $region_max = hex("0xffffffffff601000");
343
344 return ($hex >= $region_min and $hex <= $region_max);
345}
346
223# True if argument potentially contains a kernel address. 347# True if argument potentially contains a kernel address.
224sub may_leak_address 348sub may_leak_address
225{ 349{
@@ -238,14 +362,8 @@ sub may_leak_address
238 return 0; 362 return 0;
239 } 363 }
240 364
241 # One of these is guaranteed to be true. 365 $address_re = get_address_re();
242 if (is_x86_64()) { 366 while ($line =~ /($address_re)/g) {
243 $address_re = '\b(0x)?ffff[[:xdigit:]]{12}\b';
244 } elsif (is_ppc64()) {
245 $address_re = '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b';
246 }
247
248 while (/($address_re)/g) {
249 if (!is_false_positive($1)) { 367 if (!is_false_positive($1)) {
250 return 1; 368 return 1;
251 } 369 }
@@ -254,6 +372,31 @@ sub may_leak_address
254 return 0; 372 return 0;
255} 373}
256 374
375sub get_address_re
376{
377 if (is_ppc64()) {
378 return '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b';
379 } elsif (is_32bit()) {
380 return '\b(0x)?[[:xdigit:]]{8}\b';
381 }
382
383 return get_x86_64_re();
384}
385
386sub get_x86_64_re
387{
388 # We handle page table levels but only if explicitly configured using
389 # CONFIG_PGTABLE_LEVELS. If config file parsing fails or config option
390 # is not found we default to using address regular expression suitable
391 # for 4 page table levels.
392 state $ptl = get_kernel_config_option('CONFIG_PGTABLE_LEVELS');
393
394 if ($ptl == 5) {
395 return '\b(0x)?ff[[:xdigit:]]{14}\b';
396 }
397 return '\b(0x)?ffff[[:xdigit:]]{12}\b';
398}
399
257sub parse_dmesg 400sub parse_dmesg
258{ 401{
259 open my $cmd, '-|', 'dmesg'; 402 open my $cmd, '-|', 'dmesg';
@@ -268,26 +411,20 @@ sub parse_dmesg
268# True if we should skip this path. 411# True if we should skip this path.
269sub skip 412sub skip
270{ 413{
271 my ($path, $paths_abs, $paths_any) = @_; 414 my ($path) = @_;
272 415
273 foreach (@$paths_abs) { 416 foreach (@skip_abs) {
274 return 1 if (/^$path$/); 417 return 1 if (/^$path$/);
275 } 418 }
276 419
277 my($filename, $dirs, $suffix) = fileparse($path); 420 my($filename, $dirs, $suffix) = fileparse($path);
278 foreach (@$paths_any) { 421 foreach (@skip_any) {
279 return 1 if (/^$filename$/); 422 return 1 if (/^$filename$/);
280 } 423 }
281 424
282 return 0; 425 return 0;
283} 426}
284 427
285sub skip_parse
286{
287 my ($path) = @_;
288 return skip($path, \@skip_parse_files_abs, \@skip_parse_files_any);
289}
290
291sub timed_parse_file 428sub timed_parse_file
292{ 429{
293 my ($file) = @_; 430 my ($file) = @_;
@@ -313,11 +450,9 @@ sub parse_file
313 return; 450 return;
314 } 451 }
315 452
316 if (skip_parse($file)) { 453 if (! -T $file) {
317 dprint "skipping file: $file\n";
318 return; 454 return;
319 } 455 }
320 dprint "parsing: $file\n";
321 456
322 open my $fh, "<", $file or return; 457 open my $fh, "<", $file or return;
323 while ( <$fh> ) { 458 while ( <$fh> ) {
@@ -328,12 +463,14 @@ sub parse_file
328 close $fh; 463 close $fh;
329} 464}
330 465
331 466# Checks if the actual path name is leaking a kernel address.
332# True if we should skip walking this directory. 467sub check_path_for_leaks
333sub skip_walk
334{ 468{
335 my ($path) = @_; 469 my ($path) = @_;
336 return skip($path, \@skip_walk_dirs_abs, \@skip_walk_dirs_any) 470
471 if (may_leak_address($path)) {
472 printf("Path name may contain address: $path\n");
473 }
337} 474}
338 475
339# Recursively walk directory tree. 476# Recursively walk directory tree.
@@ -342,7 +479,6 @@ sub walk
342 my @dirs = @_; 479 my @dirs = @_;
343 480
344 while (my $pwd = shift @dirs) { 481 while (my $pwd = shift @dirs) {
345 next if (skip_walk($pwd));
346 next if (!opendir(DIR, $pwd)); 482 next if (!opendir(DIR, $pwd));
347 my @files = readdir(DIR); 483 my @files = readdir(DIR);
348 closedir(DIR); 484 closedir(DIR);
@@ -353,11 +489,21 @@ sub walk
353 my $path = "$pwd/$file"; 489 my $path = "$pwd/$file";
354 next if (-l $path); 490 next if (-l $path);
355 491
492 # skip /proc/PID except /proc/1
493 next if (($path =~ /^\/proc\/[0-9]+$/) &&
494 ($path !~ /^\/proc\/1$/));
495
496 next if (skip($path));
497
498 check_path_for_leaks($path);
499
356 if (-d $path) { 500 if (-d $path) {
357 push @dirs, $path; 501 push @dirs, $path;
358 } else { 502 next;
359 timed_parse_file($path);
360 } 503 }
504
505 dprint "parsing: $path\n";
506 timed_parse_file($path);
361 } 507 }
362 } 508 }
363} 509}