| @@ -23,6 +23,7 @@ use strict; · 22 unmodified lines |
| 23 | use POSIX; | 23 | use POSIX; |
| 24 | use File::Basename; | 24 | use File::Basename; |
| 25 | use File::Spec; | 25 | use File::Spec; |
| | 26 | use File::Temp qw/tempfile/; |
| 26 | use Cwd 'abs_path'; | 27 | use Cwd 'abs_path'; |
| 27 | use Term::ANSIColor qw(:constants); | 28 | use Term::ANSIColor qw(:constants); |
| 28 | use Getopt::Long qw(:config no_auto_abbrev); | 29 | use Getopt::Long qw(:config no_auto_abbrev); |
| @@ -51,10 +52,13 @@ my $input_raw = ""; # Read raw results from file instead of scanning. · 22 unmodified lines |
| 51 | my $suppress_dmesg = 0; # Don't show dmesg in output. | 52 | my $suppress_dmesg = 0; # Don't show dmesg in output. |
| 52 | my $squash_by_path = 0; # Summary report grouped by absolute path. | 53 | my $squash_by_path = 0; # Summary report grouped by absolute path. |
| 53 | my $squash_by_filename = 0; # Summary report grouped by filename. | 54 | my $squash_by_filename = 0; # Summary report grouped by filename. |
| | 55 | my $kallsyms_file = ""; # Kernel symbols file. |
| 54 | my $kernel_config_file = ""; # Kernel configuration file. | 56 | my $kernel_config_file = ""; # Kernel configuration file. |
| 55 | my $opt_32bit = 0; # Scan 32-bit kernel. | 57 | my $opt_32bit = 0; # Scan 32-bit kernel. |
| 56 | my $page_offset_32bit = 0; # Page offset for 32-bit kernel. | 58 | my $page_offset_32bit = 0; # Page offset for 32-bit kernel. |
| 57 | | 59 | |
| | 60 | my @kallsyms = (); |
| | 61 | |
| 58 | # Skip these absolute paths. | 62 | # Skip these absolute paths. |
| 59 | my @skip_abs = ( | 63 | my @skip_abs = ( |
| 60 | '/proc/kmsg', | 64 | '/proc/kmsg', |
| @@ -95,6 +99,8 @@ Options: · 34 unmodified lines |
| 95 | --squash-by-path Show one result per unique path. | 99 | --squash-by-path Show one result per unique path. |
| 96 | --squash-by-filename Show one result per unique filename. | 100 | --squash-by-filename Show one result per unique filename. |
| 97 | --kernel-config-file=<file> Kernel configuration file (e.g /boot/config) | 101 | --kernel-config-file=<file> Kernel configuration file (e.g /boot/config) |
| | 102 | --kallsyms=<file> Read kernel symbol addresses from file (for |
| | 103 | scanning binary files). |
| 98 | --32-bit Scan 32-bit kernel. | 104 | --32-bit Scan 32-bit kernel. |
| 99 | --page-offset-32-bit=o Page offset (for 32-bit kernel 0xABCD1234). | 105 | --page-offset-32-bit=o Page offset (for 32-bit kernel 0xABCD1234). |
| 100 | -d, --debug Display debugging output. | 106 | -d, --debug Display debugging output. |
| @@ -115,6 +121,7 @@ GetOptions( · 14 unmodified lines |
| 115 | 'squash-by-path' => \$squash_by_path, | 121 | 'squash-by-path' => \$squash_by_path, |
| 116 | 'squash-by-filename' => \$squash_by_filename, | 122 | 'squash-by-filename' => \$squash_by_filename, |
| 117 | 'raw' => \$raw, | 123 | 'raw' => \$raw, |
| | 124 | 'kallsyms=s' => \$kallsyms_file, |
| 118 | 'kernel-config-file=s' => \$kernel_config_file, | 125 | 'kernel-config-file=s' => \$kernel_config_file, |
| 119 | '32-bit' => \$opt_32bit, | 126 | '32-bit' => \$opt_32bit, |
| 120 | 'page-offset-32-bit=o' => \$page_offset_32bit, | 127 | 'page-offset-32-bit=o' => \$page_offset_32bit, |
| @@ -155,6 +162,25 @@ if ($output_raw) { · 34 unmodified lines |
| 155 | select $fh; | 162 | select $fh; |
| 156 | } | 163 | } |
| 157 | | 164 | |
| | 165 | if ($kallsyms_file) { |
| | 166 | open my $fh, '<', $kallsyms_file or die "$0: $kallsyms_file: $!\n"; |
| | 167 | while (<$fh>) { |
| | 168 | chomp; |
| | 169 | my @entry = split / /, $_; |
| | 170 | my $addr_text = $entry[0]; |
| | 171 | if ($addr_text !~ /^0/) { |
| | 172 | # TODO: Why is hex() so impossibly slow? |
| | 173 | my $addr = hex($addr_text); |
| | 174 | my $symbol = $entry[2]; |
| | 175 | # Only keep kernel text addresses. |
| | 176 | my $long = pack("J", $addr); |
| | 177 | my $entry = [$long, $symbol]; |
| | 178 | push @kallsyms, $entry; |
| | 179 | } |
| | 180 | } |
| | 181 | close $fh; |
| | 182 | } |
| | 183 | |
| 158 | parse_dmesg(); | 184 | parse_dmesg(); |
| 159 | walk(@DIRS); | 185 | walk(@DIRS); |
| 160 | | 186 | |
| @@ -221,6 +247,7 @@ sub get_kernel_config_option · 60 unmodified lines |
| 221 | { | 247 | { |
| 222 | my ($option) = @_; | 248 | my ($option) = @_; |
| 223 | my $value = ""; | 249 | my $value = ""; |
| | 250 | my $tmp_fh; |
| 224 | my $tmp_file = ""; | 251 | my $tmp_file = ""; |
| 225 | my @config_files; | 252 | my @config_files; |
| 226 | | 253 | |
| @@ -228,7 +255,8 @@ sub get_kernel_config_option · 1 unmodified line |
| 228 | if ($kernel_config_file ne "") { | 255 | if ($kernel_config_file ne "") { |
| 229 | @config_files = ($kernel_config_file); | 256 | @config_files = ($kernel_config_file); |
| 230 | } elsif (-R "/proc/config.gz") { | 257 | } elsif (-R "/proc/config.gz") { |
| 231 | my $tmp_file = "/tmp/tmpkconf"; | 258 | ($tmp_fh, $tmp_file) = tempfile("config.gz-XXXXXX", |
| | 259 | UNLINK => 1); |
| 232 | | 260 | |
| 233 | if (system("gunzip < /proc/config.gz > $tmp_file")) { | 261 | if (system("gunzip < /proc/config.gz > $tmp_file")) { |
| 234 | dprint("system(gunzip < /proc/config.gz) failed\n"); | 262 | dprint("system(gunzip < /proc/config.gz) failed\n"); |
| @@ -250,10 +278,6 @@ sub get_kernel_config_option · 15 unmodified lines |
| 250 | } | 278 | } |
| 251 | } | 279 | } |
| 252 | | 280 | |
| 253 | if ($tmp_file ne "") { | | |
| 254 | system("rm -f $tmp_file"); | | |
| 255 | } | | |
| 256 | | | |
| 257 | return $value; | 281 | return $value; |
| 258 | } | 282 | } |
| 259 | | 283 | |
| @@ -285,9 +309,10 @@ sub is_false_positive · 25 unmodified lines |
| 285 | return is_false_positive_32bit($match); | 309 | return is_false_positive_32bit($match); |
| 286 | } | 310 | } |
| 287 | | 311 | |
| 288 | # 64 bit false positives. | 312 | # Ignore 64 bit false positives: |
| 289 | | 313 | # 0xfffffffffffffff[0-f] |
| 290 | if ($match =~ '\b(0x)?(f|F){16}\b' or | 314 | # 0x0000000000000000 |
| | 315 | if ($match =~ '\b(0x)?(f|F){15}[0-9a-f]\b' or |
| 291 | $match =~ '\b(0x)?0{16}\b') { | 316 | $match =~ '\b(0x)?0{16}\b') { |
| 292 | return 1; | 317 | return 1; |
| 293 | } | 318 | } |
| @@ -304,7 +329,7 @@ sub is_false_positive_32bit · 10 unmodified lines |
| 304 | my ($match) = @_; | 329 | my ($match) = @_; |
| 305 | state $page_offset = get_page_offset(); | 330 | state $page_offset = get_page_offset(); |
| 306 | | 331 | |
| 307 | if ($match =~ '\b(0x)?(f|F){8}\b') { | 332 | if ($match =~ '\b(0x)?(f|F){7}[0-9a-f]\b') { |
| 308 | return 1; | 333 | return 1; |
| 309 | } | 334 | } |
| 310 | | 335 | |
| @@ -347,18 +372,23 @@ sub is_in_vsyscall_memory_region · 36 unmodified lines |
| 347 | # True if argument potentially contains a kernel address. | 372 | # True if argument potentially contains a kernel address. |
| 348 | sub may_leak_address | 373 | sub may_leak_address |
| 349 | { | 374 | { |
| 350 | my ($line) = @_; | 375 | my ($path, $line) = @_; |
| 351 | my $address_re; | 376 | my $address_re; |
| 352 | | 377 | |
| 353 | # Signal masks. | 378 | # Ignore Signal masks. |
| 354 | if ($line =~ '^SigBlk:' or | 379 | if ($line =~ '^SigBlk:' or |
| 355 | $line =~ '^SigIgn:' or | 380 | $line =~ '^SigIgn:' or |
| 356 | $line =~ '^SigCgt:') { | 381 | $line =~ '^SigCgt:') { |
| 357 | return 0; | 382 | return 0; |
| 358 | } | 383 | } |
| 359 | | 384 | |
| 360 | if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or | 385 | # Ignore input device reporting. |
| 361 | $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') { | 386 | # /proc/bus/input/devices: B: KEY=402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe |
| | 387 | # /sys/devices/platform/i8042/serio0/input/input1/uevent: KEY=402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe |
| | 388 | # /sys/devices/platform/i8042/serio0/input/input1/capabilities/key: 402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe |
| | 389 | if ($line =~ '\bKEY=[[:xdigit:]]{9,14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or |
| | 390 | ($path =~ '\bkey$' and |
| | 391 | $line =~ '\b[[:xdigit:]]{9,14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b')) { |
| 362 | return 0; | 392 | return 0; |
| 363 | } | 393 | } |
| 364 | | 394 | |
| @@ -401,7 +431,7 @@ sub parse_dmesg · 36 unmodified lines |
| 401 | { | 431 | { |
| 402 | open my $cmd, '-|', 'dmesg'; | 432 | open my $cmd, '-|', 'dmesg'; |
| 403 | while (<$cmd>) { | 433 | while (<$cmd>) { |
| 404 | if (may_leak_address($_)) { | 434 | if (may_leak_address("dmesg", $_)) { |
| 405 | print 'dmesg: ' . $_; | 435 | print 'dmesg: ' . $_; |
| 406 | } | 436 | } |
| 407 | } | 437 | } |
| @@ -442,6 +472,25 @@ sub timed_parse_file · 34 unmodified lines |
| 442 | } | 472 | } |
| 443 | } | 473 | } |
| 444 | | 474 | |
| | 475 | sub parse_binary |
| | 476 | { |
| | 477 | my ($file) = @_; |
| | 478 | |
| | 479 | open my $fh, "<:raw", $file or return; |
| | 480 | local $/ = undef; |
| | 481 | my $bytes = <$fh>; |
| | 482 | close $fh; |
| | 483 | |
| | 484 | foreach my $entry (@kallsyms) { |
| | 485 | my $addr = $entry->[0]; |
| | 486 | my $symbol = $entry->[1]; |
| | 487 | my $offset = index($bytes, $addr); |
| | 488 | if ($offset != -1) { |
| | 489 | printf("$file: $symbol @ $offset\n"); |
| | 490 | } |
| | 491 | } |
| | 492 | } |
| | 493 | |
| 445 | sub parse_file | 494 | sub parse_file |
| 446 | { | 495 | { |
| 447 | my ($file) = @_; | 496 | my ($file) = @_; |
| @@ -451,13 +500,22 @@ sub parse_file · 3 unmodified lines |
| 451 | } | 500 | } |
| 452 | | 501 | |
| 453 | if (! -T $file) { | 502 | if (! -T $file) { |
| | 503 | if ($file =~ m|^/sys/kernel/btf/| or |
| | 504 | $file =~ m|^/sys/devices/pci| or |
| | 505 | $file =~ m|^/sys/firmware/efi/efivars/| or |
| | 506 | $file =~ m|^/proc/bus/pci/|) { |
| | 507 | return; |
| | 508 | } |
| | 509 | if (scalar @kallsyms > 0) { |
| | 510 | parse_binary($file); |
| | 511 | } |
| 454 | return; | 512 | return; |
| 455 | } | 513 | } |
| 456 | | 514 | |
| 457 | open my $fh, "<", $file or return; | 515 | open my $fh, "<", $file or return; |
| 458 | while ( <$fh> ) { | 516 | while ( <$fh> ) { |
| 459 | chomp; | 517 | chomp; |
| 460 | if (may_leak_address($_)) { | 518 | if (may_leak_address($file, $_)) { |
| 461 | printf("$file: $_\n"); | 519 | printf("$file: $_\n"); |
| 462 | } | 520 | } |
| 463 | } | 521 | } |
| @@ -469,7 +527,7 @@ sub check_path_for_leaks · 5 unmodified lines |
| 469 | { | 527 | { |
| 470 | my ($path) = @_; | 528 | my ($path) = @_; |
| 471 | | 529 | |
| 472 | if (may_leak_address($path)) { | 530 | if (may_leak_address($path, $path)) { |
| 473 | printf("Path name may contain address: $path\n"); | 531 | printf("Path name may contain address: $path\n"); |
| 474 | } | 532 | } |
| 475 | } | 533 | } |