From 7e5758f7f74a591b52c6e8a8cfe82e6288ddced0 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 8 Nov 2017 11:01:59 +1100 Subject: leaking_addresses: use tabs instead of spaces Current code uses spaces instead of tabs in places. Use tabs instead of spaces. Signed-off-by: Tobin C. Harding --- scripts/leaking_addresses.pl | 54 ++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 27 deletions(-) (limited to 'scripts') diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl index 2977371..b64efce 100755 --- a/scripts/leaking_addresses.pl +++ b/scripts/leaking_addresses.pl @@ -170,46 +170,46 @@ sub push_to_global sub is_false_positive { - my ($match) = @_; + my ($match) = @_; + + if ($match =~ '\b(0x)?(f|F){16}\b' or + $match =~ '\b(0x)?0{16}\b') { + return 1; + } - if ($match =~ '\b(0x)?(f|F){16}\b' or - $match =~ '\b(0x)?0{16}\b') { - return 1; - } - # vsyscall memory region, we should probably check against a range here. - if ($match =~ '\bf{10}600000\b' or - $match =~ '\bf{10}601000\b') { - return 1; - } + if ($match =~ '\bf{10}600000\b' or# vsyscall memory region, we should probably check against a range here. + $match =~ '\bf{10}601000\b') { + return 1; + } - return 0; + return 0; } # True if argument potentially contains a kernel address. sub may_leak_address { - my ($line) = @_; - my $address = '\b(0x)?ffff[[:xdigit:]]{12}\b'; + my ($line) = @_; + my $address = '\b(0x)?ffff[[:xdigit:]]{12}\b'; - # Signal masks. - if ($line =~ '^SigBlk:' or - $line =~ '^SigCgt:') { - return 0; - } + # Signal masks. + if ($line =~ '^SigBlk:' or + $line =~ '^SigCgt:') { + return 0; + } - if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or - $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') { + if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or + $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') { return 0; - } + } - while (/($address)/g) { - if (!is_false_positive($1)) { - return 1; - } - } + while (/($address)/g) { + if (!is_false_positive($1)) { + return 1; + } + } - return 0; + return 0; } sub parse_dmesg -- cgit v1.1 From fa31a58202c5d9ebb26f562913b17e81357fe0e7 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 8 Nov 2017 11:04:27 +1100 Subject: leaking_addresses: remove dead/unused code debug_arrays is not called. Also, %seen hash is not used. We should remove unused code. Remove dead code. Signed-off-by: Tobin C. Harding --- scripts/leaking_addresses.pl | 9 --------- 1 file changed, 9 deletions(-) (limited to 'scripts') diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl index b64efce..94b22d5 100755 --- a/scripts/leaking_addresses.pl +++ b/scripts/leaking_addresses.pl @@ -133,14 +133,6 @@ walk(@DIRS); exit 0; -sub debug_arrays -{ - print 'dirs_any: ' . join(", ", @skip_walk_dirs_any) . "\n"; - print 'dirs_abs: ' . join(", ", @skip_walk_dirs_abs) . "\n"; - print 'parse_any: ' . join(", ", @skip_parse_files_any) . "\n"; - print 'parse_abs: ' . join(", ", @skip_parse_files_abs) . "\n"; -} - sub dprint { printf(STDERR @_) if $debug; @@ -281,7 +273,6 @@ sub skip_walk sub walk { my @dirs = @_; - my %seen; while (my $pwd = shift @dirs) { next if (skip_walk($pwd)); -- cgit v1.1 From ecd39dbd27d6f2907630678cbff464374edff8fe Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 8 Nov 2017 11:11:09 +1100 Subject: leaking_addresses: remove command line options Currently script accepts files to skip. This was added to make running the script faster (for repeat runs). We can remove this functionality in preparation for adding sub commands (scan and format) to the script. Remove command line options. Signed-off-by: Tobin C. Harding --- scripts/leaking_addresses.pl | 58 -------------------------------------------- 1 file changed, 58 deletions(-) (limited to 'scripts') diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl index 94b22d5..719ed0a 100755 --- a/scripts/leaking_addresses.pl +++ b/scripts/leaking_addresses.pl @@ -7,25 +7,6 @@ # - Scans dmesg output. # - Walks directory tree and parses each file (for each directory in @DIRS). # -# You can configure the behaviour of the script; -# -# - By adding paths, for directories you do not want to walk; -# absolute paths: @skip_walk_dirs_abs -# directory names: @skip_walk_dirs_any -# -# - By adding paths, for files you do not want to parse; -# absolute paths: @skip_parse_files_abs -# file names: @skip_parse_files_any -# -# The use of @skip_xxx_xxx_any causes files to be skipped where ever they occur. -# For example adding 'fd' to @skip_walk_dirs_any causes the fd/ directory to be -# skipped for all PID sub-directories of /proc -# -# The same thing can be achieved by passing command line options to --dont-walk -# and --dont-parse. If absolute paths are supplied to these options they are -# appended to the @skip_xxx_xxx_abs arrays. If file names are supplied to these -# options, they are appended to the @skip_xxx_xxx_any arrays. -# # Use --debug to output path before parsing, this is useful to find files that # cause the script to choke. # @@ -50,8 +31,6 @@ my @DIRS = ('/proc', '/sys'); # Command line options. my $help = 0; my $debug = 0; -my @dont_walk = (); -my @dont_parse = (); # Do not parse these files (absolute path). my @skip_parse_files_abs = ('/proc/kmsg', @@ -96,20 +75,9 @@ Version: $V Options: - --dont-walk= Don't walk tree starting at . - --dont-parse= Don't parse . -d, --debug Display debugging output. -h, --help, --version Display this help and exit. -If an absolute path is passed to --dont_XXX then this path is skipped. If a -single filename is passed then this file/directory will be skipped when -appearing under any subdirectory. - -Example: - - # Just scan dmesg output. - scripts/leaking_addresses.pl --dont_walk_abs /proc --dont_walk_abs /sys - Scans the running (64 bit) kernel for potential leaking addresses. EOM @@ -117,8 +85,6 @@ EOM } GetOptions( - 'dont-walk=s' => \@dont_walk, - 'dont-parse=s' => \@dont_parse, 'd|debug' => \$debug, 'h|help' => \$help, 'version' => \$help @@ -126,8 +92,6 @@ GetOptions( help(0) if ($help); -push_to_global(); - parse_dmesg(); walk(@DIRS); @@ -138,28 +102,6 @@ sub dprint printf(STDERR @_) if $debug; } -sub push_in_abs_any -{ - my ($in, $abs, $any) = @_; - - foreach my $path (@$in) { - if (File::Spec->file_name_is_absolute($path)) { - push @$abs, $path; - } elsif (index($path,'/') == -1) { - push @$any, $path; - } else { - print 'path error: ' . $path; - } - } -} - -# Push command line options to global arrays. -sub push_to_global -{ - push_in_abs_any(\@dont_walk, \@skip_walk_dirs_abs, \@skip_walk_dirs_any); - push_in_abs_any(\@dont_parse, \@skip_parse_files_abs, \@skip_parse_files_any); -} - sub is_false_positive { my ($match) = @_; -- cgit v1.1 From a284733e26e8e173cb5f589531a655d723ecb3ea Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 9 Nov 2017 13:28:43 +1100 Subject: leaking_addresses: fix comment string typo Fix typo in comment string. Signed-off-by: Tobin C. Harding --- scripts/leaking_addresses.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl index 719ed0a..3f8c6e2 100755 --- a/scripts/leaking_addresses.pl +++ b/scripts/leaking_addresses.pl @@ -40,7 +40,7 @@ my @skip_parse_files_abs = ('/proc/kmsg', '/sys/kernel/debug/tracing/trace_pipe', '/sys/kernel/security/apparmor/revision'); -# Do not parse thes files under any subdirectory. +# Do not parse these files under any subdirectory. my @skip_parse_files_any = ('0', '1', '2', -- cgit v1.1 From 1c1e3be0bf37db1396b4ecd995992643a6d92c00 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 9 Nov 2017 14:02:41 +1100 Subject: leaking_addresses: add to exclude files/paths list There are a couple more files that cause the script to stall. /sys/firmware/devicetree and its symlink /proc/device-tree, reported by Michael Ellerman. usbmon should be skipped were ever it appears. Reported by Kees Cook Add files to be excluded from parsing. Signed-off-by: Tobin C. Harding --- scripts/leaking_addresses.pl | 3 +++ 1 file changed, 3 insertions(+) (limited to 'scripts') diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl index 3f8c6e2..0aac03a 100755 --- a/scripts/leaking_addresses.pl +++ b/scripts/leaking_addresses.pl @@ -37,6 +37,8 @@ my @skip_parse_files_abs = ('/proc/kmsg', '/proc/kcore', '/proc/fs/ext4/sdb1/mb_groups', '/proc/1/fd/3', + '/sys/firmware/devicetree', + '/proc/device-tree', '/sys/kernel/debug/tracing/trace_pipe', '/sys/kernel/security/apparmor/revision'); @@ -61,6 +63,7 @@ my @skip_walk_dirs_any = ('self', 'thread-self', 'cwd', 'fd', + 'usbmon', 'stderr', 'stdin', 'stdout'); -- cgit v1.1 From d09bd8da8812a4df69ea3303e6df846a729ec623 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 9 Nov 2017 15:07:15 +1100 Subject: leaking_addresses: add summary reporting options Currently script just dumps all results found. Potentially, this risks losing single results among multiple duplicate results. We need some way of restricting duplicates to assist users of the script. It would also be nice if we got a report instead of raw results. Duplicates can be defined in various ways, instead of trying to find a single perfect solution we can present the user with various options to display the output. Doing so will typically lead to users wanting to view the output multiple times. Currently we scan the kernel each time, this is slow and unnecessary. We can expedite the process by writing the results to file for subsequent viewing. Add command line options to enable summary reporting, including options to write to and read from file. Signed-off-by: Tobin C. Harding --- scripts/leaking_addresses.pl | 191 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 188 insertions(+), 3 deletions(-) (limited to 'scripts') diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl index 0aac03a..4610ad3 100755 --- a/scripts/leaking_addresses.pl +++ b/scripts/leaking_addresses.pl @@ -31,6 +31,13 @@ my @DIRS = ('/proc', '/sys'); # Command line options. my $help = 0; my $debug = 0; +my $raw = 0; +my $output_raw = ""; # Write raw results to file. +my $input_raw = ""; # Read raw results from file instead of scanning. + +my $suppress_dmesg = 0; # Don't show dmesg in output. +my $squash_by_path = 0; # Summary report grouped by absolute path. +my $squash_by_filename = 0; # Summary report grouped by filename. # Do not parse these files (absolute path). my @skip_parse_files_abs = ('/proc/kmsg', @@ -73,13 +80,31 @@ sub help my ($exitcode) = @_; print << "EOM"; + Usage: $P [OPTIONS] Version: $V Options: - -d, --debug Display debugging output. - -h, --help, --version Display this help and exit. + -o, --output-raw= Save results for future processing. + -i, --input-raw= Read results from file instead of scanning. + --raw Show raw results (default). + --suppress-dmesg Do not show dmesg results. + --squash-by-path Show one result per unique path. + --squash-by-filename Show one result per unique filename. + -d, --debug Display debugging output. + -h, --help, --version Display this help and exit. + +Examples: + + # Scan kernel and dump raw results. + $0 + + # Scan kernel and save results to file. + $0 --output-raw scan.out + + # View summary report. + $0 --input-raw scan.out --squash-by-filename Scans the running (64 bit) kernel for potential leaking addresses. @@ -90,11 +115,33 @@ EOM GetOptions( 'd|debug' => \$debug, 'h|help' => \$help, - 'version' => \$help + 'version' => \$help, + 'o|output-raw=s' => \$output_raw, + 'i|input-raw=s' => \$input_raw, + 'suppress-dmesg' => \$suppress_dmesg, + 'squash-by-path' => \$squash_by_path, + 'squash-by-filename' => \$squash_by_filename, + 'raw' => \$raw, ) or help(1); help(0) if ($help); +if ($input_raw) { + format_output($input_raw); + exit(0); +} + +if (!$input_raw and ($squash_by_path or $squash_by_filename)) { + printf "\nSummary reporting only available with --input-raw=\n"; + printf "(First run scan with --output-raw=.)\n"; + exit(128); +} + +if ($output_raw) { + open my $fh, '>', $output_raw or die "$0: $output_raw: $!\n"; + select $fh; +} + parse_dmesg(); walk(@DIRS); @@ -239,3 +286,141 @@ sub walk } } } + +sub format_output +{ + my ($file) = @_; + + # Default is to show raw results. + if ($raw or (!$squash_by_path and !$squash_by_filename)) { + dump_raw_output($file); + return; + } + + my ($total, $dmesg, $paths, $files) = parse_raw_file($file); + + printf "\nTotal number of results from scan (incl dmesg): %d\n", $total; + + if (!$suppress_dmesg) { + print_dmesg($dmesg); + } + + if ($squash_by_filename) { + squash_by($files, 'filename'); + } + + if ($squash_by_path) { + squash_by($paths, 'path'); + } +} + +sub dump_raw_output +{ + my ($file) = @_; + + open (my $fh, '<', $file) or die "$0: $file: $!\n"; + while (<$fh>) { + if ($suppress_dmesg) { + if ("dmesg:" eq substr($_, 0, 6)) { + next; + } + } + print $_; + } + close $fh; +} + +sub parse_raw_file +{ + my ($file) = @_; + + my $total = 0; # Total number of lines parsed. + my @dmesg; # dmesg output. + my %files; # Unique filenames containing leaks. + my %paths; # Unique paths containing leaks. + + open (my $fh, '<', $file) or die "$0: $file: $!\n"; + while (my $line = <$fh>) { + $total++; + + if ("dmesg:" eq substr($line, 0, 6)) { + push @dmesg, $line; + next; + } + + cache_path(\%paths, $line); + cache_filename(\%files, $line); + } + + return $total, \@dmesg, \%paths, \%files; +} + +sub print_dmesg +{ + my ($dmesg) = @_; + + print "\ndmesg output:\n"; + + if (@$dmesg == 0) { + print "\n"; + return; + } + + foreach(@$dmesg) { + my $index = index($_, ': '); + $index += 2; # skid ': ' + print substr($_, $index); + } +} + +sub squash_by +{ + my ($ref, $desc) = @_; + + print "\nResults squashed by $desc (excl dmesg). "; + print "Displaying [ <$desc>], \n"; + + if (keys %$ref == 0) { + print "\n"; + return; + } + + foreach(keys %$ref) { + my $lines = $ref->{$_}; + my $length = @$lines; + printf "[%d %s] %s", $length, $_, @$lines[0]; + } +} + +sub cache_path +{ + my ($paths, $line) = @_; + + my $index = index($line, ': '); + my $path = substr($line, 0, $index); + + $index += 2; # skip ': ' + add_to_cache($paths, $path, substr($line, $index)); +} + +sub cache_filename +{ + my ($files, $line) = @_; + + my $index = index($line, ': '); + my $path = substr($line, 0, $index); + my $filename = basename($path); + + $index += 2; # skip ': ' + add_to_cache($files, $filename, substr($line, $index)); +} + +sub add_to_cache +{ + my ($cache, $key, $value) = @_; + + if (!$cache->{$key}) { + $cache->{$key} = (); + } + push @{$cache->{$key}}, $value; +} -- cgit v1.1 From 62139c1242b573cb647776e3abc503a69fbd2c08 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 9 Nov 2017 15:19:40 +1100 Subject: leaking_addresses: add support for ppc64 Currently script is targeted at x86_64. We can support other architectures by using the correct regular expressions for each architecture. Add the infrastructure to support multiple architectures. Add support for ppc64. Signed-off-by: Tobin C. Harding --- scripts/leaking_addresses.pl | 66 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 6 deletions(-) (limited to 'scripts') diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl index 4610ad3..1d6ab7f 100755 --- a/scripts/leaking_addresses.pl +++ b/scripts/leaking_addresses.pl @@ -21,6 +21,7 @@ use File::Spec; use Cwd 'abs_path'; use Term::ANSIColor qw(:constants); use Getopt::Long qw(:config no_auto_abbrev); +use Config; my $P = $0; my $V = '0.01'; @@ -28,6 +29,11 @@ my $V = '0.01'; # Directories to scan. my @DIRS = ('/proc', '/sys'); +# Script can only grep for kernel addresses on the following architectures. If +# your architecture is not listed here and has a grep'able kernel address please +# consider submitting a patch. +my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64'); + # Command line options. my $help = 0; my $debug = 0; @@ -137,6 +143,20 @@ if (!$input_raw and ($squash_by_path or $squash_by_filename)) { exit(128); } +if (!is_supported_architecture()) { + printf "\nScript does not support your architecture, sorry.\n"; + printf "\nCurrently we support: \n\n"; + foreach(@SUPPORTED_ARCHITECTURES) { + printf "\t%s\n", $_; + } + + my $archname = $Config{archname}; + printf "\n\$ perl -MConfig -e \'print \"\$Config{archname}\\n\"\'\n"; + printf "%s\n", $archname; + + exit(129); +} + if ($output_raw) { open my $fh, '>', $output_raw or die "$0: $output_raw: $!\n"; select $fh; @@ -152,6 +172,31 @@ sub dprint printf(STDERR @_) if $debug; } +sub is_supported_architecture +{ + return (is_x86_64() or is_ppc64()); +} + +sub is_x86_64 +{ + my $archname = $Config{archname}; + + if ($archname =~ m/x86_64/) { + return 1; + } + return 0; +} + +sub is_ppc64 +{ + my $archname = $Config{archname}; + + if ($archname =~ m/powerpc/ and $archname =~ m/64/) { + return 1; + } + return 0; +} + sub is_false_positive { my ($match) = @_; @@ -161,10 +206,12 @@ sub is_false_positive return 1; } - - if ($match =~ '\bf{10}600000\b' or# vsyscall memory region, we should probably check against a range here. - $match =~ '\bf{10}601000\b') { - return 1; + if (is_x86_64) { + # vsyscall memory region, we should probably check against a range here. + if ($match =~ '\bf{10}600000\b' or + $match =~ '\bf{10}601000\b') { + return 1; + } } return 0; @@ -174,7 +221,7 @@ sub is_false_positive sub may_leak_address { my ($line) = @_; - my $address = '\b(0x)?ffff[[:xdigit:]]{12}\b'; + my $address_re; # Signal masks. if ($line =~ '^SigBlk:' or @@ -187,7 +234,14 @@ sub may_leak_address return 0; } - while (/($address)/g) { + # One of these is guaranteed to be true. + if (is_x86_64()) { + $address_re = '\b(0x)?ffff[[:xdigit:]]{12}\b'; + } elsif (is_ppc64()) { + $address_re = '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b'; + } + + while (/($address_re)/g) { if (!is_false_positive($1)) { return 1; } -- cgit v1.1 From dd98c252aea2a3dcd4014cb71bcdf9588519b800 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 9 Nov 2017 15:37:06 +1100 Subject: leaking_addresses: add timeout on file read Currently script can stall if we read certain files (like /proc/kmsg). While we have a mechanism to skip these files once they are discovered it would be nice to not stall on as yet undiscovered files of this kind. Set a timer before each file is parsed, warn user if timer expires. Suggested-by: Kees Cook Signed-off-by: Tobin C. Harding --- scripts/leaking_addresses.pl | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl index 1d6ab7f..6efd1fd 100755 --- a/scripts/leaking_addresses.pl +++ b/scripts/leaking_addresses.pl @@ -29,6 +29,9 @@ my $V = '0.01'; # Directories to scan. my @DIRS = ('/proc', '/sys'); +# Timer for parsing each file, in seconds. +my $TIMEOUT = 10; + # Script can only grep for kernel addresses on the following architectures. If # your architecture is not listed here and has a grep'able kernel address please # consider submitting a patch. @@ -284,6 +287,23 @@ sub skip_parse return skip($path, \@skip_parse_files_abs, \@skip_parse_files_any); } +sub timed_parse_file +{ + my ($file) = @_; + + eval { + local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required. + alarm $TIMEOUT; + parse_file($file); + alarm 0; + }; + + if ($@) { + die unless $@ eq "alarm\n"; # Propagate unexpected errors. + printf STDERR "timed out parsing: %s\n", $file; + } +} + sub parse_file { my ($file) = @_; @@ -335,7 +355,7 @@ sub walk if (-d $path) { push @dirs, $path; } else { - parse_file($path); + timed_parse_file($path); } } } -- cgit v1.1 From a11949ec20635b43d82ee229315fd2e3c80c22a3 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 14 Nov 2017 09:25:11 +1100 Subject: leaking_addresses: add SigIgn to false positives Signal masks are false positives, we already check for SigBlk and SigCgt but we missed SigIgn. Add SigIgn to false positive check. Signed-off-by: Tobin C. Harding --- scripts/leaking_addresses.pl | 1 + 1 file changed, 1 insertion(+) (limited to 'scripts') diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl index 6efd1fd..bc57880 100755 --- a/scripts/leaking_addresses.pl +++ b/scripts/leaking_addresses.pl @@ -228,6 +228,7 @@ sub may_leak_address # Signal masks. if ($line =~ '^SigBlk:' or + $line =~ '^SigIgn:' or $line =~ '^SigCgt:') { return 0; } -- cgit v1.1