aboutsummaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
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}