0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021 use warnings;
0022 use strict;
0023 use POSIX;
0024 use File::Basename;
0025 use File::Spec;
0026 use Cwd 'abs_path';
0027 use Term::ANSIColor ;
0028 use Getopt::Long ;
0029 use Config;
0030 use bigint ;
0031 use feature 'state';
0032
0033 my $P = $0;
0034
0035
0036 my @DIRS = ('/proc', '/sys');
0037
0038
0039 my $TIMEOUT = 10;
0040
0041
0042
0043 my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64', 'x86');
0044
0045
0046 my $help = 0;
0047 my $debug = 0;
0048 my $raw = 0;
0049 my $output_raw = "";
0050 my $input_raw = "";
0051 my $suppress_dmesg = 0;
0052 my $squash_by_path = 0;
0053 my $squash_by_filename = 0;
0054 my $kernel_config_file = "";
0055 my $opt_32bit = 0;
0056 my $page_offset_32bit = 0;
0057
0058
0059 my @skip_abs = (
0060 '/proc/kmsg',
0061 '/proc/device-tree',
0062 '/proc/1/syscall',
0063 '/sys/firmware/devicetree',
0064 '/sys/kernel/debug/tracing/trace_pipe',
0065 '/sys/kernel/security/apparmor/revision');
0066
0067
0068 my @skip_any = (
0069 'pagemap',
0070 'events',
0071 'access',
0072 'registers',
0073 'snapshot_raw',
0074 'trace_pipe_raw',
0075 'ptmx',
0076 'trace_pipe',
0077 'fd',
0078 'usbmon');
0079
0080 sub help
0081 {
0082 my ($exitcode) = @_;
0083
0084 print << "EOM";
0085
0086 Usage: $P [OPTIONS]
0087
0088 Options:
0089
0090 -o, --output-raw=<file> Save results for future processing.
0091 -i, --input-raw=<file> Read results from file instead of scanning.
0092 --raw Show raw results (default).
0093 --suppress-dmesg Do not show dmesg results.
0094 --squash-by-path Show one result per unique path.
0095 --squash-by-filename Show one result per unique filename.
0096 --kernel-config-file=<file> Kernel configuration file (e.g /boot/config)
0097 --32-bit Scan 32-bit kernel.
0098 --page-offset-32-bit=o Page offset (for 32-bit kernel 0xABCD1234).
0099 -d, --debug Display debugging output.
0100 -h, --help Display this help and exit.
0101
0102 Scans the running kernel for potential leaking addresses.
0103
0104 EOM
0105 exit($exitcode);
0106 }
0107
0108 GetOptions(
0109 'd|debug' => \$debug,
0110 'h|help' => \$help,
0111 'o|output-raw=s' => \$output_raw,
0112 'i|input-raw=s' => \$input_raw,
0113 'suppress-dmesg' => \$suppress_dmesg,
0114 'squash-by-path' => \$squash_by_path,
0115 'squash-by-filename' => \$squash_by_filename,
0116 'raw' => \$raw,
0117 'kernel-config-file=s' => \$kernel_config_file,
0118 '32-bit' => \$opt_32bit,
0119 'page-offset-32-bit=o' => \$page_offset_32bit,
0120 ) or help(1);
0121
0122 help(0) if ($help);
0123
0124 if ($input_raw) {
0125 format_output($input_raw);
0126 exit(0);
0127 }
0128
0129 if (!$input_raw and ($squash_by_path or $squash_by_filename)) {
0130 printf "\nSummary reporting only available with --input-raw=<file>\n";
0131 printf "(First run scan with --output-raw=<file>.)\n";
0132 exit(128);
0133 }
0134
0135 if (!(is_supported_architecture() or $opt_32bit or $page_offset_32bit)) {
0136 printf "\nScript does not support your architecture, sorry.\n";
0137 printf "\nCurrently we support: \n\n";
0138 foreach(@SUPPORTED_ARCHITECTURES) {
0139 printf "\t%s\n", $_;
0140 }
0141 printf("\n");
0142
0143 printf("If you are running a 32-bit architecture you may use:\n");
0144 printf("\n\t--32-bit or --page-offset-32-bit=<page offset>\n\n");
0145
0146 my $archname = `uname -m`;
0147 printf("Machine hardware name (`uname -m`): %s\n", $archname);
0148
0149 exit(129);
0150 }
0151
0152 if ($output_raw) {
0153 open my $fh, '>', $output_raw or die "$0: $output_raw: $!\n";
0154 select $fh;
0155 }
0156
0157 parse_dmesg();
0158 walk(@DIRS);
0159
0160 exit 0;
0161
0162 sub dprint
0163 {
0164 printf(STDERR @_) if $debug;
0165 }
0166
0167 sub is_supported_architecture
0168 {
0169 return (is_x86_64() or is_ppc64() or is_ix86_32());
0170 }
0171
0172 sub is_32bit
0173 {
0174
0175 if ($opt_32bit or $page_offset_32bit) {
0176 return 1;
0177 }
0178
0179 return is_ix86_32();
0180 }
0181
0182 sub is_ix86_32
0183 {
0184 state $arch = `uname -m`;
0185
0186 chomp $arch;
0187 if ($arch =~ ) {
0188 return 1;
0189 }
0190 return 0;
0191 }
0192
0193 sub is_arch
0194 {
0195 my ($desc) = @_;
0196 my $arch = `uname -m`;
0197
0198 chomp $arch;
0199 if ($arch eq $desc) {
0200 return 1;
0201 }
0202 return 0;
0203 }
0204
0205 sub is_x86_64
0206 {
0207 state $is = is_arch('x86_64');
0208 return $is;
0209 }
0210
0211 sub is_ppc64
0212 {
0213 state $is = is_arch('ppc64');
0214 return $is;
0215 }
0216
0217
0218
0219 sub get_kernel_config_option
0220 {
0221 my ($option) = @_;
0222 my $value = "";
0223 my $tmp_file = "";
0224 my @config_files;
0225
0226
0227 if ($kernel_config_file ne "") {
0228 @config_files = ($kernel_config_file);
0229 } elsif (-R "/proc/config.gz") {
0230 my $tmp_file = "/tmp/tmpkconf";
0231
0232 if (system("gunzip < /proc/config.gz > $tmp_file")) {
0233 dprint("system(gunzip < /proc/config.gz) failed\n");
0234 return "";
0235 } else {
0236 @config_files = ($tmp_file);
0237 }
0238 } else {
0239 my $file = '/boot/config-' . `uname -r`;
0240 chomp $file;
0241 @config_files = ($file, '/boot/config');
0242 }
0243
0244 foreach my $file (@config_files) {
0245 dprint("parsing config file: $file\n");
0246 $value = option_from_file($option, $file);
0247 if ($value ne "") {
0248 last;
0249 }
0250 }
0251
0252 if ($tmp_file ne "") {
0253 system("rm -f $tmp_file");
0254 }
0255
0256 return $value;
0257 }
0258
0259
0260 sub option_from_file
0261 {
0262 my ($option, $file) = @_;
0263 my $str = "";
0264 my $val = "";
0265
0266 open(my $fh, "<", $file) or return "";
0267 while (my $line = <$fh> ) {
0268 if ($line =~ /^$option/) {
0269 ($str, $val) = split /=/, $line;
0270 chomp $val;
0271 last;
0272 }
0273 }
0274
0275 close $fh;
0276 return $val;
0277 }
0278
0279 sub is_false_positive
0280 {
0281 my ($match) = @_;
0282
0283 if (is_32bit()) {
0284 return is_false_positive_32bit($match);
0285 }
0286
0287
0288
0289 if ($match =~ '\b(0x)?(f|F){16}\b' or
0290 $match =~ '\b(0x)?0{16}\b') {
0291 return 1;
0292 }
0293
0294 if (is_x86_64() and is_in_vsyscall_memory_region($match)) {
0295 return 1;
0296 }
0297
0298 return 0;
0299 }
0300
0301 sub is_false_positive_32bit
0302 {
0303 my ($match) = @_;
0304 state $page_offset = get_page_offset();
0305
0306 if ($match =~ '\b(0x)?(f|F){8}\b') {
0307 return 1;
0308 }
0309
0310 if (hex($match) < $page_offset) {
0311 return 1;
0312 }
0313
0314 return 0;
0315 }
0316
0317
0318 sub get_page_offset
0319 {
0320 my $page_offset;
0321 my $default_offset = 0xc0000000;
0322
0323
0324 if ($page_offset_32bit != 0) {
0325 return $page_offset_32bit;
0326 }
0327
0328 $page_offset = get_kernel_config_option('CONFIG_PAGE_OFFSET');
0329 if (!$page_offset) {
0330 return $default_offset;
0331 }
0332 return $page_offset;
0333 }
0334
0335 sub is_in_vsyscall_memory_region
0336 {
0337 my ($match) = @_;
0338
0339 my $hex = hex($match);
0340 my $region_min = hex("0xffffffffff600000");
0341 my $region_max = hex("0xffffffffff601000");
0342
0343 return ($hex >= $region_min and $hex <= $region_max);
0344 }
0345
0346
0347 sub may_leak_address
0348 {
0349 my ($line) = @_;
0350 my $address_re;
0351
0352
0353 if ($line =~ '^SigBlk:' or
0354 $line =~ '^SigIgn:' or
0355 $line =~ '^SigCgt:') {
0356 return 0;
0357 }
0358
0359 if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or
0360 $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') {
0361 return 0;
0362 }
0363
0364 $address_re = get_address_re();
0365 while ($line =~ /($address_re)/g) {
0366 if (!is_false_positive($1)) {
0367 return 1;
0368 }
0369 }
0370
0371 return 0;
0372 }
0373
0374 sub get_address_re
0375 {
0376 if (is_ppc64()) {
0377 return '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b';
0378 } elsif (is_32bit()) {
0379 return '\b(0x)?[[:xdigit:]]{8}\b';
0380 }
0381
0382 return get_x86_64_re();
0383 }
0384
0385 sub get_x86_64_re
0386 {
0387
0388
0389
0390
0391 state $ptl = get_kernel_config_option('CONFIG_PGTABLE_LEVELS');
0392
0393 if ($ptl == 5) {
0394 return '\b(0x)?ff[[:xdigit:]]{14}\b';
0395 }
0396 return '\b(0x)?ffff[[:xdigit:]]{12}\b';
0397 }
0398
0399 sub parse_dmesg
0400 {
0401 open my $cmd, '-|', 'dmesg';
0402 while (<$cmd>) {
0403 if (may_leak_address($_)) {
0404 print 'dmesg: ' . $_;
0405 }
0406 }
0407 close $cmd;
0408 }
0409
0410
0411 sub skip
0412 {
0413 my ($path) = @_;
0414
0415 foreach (@skip_abs) {
0416 return 1 if (/^$path$/);
0417 }
0418
0419 my($filename, $dirs, $suffix) = fileparse($path);
0420 foreach (@skip_any) {
0421 return 1 if (/^$filename$/);
0422 }
0423
0424 return 0;
0425 }
0426
0427 sub timed_parse_file
0428 {
0429 my ($file) = @_;
0430
0431 eval {
0432 local $SIG{ALRM} = sub { die "alarm\n" };
0433 alarm $TIMEOUT;
0434 parse_file($file);
0435 alarm 0;
0436 };
0437
0438 if ($@) {
0439 die unless $@ eq "alarm\n";
0440 printf STDERR "timed out parsing: %s\n", $file;
0441 }
0442 }
0443
0444 sub parse_file
0445 {
0446 my ($file) = @_;
0447
0448 if (! -R $file) {
0449 return;
0450 }
0451
0452 if (! -T $file) {
0453 return;
0454 }
0455
0456 open my $fh, "<", $file or return;
0457 while ( <$fh> ) {
0458 chomp;
0459 if (may_leak_address($_)) {
0460 printf("$file: $_\n");
0461 }
0462 }
0463 close $fh;
0464 }
0465
0466
0467 sub check_path_for_leaks
0468 {
0469 my ($path) = @_;
0470
0471 if (may_leak_address($path)) {
0472 printf("Path name may contain address: $path\n");
0473 }
0474 }
0475
0476
0477 sub walk
0478 {
0479 my @dirs = @_;
0480
0481 while (my $pwd = shift @dirs) {
0482 next if (!opendir(DIR, $pwd));
0483 my @files = readdir(DIR);
0484 closedir(DIR);
0485
0486 foreach my $file (@files) {
0487 next if ($file eq '.' or $file eq '..');
0488
0489 my $path = "$pwd/$file";
0490 next if (-l $path);
0491
0492
0493 next if (($path =~ /^\/proc\/[0-9]+$/) &&
0494 ($path !~ /^\/proc\/1$/));
0495
0496 next if (skip($path));
0497
0498 check_path_for_leaks($path);
0499
0500 if (-d $path) {
0501 push @dirs, $path;
0502 next;
0503 }
0504
0505 dprint("parsing: $path\n");
0506 timed_parse_file($path);
0507 }
0508 }
0509 }
0510
0511 sub format_output
0512 {
0513 my ($file) = @_;
0514
0515
0516 if ($raw or (!$squash_by_path and !$squash_by_filename)) {
0517 dump_raw_output($file);
0518 return;
0519 }
0520
0521 my ($total, $dmesg, $paths, $files) = parse_raw_file($file);
0522
0523 printf "\nTotal number of results from scan (incl dmesg): %d\n", $total;
0524
0525 if (!$suppress_dmesg) {
0526 print_dmesg($dmesg);
0527 }
0528
0529 if ($squash_by_filename) {
0530 squash_by($files, 'filename');
0531 }
0532
0533 if ($squash_by_path) {
0534 squash_by($paths, 'path');
0535 }
0536 }
0537
0538 sub dump_raw_output
0539 {
0540 my ($file) = @_;
0541
0542 open (my $fh, '<', $file) or die "$0: $file: $!\n";
0543 while (<$fh>) {
0544 if ($suppress_dmesg) {
0545 if ("dmesg:" eq substr($_, 0, 6)) {
0546 next;
0547 }
0548 }
0549 print $_;
0550 }
0551 close $fh;
0552 }
0553
0554 sub parse_raw_file
0555 {
0556 my ($file) = @_;
0557
0558 my $total = 0;
0559 my @dmesg;
0560 my %files;
0561 my %paths;
0562
0563 open (my $fh, '<', $file) or die "$0: $file: $!\n";
0564 while (my $line = <$fh>) {
0565 $total++;
0566
0567 if ("dmesg:" eq substr($line, 0, 6)) {
0568 push @dmesg, $line;
0569 next;
0570 }
0571
0572 cache_path(\%paths, $line);
0573 cache_filename(\%files, $line);
0574 }
0575
0576 return $total, \@dmesg, \%paths, \%files;
0577 }
0578
0579 sub print_dmesg
0580 {
0581 my ($dmesg) = @_;
0582
0583 print "\ndmesg output:\n";
0584
0585 if (@$dmesg == 0) {
0586 print "<no results>\n";
0587 return;
0588 }
0589
0590 foreach(@$dmesg) {
0591 my $index = index($_, ': ');
0592 $index += 2;
0593 print substr($_, $index);
0594 }
0595 }
0596
0597 sub squash_by
0598 {
0599 my ($ref, $desc) = @_;
0600
0601 print "\nResults squashed by $desc (excl dmesg). ";
0602 print "Displaying [<number of results> <$desc>], <example result>\n";
0603
0604 if (keys %$ref == 0) {
0605 print "<no results>\n";
0606 return;
0607 }
0608
0609 foreach(keys %$ref) {
0610 my $lines = $ref->{$_};
0611 my $length = @$lines;
0612 printf "[%d %s] %s", $length, $_, @$lines[0];
0613 }
0614 }
0615
0616 sub cache_path
0617 {
0618 my ($paths, $line) = @_;
0619
0620 my $index = index($line, ': ');
0621 my $path = substr($line, 0, $index);
0622
0623 $index += 2;
0624 add_to_cache($paths, $path, substr($line, $index));
0625 }
0626
0627 sub cache_filename
0628 {
0629 my ($files, $line) = @_;
0630
0631 my $index = index($line, ': ');
0632 my $path = substr($line, 0, $index);
0633 my $filename = basename($path);
0634
0635 $index += 2;
0636 add_to_cache($files, $filename, substr($line, $index));
0637 }
0638
0639 sub add_to_cache
0640 {
0641 my ($cache, $key, $value) = @_;
0642
0643 if (!$cache->{$key}) {
0644 $cache->{$key} = ();
0645 }
0646 push @{$cache->{$key}}, $value;
0647 }