leaking_addresses: add generic 32-bit support
PATCH 1/1 leaking_addresses: add generic 32-bit support
The script attempts to detect the architecture it's running upon; as of now, we explicitly support x86_64, PPC64 and x86_32.
If it's one of them, we proceed "normally". If we fail to detect the arch, we fallback to 64-bit scanning, unless the user has passed either of these option switches: "--32-bit" and/or "--page-offset-32bit=<val>".
If so, we switch to scanning for leaked addresses based on the value of PAGE_OFFSET (via an auto-detected or fallback mechanism).
As of now, we have code (or "rules") to detect special cases for x86_64 and ppc64 (in the get_address_re sub). Also, we now have also builtin "stubs", for lack of a better term, where additional rules for other 64-bit arch's can be plugged in, in future,
as applicable.
---
This is a patch based on Tobin's latest tree, 'leaks' branch.
Applies on top of commit 6c3942594657 (leaking_addresses: add support for 5 page table levels (origin/leaks))
Thanks,
Kaiwan.
scripts/leaking_addresses.pl | 213 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 184 insertions(+), 29 deletions(-)
scripts/leaking_addresses.pl +184 -29
@@ -1,10 +1,10 @@ #!/usr/bin/env perl # # (c) 2017 Tobin C. Harding <[email protected]> - +# (c) 2017 Kaiwan N Billimoria <[email protected]> # Licensed under the terms of the GNU GPL License version 2 # -# leaking_addresses.pl: Scan 64 bit kernel for potential leaking addresses. +# leaking_addresses.pl: Scan kernel for potential leaking addresses. # - Scans dmesg output. # - Walks directory tree and parses each file (for each directory in @DIRS). #
@@ -35,7 +35,7 @@ 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. -my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64'); +my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64', 'i[3456]86'); # Command line options. my $help = 0;
@@ -48,7 +48,9 @@ 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. -my $kernel_config_file = ""; # Kernel configuration file. +my $opt_32_bit = 0; # Detect 32-bit kernel leaking addresses. +my $page_offset_32bit = 0; # 32-bit: value of CONFIG_PAGE_OFFSET. +my $kernel_config_file = ""; # Kernel configuration file. # Do not parse these files (absolute path). my @skip_parse_files_abs = ('/proc/kmsg',
@@ -97,17 +99,19 @@ Version: $V Options: - -o, --output-raw=<file> Save results for future processing. - -i, --input-raw=<file> 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. - --kernel-config-file=<file> Kernel configuration file (e.g /boot/config) - -d, --debug Display debugging output. - -h, --help, --versionq Display this help and exit. + -o, --output-raw=<file> Save results for future processing. + -i, --input-raw=<file> 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. + --32-bit Detect 32-bit kernel leaking addresses. + --page-offset-32bit=<hex> PAGE_OFFSET value (for 32-bit kernels). + --kernel-config-file=<file> Kernel configuration file (e.g /boot/config). + -d, --debug Display debugging output. + -h, --help, --version Display this help and exit. -Scans the running (64 bit) kernel for potential leaking addresses. +Scans the running kernel for potential leaking addresses. EOM exit($exitcode);
@@ -123,7 +127,9 @@ GetOptions( 'squash-by-path' => \$squash_by_path, 'squash-by-filename' => \$squash_by_filename, 'raw' => \$raw, - 'kernel-config-file=s' => \$kernel_config_file, + '32-bit' => \$opt_32_bit, + 'page-offset-32bit=o' => \$page_offset_32bit, + 'kernel-config-file=s' => \$kernel_config_file, ) or help(1); help(0) if ($help);
@@ -139,11 +145,16 @@ 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", $_; +show_detected_architecture() if $debug; + +if (!is_known_architecture()) { + printf STDERR "\n*** WARNING! Script does not recognize your architecture ***\n"; + if ($opt_32_bit or $page_offset_32bit) { + printf STDERR "Scanning for 32-bit leaking kernel addresses\n\n"; + } else { + printf STDERR "Scanning for 64-bit leaking kernel addresses\n"; + printf STDERR "If you\'d rather scan for 32-bit addresses, use the "; + printf STDERR "--32-bit (and --page-offset-32bit=) option switch(es).\n\n"; } my $archname = $Config{archname};
@@ -168,9 +179,14 @@ sub dprint printf(STDERR @_) if $debug; } -sub is_supported_architecture +sub is_known_architecture +{ + return (is_64bit() or is_ix86_32()); +} + +sub is_64bit { - return (is_x86_64() or is_ppc64()); + return (is_x86_64() or is_ppc64() or is_arm64() or is_mips64()); } sub is_x86_64
@@ -193,6 +209,50 @@ sub is_ppc64 return 0; } +sub is_arm64 +{ + if (`uname -m` eq "aarch64") { + return 1; + } + return 0; +} + +sub is_mips64 +{ + if (`uname -m` eq "mips64") { + return 1; + } + return 0; +} + +sub is_ix86_32 +{ + my $archname = $Config{archname}; + + if ($archname =~ m/i[3456]86-linux/) { + return 1; + } + return 0; +} + +sub show_detected_architecture +{ + printf "Detected architecture: "; + if (is_ix86_32()) { + printf "32 bit x86\n"; + } elsif (is_x86_64()) { + printf "x86_64\n"; + } elsif (is_ppc64()) { + printf "ppc64\n"; + } elsif (is_arm64()) { + printf "ARM64\n"; + } elsif (is_mips64()) { + printf "MIPS64\n"; + } else { + printf "failed to detect architecture\n" + } +} + # gets config option value from kernel config file sub get_kernel_config_option {
@@ -220,7 +280,8 @@ sub get_kernel_config_option } foreach my $file (@config_files) { - dprint("parsing config file: %s\n", $file); + printf("file: %s\n", $file) if $debug; + $value = option_from_file($option, $file); if ($value ne "") { last;
@@ -258,6 +319,14 @@ sub is_false_positive { my ($match) = @_; + # 32 bit architectures, actual or forced + + if (!is_64bit() and ($opt_32_bit or $page_offset_32bit)) { + return is_false_positive_32bit($match); + } + + # 64 bit architectures + if ($match =~ '\b(0x)?(f|F){16}\b' or $match =~ '\b(0x)?0{16}\b') { return 1;
@@ -281,6 +350,91 @@ sub is_in_vsyscall_memory_region return ($hex >= $region_min and $hex <= $region_max); } +sub is_false_positive_32bit +{ + my ($match) = @_; + state $page_offset = get_page_offset(); # only gets called once + + if ($match =~ '\b(0x)?(f|F){8}\b') { + return 1; + } + + my $addr32 = eval hex($match); + if ($addr32 < $page_offset) { + return 1; + } + + return 0; +} + +sub get_page_offset +{ + my $page_offset; + my $default_offset = hex("0xc0000000"); + my @config_files; + + # Allow --page-offset-32bit to override. + if ($page_offset_32bit != 0) { + return $page_offset_32bit; + } + + # Allow --kernel-config-file to override. + if ($kernel_config_file ne "") { + @config_files = ($kernel_config_file); + } else { + my $config_file = '/boot/config-' . `uname -r`; + @config_files = ($config_file, '/boot/config'); + } + + if (-R "/proc/config.gz") { + my $tmp_file = "/tmp/tmpkconf"; + if (system("gunzip < /proc/config.gz > $tmp_file")) { + dprint " parse_kernel_config: system(gunzip...) failed\n"; + system("rm -f $tmp_file 2>/dev/null"); + } else { + $page_offset = parse_kernel_config_file($tmp_file); + system("rm -f $tmp_file"); + if ($page_offset ne "") { + return hex($page_offset); + } + } + } + + foreach my $config_file (@config_files) { + chomp $config_file; + $page_offset = parse_kernel_config_file($config_file); + if ($page_offset ne "") { + return hex($page_offset); + } + } + + printf STDERR "\nFailed to parse kernel config files\n"; + printf STDERR "*** NOTE ***\n"; + printf STDERR "Falling back to PAGE_OFFSET = %#x\n\n", $default_offset; + + return $default_offset; +} + +sub parse_kernel_config_file +{ + my ($file) = @_; + my $config = 'CONFIG_PAGE_OFFSET'; + my $str = ""; + my $val = ""; + + open(my $fh, "<", $file) or return ""; + while (my $line = <$fh> ) { + if ($line =~ /^$config/) { + ($str, $val) = split /=/, $line; + chomp($val); + last; + } + } + + close $fh; + return $val; +} + # True if argument potentially contains a kernel address. sub may_leak_address {
@@ -300,7 +454,7 @@ sub may_leak_address } $address_re = get_address_re(); - dprint("Kernel address regular expression: %s\n", $address_re); +# dprint("Kernel address regular expression: %s\n", $address_re); while (/($address_re)/g) { if (!is_false_positive($1)) {
@@ -313,16 +467,17 @@ sub may_leak_address sub get_address_re { - my $re; + my $re = ""; if (is_x86_64()) { $re = get_x86_64_re(); } elsif (is_ppc64()) { $re = '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b'; - } - - if ($re eq "") { - print STDERR "$0: failed to build kernel address regular expression\n"; + ### + # Any special cases for other arch's go below this line + ### + } else { # nothing? then we assume it's a generic 32-bit + $re = '\b(0x)?[[:xdigit:]]{8}\b'; } return $re;
Signature · 1 line
2.14.3
Quoted 14 lines · On Mon, Dec 18, 2017 at 09:24:47AM +0530, [email protected] wrote:
The script attempts to detect the architecture it's running upon; as of now, we explicitly support
x86_64, PPC64 andx86_32.
If it's one of them, we proceed "normally". If we fail to detect the arch, we fallback to 64-bit scanning, unless the user has passed either of these option switches: "--32-bit" and/or "--page-offset-32bit=<val>".If so, we switch to scanning for leaked addresses based on the value of
PAGE_OFFSET(via an auto-detected or fallback mechanism).As of now, we have code (or "rules") to detect special cases for
x86_64and ppc64 (in theget_address_resub). Also, we now have also builtin "stubs", for lack of a better term, where additional rules for other 64-bit arch's can be plugged in, in future,
as applicable.
Quoted 5 lines
Signed-off-by Kaiwan N Billimoria <[email protected]>---
This is a patch based on Tobin's latest tree, 'leaks' branch.
Applies on top of commit6c3942594657(leaking_addresses: add support for 5 page table levels (origin/leaks))
That commit is not the tip of the branch. leaks branch is currently at
commit 266891c62bf0 (leaking_addresses: add support for 5 page table levels)
Quoted 58 lines
Thanks,
Kaiwan.scripts/leaking_addresses.pl | 213 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 184 insertions(+), 29 deletions(-)scripts/leaking_addresses.pl +7
-13@@ -1,10 +1,10 @@ #!/usr/bin/env perl # # (c) 2017 Tobin C. Harding <[email protected]> - +# (c) 2017 Kaiwan N Billimoria <[email protected]> # Licensed under the terms of the GNU GPL License version 2 # -# leaking_addresses.pl: Scan 64 bit kernel for potential leaking addresses. +# leaking_addresses.pl: Scan kernel for potential leaking addresses. # - Scans dmesg output. # - Walks directory tree and parses each file (for each directory in @DIRS). #@@ -35,7 +35,7 @@ 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. -my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64'); +my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64', 'i[3456]86'); # Command line options. my $help = 0;@@ -48,7 +48,9 @@ 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. -my $kernel_config_file = ""; # Kernel configuration file. +my $opt_32_bit = 0; # Detect 32-bit kernel leaking addresses. +my $page_offset_32bit = 0; # 32-bit: value of CONFIG_PAGE_OFFSET. +my $kernel_config_file = ""; # Kernel configuration file. # Do not parse these files (absolute path). my @skip_parse_files_abs = ('/proc/kmsg',@@ -97,17 +99,19 @@ Version: $V Options: - -o, --output-raw=<file> Save results for future processing. - -i, --input-raw=<file> 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. - --kernel-config-file=<file> Kernel configuration file (e.g /boot/config) - -d, --debug Display debugging output. - -h, --help, --versionq Display this help and exit. + -o, --output-raw=<file> Save results for future processing.
scripts/leaking_addresses.pl Version: $V · line 112
Quoted 10 lines
scripts/leaking_addresses.pl +10
-0+ -i, --input-raw=<file> 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. + --32-bit Detect 32-bit kernel leaking addresses. + --page-offset-32bit=<hex> PAGE_OFFSET value (for 32-bit kernels). + --kernel-config-file=<file> Kernel configuration file (e.g /boot/config). + -d, --debug Display debugging output. + -h, --help, --version Display this help and exit.
We don't need this, it's already indented.
scripts/leaking_addresses.pl GetOptions( · line 132
Quoted 13 lines
scripts/leaking_addresses.pl +4
-2-Scans the running (64 bit) kernel for potential leaking addresses. +Scans the running kernel for potential leaking addresses. EOM exit($exitcode);@@ -123,7 +127,9 @@ GetOptions( 'squash-by-path' => \$squash_by_path, 'squash-by-filename' => \$squash_by_filename, 'raw' => \$raw, - 'kernel-config-file=s' => \$kernel_config_file, + '32-bit' => \$opt_32_bit, + 'page-offset-32bit=o' => \$page_offset_32bit, + 'kernel-config-file=s' => \$kernel_config_file,
Perhaps
$opt_32bit so as to be consistent.
Quoted 30 lines
scripts/leaking_addresses.pl +10
-6) or help(1); help(0) if ($help);@@ -139,11 +145,16 @@ 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", $_; +show_detected_architecture() if $debug; + +if (!is_known_architecture()) { + printf STDERR "\n*** WARNING! Script does not recognize your architecture ***\n"; + if ($opt_32_bit or $page_offset_32bit) { + printf STDERR "Scanning for 32-bit leaking kernel addresses\n\n"; + } else { + printf STDERR "Scanning for 64-bit leaking kernel addresses\n"; + printf STDERR "If you\'d rather scan for 32-bit addresses, use the "; + printf STDERR "--32-bit (and --page-offset-32bit=) option switch(es).\n\n"; } my $archname = $Config{archname};@@ -168,9 +179,14 @@ sub dprint printf(STDERR @_) if $debug; } -sub is_supported_architecture
scripts/leaking_addresses.pl sub dprint · line 190
Quoted 10 lines
scripts/leaking_addresses.pl +7
-1+sub is_known_architecture +{ + return (is_64bit() or is_ix86_32()); +} + +sub is_64bit { - return (is_x86_64() or is_ppc64()); + return (is_x86_64() or is_ppc64() or is_arm64() or is_mips64()); }
Perhaps we could have
sub is_32bit
{
if ($opt_32bit or $page_offset_32bit) {
return 1;
}
if (is_ix86_32()) {
return 1;
}
return 0;
}Quoted 21 lines
scripts/leaking_addresses.pl +15
-0sub is_x86_64@@ -193,6 +209,50 @@ sub is_ppc64 return 0; } +sub is_arm64 +{ + if (`uname -m` eq "aarch64") { + return 1; + } + return 0; +} + +sub is_mips64 +{ + if (`uname -m` eq "mips64") { + return 1; + } + return 0; +}
scripts/leaking_addresses.pl sub is_ppc64 · line 236
Quoted 10 lines
scripts/leaking_addresses.pl +10
-0+ +sub is_ix86_32 +{ + my $archname = $Config{archname}; + + if ($archname =~ m/i[3456]86-linux/) { + return 1; + } + return 0; +}
Why do we use $Config{archname} here and uname -m above? Can we use just one of them? If so we could have a helper function
sub is_arch()
{
my ($desc) = @_;
if (`uname -m` eq $desc) {
return 1;
}
return 0;
}and
is_mips64 { is_arch("mips64"); }
...
scripts/leaking_addresses.pl sub is_ppc64 · line 246
Quoted 9 lines
scripts/leaking_addresses.pl +9
-0+sub show_detected_architecture +{ + printf "Detected architecture: "; + if (is_ix86_32()) { + printf "32 bit x86\n"; + } elsif (is_x86_64()) { + printf "x86_64\n"; + } elsif (is_ppc64()) { + printf "ppc64\n";
We probably should use capitals for PPC64 since ARM and MIPS get capitals.
Quoted 13 lines
scripts/leaking_addresses.pl +9
-0+ } elsif (is_arm64()) { + printf "ARM64\n"; + } elsif (is_mips64()) { + printf "MIPS64\n"; + } else { + printf "failed to detect architecture\n" + } +} + # gets config option value from kernel config file sub get_kernel_config_option {
scripts/leaking_addresses.pl sub get_kernel_config_option · line 283
Quoted 5 lines
scripts/leaking_addresses.pl +1
-1@@ -220,7 +280,8 @@ sub get_kernel_config_option } foreach my $file (@config_files) { - dprint("parsing config file: %s\n", $file); + printf("file: %s\n", $file) if $debug;
We should actually just remove this debugging line all together, it will be overly verbose and not that useful (see below).
scripts/leaking_addresses.pl sub is_false_positive · line 326
Quoted 12 lines
scripts/leaking_addresses.pl +5
-0$value = option_from_file($option, $file); if ($value ne "") { last;@@ -258,6 +319,14 @@ sub is_false_positive { my ($match) = @_; + # 32 bit architectures, actual or forced + + if (!is_64bit() and ($opt_32_bit or $page_offset_32bit)) { + return is_false_positive_32bit($match); + }
and now we could have just
if (is_32_bit()) {
...Quoted 10 lines
scripts/leaking_addresses.pl +3
-0+ + # 64 bit architectures + if ($match =~ '\b(0x)?(f|F){16}\b' or $match =~ '\b(0x)?0{16}\b') { return 1;@@ -281,6 +350,91 @@ sub is_in_vsyscall_memory_region return ($hex >= $region_min and $hex <= $region_max); }
scripts/leaking_addresses.pl sub is_in_vsyscall_memory_region · line 362
Quoted 10 lines
scripts/leaking_addresses.pl +10
-0+sub is_false_positive_32bit +{ + my ($match) = @_; + state $page_offset = get_page_offset(); # only gets called once + + if ($match =~ '\b(0x)?(f|F){8}\b') { + return 1; + } + + my $addr32 = eval hex($match);
Remember we don't like 'eval' :) Just make sure your code does not generate warnings in the first place.
Quoted 7 lines
scripts/leaking_addresses.pl +7
-0+ if ($addr32 < $page_offset) { + return 1; + } + + return 0; +} +
scripts/leaking_addresses.pl sub is_in_vsyscall_memory_region · line 379
Quoted 10 lines
scripts/leaking_addresses.pl +10
-0+sub get_page_offset +{ + my $page_offset; + my $default_offset = hex("0xc0000000"); + my @config_files; + + # Allow --page-offset-32bit to override. + if ($page_offset_32bit != 0) { + return $page_offset_32bit; + }
We don't need the rest of this function since we now have
get_kernel_config_option('CONFIG_PAGE_OFFSET');And using this for CONFIG_PAGE_OFFSET after we have done so for CONFIG_PGTABLE_LEVELS is why I suggest above removing debugging line.
Quoted 62 lines
scripts/leaking_addresses.pl +58
-0+ + # Allow --kernel-config-file to override. + if ($kernel_config_file ne "") { + @config_files = ($kernel_config_file); + } else { + my $config_file = '/boot/config-' . `uname -r`; + @config_files = ($config_file, '/boot/config'); + } + + if (-R "/proc/config.gz") { + my $tmp_file = "/tmp/tmpkconf"; + if (system("gunzip < /proc/config.gz > $tmp_file")) { + dprint " parse_kernel_config: system(gunzip...) failed\n"; + system("rm -f $tmp_file 2>/dev/null"); + } else { + $page_offset = parse_kernel_config_file($tmp_file); + system("rm -f $tmp_file"); + if ($page_offset ne "") { + return hex($page_offset); + } + } + } + + foreach my $config_file (@config_files) { + chomp $config_file; + $page_offset = parse_kernel_config_file($config_file); + if ($page_offset ne "") { + return hex($page_offset); + } + } + + printf STDERR "\nFailed to parse kernel config files\n"; + printf STDERR "*** NOTE ***\n"; + printf STDERR "Falling back to PAGE_OFFSET = %#x\n\n", $default_offset; + + return $default_offset; +} + +sub parse_kernel_config_file +{ + my ($file) = @_; + my $config = 'CONFIG_PAGE_OFFSET'; + my $str = ""; + my $val = ""; + + open(my $fh, "<", $file) or return ""; + while (my $line = <$fh> ) { + if ($line =~ /^$config/) { + ($str, $val) = split /=/, $line; + chomp($val); + last; + } + } + + close $fh; + return $val; +} + # True if argument potentially contains a kernel address. sub may_leak_address {
scripts/leaking_addresses.pl sub may_leak_address · line 457
Quoted 5 lines
scripts/leaking_addresses.pl +1
-1@@ -300,7 +454,7 @@ sub may_leak_address } $address_re = get_address_re(); - dprint("Kernel address regular expression: %s\n", $address_re); +# dprint("Kernel address regular expression: %s\n", $address_re);
Just remove this line altogether (I assume it annoyed you while debugging).
scripts/leaking_addresses.pl sub may_leak_address · line 475
Quoted 13 lines
scripts/leaking_addresses.pl +1
-1while (/($address_re)/g) { if (!is_false_positive($1)) {@@ -313,16 +467,17 @@ sub may_leak_address sub get_address_re { - my $re; + my $re = ""; if (is_x86_64()) { $re = get_x86_64_re(); } elsif (is_ppc64()) { $re = '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b';
} elsif (is_32bit())
$re = '\b(0x)?[[:xdigit:]]{8}\b';scripts/leaking_addresses.pl sub may_leak_address · line 325
Quoted 4 lines
scripts/leaking_addresses.pl +0
-4- } - - if ($re eq "") { - print STDERR "$0: failed to build kernel address regular expression\n";
And then we can leave this as is.
Quoted 10 lines
scripts/leaking_addresses.pl +5
-0+ ### + # Any special cases for other arch's go below this line + ### + } else { # nothing? then we assume it's a generic 32-bit + $re = '\b(0x)?[[:xdigit:]]{8}\b'; } return $re;Signature · 1 line
2.14.3
thanks,
Tobin.
Hey, Merry Xmas all !! :-)
Re inline below,
Updated patch to follow..
Quoted 108 lines · On Mon, 18 Dec 2017 16:57:46 +1100 "Tobin C. Harding" <[email protected]> wrote:
Quoted 16 lines · On Mon, Dec 18, 2017 at 09:24:47AM +0530, [email protected] wrote:
The script attempts to detect the architecture it's running upon; as of now, we explicitly support
x86_64, PPC64 andx86_32. If it's one of them, we proceed "normally". If we fail to detect the arch, we fallback to 64-bit scanning, unless the user has passed either of these option switches: "--32-bit" and/or "--page-offset-32bit=<val>".If so, we switch to scanning for leaked addresses based on the value of
PAGE_OFFSET(via an auto-detected or fallback mechanism).As of now, we have code (or "rules") to detect special cases for
x86_64and ppc64 (in theget_address_resub). Also, we now have also builtin "stubs", for lack of a better term, where additional rules for other 64-bit arch's can be plugged in, in future, as applicable.Quoted 6 lines
Signed-off-by Kaiwan N Billimoria <[email protected]>---
This is a patch based on Tobin's latest tree, 'leaks' branch. Applies on top of commit
6c3942594657(leaking_addresses: add support for 5 page table levels (origin/leaks))That commit is not the tip of the branch. leaks branch is currently at
commit
266891c62bf0(leaking_addresses: add support for 5 page table levels)Quoted 78 lines
Thanks,
Kaiwan.scripts/leaking_addresses.pl | 213+++++++++++++++++++++++++++++++++++++------ 1 file changed, 184 insertions(+), 29 deletions(-)
(quoted diff) +0
-0
b/scripts/leaking_addresses.plindexa29e13e577a7..a667f243c95b100755 ---a/scripts/leaking_addresses.pl
+++b/scripts/leaking_addresses.plscripts/leaking_addresses.pl +1
-2@@ -1,10 +1,10 @@ #!/usr/bin/env perl # # (c) 2017 Tobin C. Harding <[email protected]> - +# (c) 2017 Kaiwan N Billimoria <[email protected]> # Licensed under the terms of the GNU GPL License version 2 # -# leaking_addresses.pl: Scan 64 bit kernel for potential leakingaddresses. +# leaking_addresses.pl: Scan kernel for potential leaking addresses. # - Scans dmesg output.
# - Walks directory tree and parses each file (for each directory
in @DIRS). #(quoted diff) +0
-0@@ -35,7 +35,7 @@ my $TIMEOUT = 10; # Script can only grep for kernel addresses on the followingarchitectures. If # your architecture is not listed here and has a grep'able kernel address please # consider submitting a patch.
scripts/leaking_addresses.pl +1
-1-my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64'); +my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64', 'i[3456]86'); # Command line options. my $help = 0;@@ -48,7 +48,9 @@ my $suppress_dmesg = 0; # Don'tshow dmesg in output. my $squash_by_path = 0; #
Summary report grouped by absolute path. my $squash_by_filename =
0; # Summary report grouped by filename.(quoted diff) +1
-1-my $kernel_config_file = ""; # Kernel configuration file. +my $opt_32_bit = 0; # Detect 32-bit kernel leakingaddresses. +my $page_offset_32bit = 0; # 32-bit: value of
CONFIG_PAGE_OFFSET. +my $kernel_config_file = ""; # Kernel
configuration file.
# Do not parse these files (absolute path).
my @skip_parse_files_abs = ('/proc/kmsg',scripts/leaking_addresses.pl +0
-1@@ -97,17 +99,19 @@ Version: $V Options: - -o, --output-raw=<file> Save results for futureprocessing.
- -i, --input-raw=<file> Read results from file
instead of scanning.scripts/leaking_addresses.pl +0
-3- --raw Show raw results (default). - --suppress-dmesg Do not show dmesg results. - --squash-by-path Show one result per uniquepath.
- --squash-by-filename Show one result per unique
filename.
- --kernel-config-file=<file> Kernel configuration file
(e.g/boot/config)scripts/leaking_addresses.pl +1
-2- -d, --debug Display debugging output. - -h, --help, --versionq Display this help and exit. + -o, --output-raw=<file> Save results for futureprocessing.
+ -i, --input-raw=<file> 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.
+ --32-bit Detect 32-bit kernel
leaking addresses.
+ --page-offset-32bit=<hex>PAGE_OFFSETvalue (for
Quoted 8 lines
Quoted 6 lines
32-bit kernels).
+ --kernel-config-file=<file> Kernel configuration file
(e.g/boot/config).(quoted diff) +2
-0+ -d, --debug Display debugging output. + -h, --help, --version Display this help andexit.
We don't need this, it's already indented.
Righto.
Quoted 17 lines
Quoted 7 lines
scripts/leaking_addresses.pl +1
-1-Scans the running (64 bit) kernel for potential leaking addresses. +Scans the running kernel for potential leaking addresses. EOM exit($exitcode);scripts/leaking_addresses.pl GetOptions( · line 132
Quoted 7 lines
scripts/leaking_addresses.pl +3
-1@@ -123,7 +127,9 @@ GetOptions( 'squash-by-path' => \$squash_by_path, 'squash-by-filename' => \$squash_by_filename, 'raw' => \$raw, - 'kernel-config-file=s' => \$kernel_config_file, + '32-bit' => \$opt_32_bit, + 'page-offset-32bit=o' => \$page_offset_32bit, + 'kernel-config-file=s' => \$kernel_config_file,Perhaps
Quoted 1 line
$opt_32bit so as to be consistent.
Ok.
Quoted 50 lines
Quoted 36 lines
scripts/leaking_addresses.pl +8
-5) or help(1); help(0) if ($help);@@ -139,11 +145,16 @@ 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", $_; +show_detected_architecture() if $debug; + +if (!is_known_architecture()) { + printf STDERR "\n*** WARNING! Script does not recognize your architecture ***\n"; + if ($opt_32_bit or $page_offset_32bit) { + printf STDERR "Scanning for 32-bit leaking kernel addresses\n\n"; + } else { + printf STDERR "Scanning for 64-bit leaking kerneladdresses\n";
+ printf STDERR "If you\'d rather scan for 32-bit
addresses, use the ";
+ printf STDERR "--32-bit (and --page-offset-32bit=)
option switch(es).\n\n"; }my $archname = $Config{archname};scripts/leaking_addresses.pl +0
-1@@ -168,9 +179,14 @@ sub dprint printf(STDERR @_) if $debug; } -sub is_supported_architecturescripts/leaking_addresses.pl sub dprint · line 190
Quoted 10 lines
scripts/leaking_addresses.pl +7
-1+sub is_known_architecture +{ + return (is_64bit() or is_ix86_32()); +} + +sub is_64bit { - return (is_x86_64() or is_ppc64()); + return (is_x86_64() or is_ppc64() or is_arm64() or is_mips64()); }Perhaps we could have
sub
is_32bit
Quoted 10 lines
{
if ($opt_32bit or $page_offset_32bit) {
return 1;
}
if (is_ix86_32()) {
return 1;
}
return 0;
}Yes, can see the logic in that..
Quoted 113 lines
Quoted 21 lines
scripts/leaking_addresses.pl +15
-0sub is_x86_64@@ -193,6 +209,50 @@ sub is_ppc64 return 0; } +sub is_arm64 +{ + if (`uname -m` eq "aarch64") { + return 1; + } + return 0; +} + +sub is_mips64 +{ + if (`uname -m` eq "mips64") { + return 1; + } + return 0; +}scripts/leaking_addresses.pl sub is_ppc64 · line 236
Quoted 10 lines
scripts/leaking_addresses.pl +10
-0+ +sub is_ix86_32 +{ + my $archname = $Config{archname}; + + if ($archname =~ m/i[3456]86-linux/) { + return 1; + } + return 0; +}Why do we use $Config{archname} here and
uname -mabove? Can we use just one of them? If so we could have a helper functionsub
is_arch(){ my ($desc) = @_; if (`uname -m` eq $desc) { return 1; } return 0; }and
is_mips64{is_arch("mips64"); }
...scripts/leaking_addresses.pl sub is_ppc64 · line 246
Quoted 9 lines
scripts/leaking_addresses.pl +9
-0+sub show_detected_architecture +{ + printf "Detected architecture: "; + if (is_ix86_32()) { + printf "32 bit x86\n"; + } elsif (is_x86_64()) { + printf "x86_64\n"; + } elsif (is_ppc64()) { + printf "ppc64\n";We probably should use capitals for PPC64 since ARM and MIPS get capitals.
Quoted 13 lines
scripts/leaking_addresses.pl +9
-0+ } elsif (is_arm64()) { + printf "ARM64\n"; + } elsif (is_mips64()) { + printf "MIPS64\n"; + } else { + printf "failed to detect architecture\n" + } +} + # gets config option value from kernel config file sub get_kernel_config_option {scripts/leaking_addresses.pl sub get_kernel_config_option · line 283
Quoted 5 lines
scripts/leaking_addresses.pl +1
-1@@ -220,7 +280,8 @@ sub get_kernel_config_option } foreach my $file (@config_files) { - dprint("parsing config file: %s\n", $file); + printf("file: %s\n", $file) if $debug;We should actually just remove this debugging line all together, it will be overly verbose and not that useful (see below).
scripts/leaking_addresses.pl sub is_false_positive · line 326
Quoted 12 lines
scripts/leaking_addresses.pl +5
-0$value = option_from_file($option, $file); if ($value ne "") { last;@@ -258,6 +319,14 @@ sub is_false_positive { my ($match) = @_; + # 32 bit architectures, actual or forced + + if (!is_64bit() and ($opt_32_bit or $page_offset_32bit)) { + return is_false_positive_32bit($match); + }and now we could have just
if (is_32_bit()) { ...Quoted 10 lines
scripts/leaking_addresses.pl +3
-0+ + # 64 bit architectures + if ($match =~ '\b(0x)?(f|F){16}\b' or $match =~ '\b(0x)?0{16}\b') { return 1;@@ -281,6 +350,91 @@ sub is_in_vsyscall_memory_region return ($hex >= $region_min and $hex <= $region_max); }
Quoted 13 lines
scripts/leaking_addresses.pl sub is_in_vsyscall_memory_region · line 362
Quoted 10 lines
scripts/leaking_addresses.pl +4
-0+sub is_false_positive_32bit +{ + my ($match) = @_; + state $page_offset = get_page_offset(); # only gets calledonce +
scripts/leaking_addresses.pl +5
-0+ if ($match =~ '\b(0x)?(f|F){8}\b') { + return 1; + } + + my $addr32 = eval hex($match);Remember we don't like 'eval' :) Just make sure your code does not generate warnings in the first place.
Ok..
Quoted 21 lines
Quoted 8 lines
scripts/leaking_addresses.pl +7
-0+ if ($addr32 < $page_offset) { + return 1; + } + + return 0; +} +scripts/leaking_addresses.pl sub is_in_vsyscall_memory_region · line 379
Quoted 10 lines
scripts/leaking_addresses.pl +10
-0+sub get_page_offset +{ + my $page_offset; + my $default_offset = hex("0xc0000000"); + my @config_files; + + # Allow --page-offset-32bit to override. + if ($page_offset_32bit != 0) { + return $page_offset_32bit; + }We don't need the rest of this function since we now have
Quoted 4 lines
get_kernel_config_option('CONFIG_PAGE_OFFSET');And using this for
CONFIG_PAGE_OFFSETafter we have done so forCONFIG_PGTABLE_LEVELSis why I suggest above removing debugging line.
Ah of course. Sorry..
Quoted 69 lines
Quoted 69 lines
scripts/leaking_addresses.pl +12
-0+ + # Allow --kernel-config-file to override. + if ($kernel_config_file ne "") { + @config_files = ($kernel_config_file); + } else { + my $config_file = '/boot/config-' . `uname -r`; + @config_files = ($config_file, '/boot/config'); + } + + if (-R "/proc/config.gz") { + my $tmp_file = "/tmp/tmpkconf"; + if (system("gunzip < /proc/config.gz > $tmp_file")){
+ dprint "parse_kernel_config:
system(gunzip...) failed\n";scripts/leaking_addresses.pl +3
-0+ system("rm -f $tmp_file 2>/dev/null"); + } else { + $page_offset =
parse_kernel_config_file($tmp_file);scripts/leaking_addresses.pl +10
-0+ system("rm -f $tmp_file"); + if ($page_offset ne "") { + return hex($page_offset); + } + } + } + + foreach my $config_file (@config_files) { + chomp $config_file; + $page_offset =
parse_kernel_config_file($config_file);scripts/leaking_addresses.pl +8
-0+ if ($page_offset ne "") { + return hex($page_offset); + } + } + + printf STDERR "\nFailed to parse kernel config files\n"; + printf STDERR "*** NOTE ***\n"; + printf STDERR "Falling back to PAGE_OFFSET = %#x\n\n",$default_offset; +
scripts/leaking_addresses.pl +23
-0+ return $default_offset; +} + +sub parse_kernel_config_file +{ + my ($file) = @_; + my $config = 'CONFIG_PAGE_OFFSET'; + my $str = ""; + my $val = ""; + + open(my $fh, "<", $file) or return ""; + while (my $line = <$fh> ) { + if ($line =~ /^$config/) { + ($str, $val) = split /=/, $line; + chomp($val); + last; + } + } + + close $fh; + return $val; +} + # True if argument potentially contains a kernel address. sub may_leak_address {@@ -300,7 +454,7 @@ sub may_leak_address }
Quoted 7 lines
Quoted 4 lines
(quoted diff) +0
-1$address_re = get_address_re(); - dprint("Kernel address regular expression: %s\n",$address_re); +# dprint("Kernel address regular expression:
%s\n", $address_re);Just remove this line altogether (I assume it annoyed you while debugging).
yeah :-)
Quoted 17 lines
Quoted 4 lines
while (/($address_re)/g) { if (!is_false_positive($1)) {scripts/leaking_addresses.pl sub may_leak_address · line 475
Quoted 10 lines
scripts/leaking_addresses.pl +1
-1@@ -313,16 +467,17 @@ sub may_leak_address sub get_address_re { - my $re; + my $re = ""; if (is_x86_64()) { $re = get_x86_64_re(); } elsif (is_ppc64()) { $re = '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b';} elsif (is_32bit()) $re = '\b(0x)?[[:xdigit:]]{8}\b';
Quoted 7 lines
scripts/leaking_addresses.pl sub may_leak_address
Quoted 5 lines
scripts/leaking_addresses.pl +0
-4- } - - if ($re eq "") { - print STDERR "$0: failed to build kernel address regular expression\n";And then we can leave this as is.
Um, am not clear on this point.. could you elaborate pl.
I thought all conditions are covered by if-else ladder: currently, our logic is: if it's either x86_64 or PPC64, form 'special' regex's, else default to a 32-bit-suitable regex.
Quoted 8 lines
Quoted 8 lines
scripts/leaking_addresses.pl +5
-0+ ### + # Any special cases for other arch's go below this line + ### + } else { # nothing? then we assume it's a generic 32-bit + $re = '\b(0x)?[[:xdigit:]]{8}\b'; }
Quoted 6 lines
Quoted 3 lines
return $re;Signature · 1 line
2.14.3thanks,
Tobin.
Patch follows this email,
Thanks,
Kaiwan.
The script attempts to detect the architecture it's running upon; as of now, we explicitly support x86_64, PPC64 and x86_32.
If it's one of them, we proceed "normally". If we fail to detect the arch, we fallback to 64-bit scanning, unless the user has passed either of these option switches: "--opt-32bit" and/or "--page-offset-32bit=<val>".
If so, we switch to scanning for leaked addresses based on the value of PAGE_OFFSET (via an auto-detected or fallback mechanism).
As of now, we have code (or "rules") to detect special cases for x86_64 and PPC64
(in the get_address_re sub). Also, we now have also builtin "stubs", for lack of a better term, where additional rules for other 64-bit arch's can be plugged into the code, in future, as applicable.
--- scripts/leaking_addresses.pl | 190 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 156 insertions(+), 34 deletions(-)
This patch is based on Tobin's suggestions and my replies to them (see prev email in this thread).
scripts/leaking_addresses.pl +156 -34
@@ -1,10 +1,10 @@ #!/usr/bin/env perl # # (c) 2017 Tobin C. Harding <[email protected]> - +# (c) 2017 Kaiwan N Billimoria <[email protected]> # Licensed under the terms of the GNU GPL License version 2 # -# leaking_addresses.pl: Scan 64 bit kernel for potential leaking addresses. +# leaking_addresses.pl: Scan kernel for potential leaking addresses. # - Scans dmesg output. # - Walks directory tree and parses each file (for each directory in @DIRS). #
@@ -32,11 +32,6 @@ 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. -my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64'); - # Command line options. my $help = 0; my $debug = 0;
@@ -48,7 +43,9 @@ 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. -my $kernel_config_file = ""; # Kernel configuration file. +my $opt_32_bit = 0; # Detect (only) 32-bit kernel leaking addresses. +my $page_offset_32bit = 0; # 32-bit: value of CONFIG_PAGE_OFFSET. +my $kernel_config_file = ""; # Kernel configuration file. # Do not parse these files (absolute path). my @skip_parse_files_abs = ('/proc/kmsg',
@@ -104,10 +101,12 @@ Options: --squash-by-path Show one result per unique path. --squash-by-filename Show one result per unique filename. --kernel-config-file=<file> Kernel configuration file (e.g /boot/config) + --opt-32bit Detect (only) 32-bit kernel leaking addresses. + --page-offset-32bit=<hex> PAGE_OFFSET value (for 32-bit kernels). -d, --debug Display debugging output. - -h, --help, --versionq Display this help and exit. + -h, --help, --version Display this help and exit. -Scans the running (64 bit) kernel for potential leaking addresses. +Scans the running kernel for potential leaking addresses. EOM exit($exitcode);
@@ -123,7 +122,9 @@ GetOptions( 'squash-by-path' => \$squash_by_path, 'squash-by-filename' => \$squash_by_filename, 'raw' => \$raw, - 'kernel-config-file=s' => \$kernel_config_file, + 'opt-32bit' => \$opt_32_bit, + 'page-offset-32bit=o' => \$page_offset_32bit, + 'kernel-config-file=s' => \$kernel_config_file, ) or help(1); help(0) if ($help);
@@ -139,16 +140,15 @@ 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", $_; - } +show_detected_architecture() if $debug; - my $archname = $Config{archname}; - printf "\n\$ perl -MConfig -e \'print \"\$Config{archname}\\n\"\'\n"; - printf "%s\n", $archname; +if (!is_known_architecture()) { + printf STDERR "\nFATAL: Script does not recognize your architecture\n"; + + my $arch = `uname -m`; + chomp $arch; + printf "\n\$ uname -m\n"; + printf "%s\n", $arch; exit(129); }
@@ -168,21 +168,45 @@ sub dprint printf(STDERR @_) if $debug; } -sub is_supported_architecture +sub is_known_architecture { - return (is_x86_64() or is_ppc64()); + return (is_64bit() or is_32bit()); } -sub is_x86_64 +sub is_32bit { - my $archname = $Config{archname}; + # 32-bit actual case + if (is_ix86_32()) { + return 1; + } - if ($archname =~ m/x86_64/) { + # 32-bit "forced" case (for any arch other than IA-32) + if ($opt_32_bit or $page_offset_32bit) { return 1; } return 0; } +sub is_64bit +{ + return (is_x86_64() or is_ppc64() or is_arm64() or is_mips64()); +} + +sub is_x86_64 +{ + is_arch("x86_64"); +} + +sub is_arm64 +{ + is_arch("aarch64"); +} + +sub is_mips64 +{ + is_arch("mips64"); +} + sub is_ppc64 { my $archname = $Config{archname};
@@ -193,6 +217,47 @@ sub is_ppc64 return 0; } +sub is_ix86_32 +{ + my $arch = `uname -m`; + + chomp $arch; + if ($arch =~ m/i[3456]86/) { + return 1; + } + return 0; +} + +sub is_arch +{ + my ($desc) = @_; + my $arch = `uname -m`; + + chomp $arch; + if ($arch eq $desc) { + return 1; + } + return 0; +} + +sub show_detected_architecture +{ + printf "Detected architecture: "; + if (is_32bit()) { + printf "32 bit\n"; + } elsif (is_x86_64()) { + printf "x86_64\n"; + } elsif (is_ppc64()) { + printf "PPC64\n"; + } elsif (is_arm64()) { + printf "ARM64\n"; + } elsif (is_mips64()) { + printf "MIPS64\n"; + } else { + printf "failed to detect architecture\n" + } +} + # gets config option value from kernel config file sub get_kernel_config_option {
@@ -212,7 +277,6 @@ sub get_kernel_config_option } else { @config_files = ($tmp_file); } - } else { my $file = '/boot/config-' . `uname -r`; chomp $file;
@@ -220,7 +284,6 @@ sub get_kernel_config_option } foreach my $file (@config_files) { - dprint("parsing config file: %s\n", $file); $value = option_from_file($option, $file); if ($value ne "") { last;
@@ -258,6 +321,14 @@ sub is_false_positive { my ($match) = @_; + # 32 bit architectures, actual or forced + + if (is_32bit()) { + return is_false_positive_32bit($match); + } + + # 64 bit architectures + if ($match =~ '\b(0x)?(f|F){16}\b' or $match =~ '\b(0x)?0{16}\b') { return 1;
@@ -281,6 +352,58 @@ sub is_in_vsyscall_memory_region return ($hex >= $region_min and $hex <= $region_max); } +sub is_false_positive_32bit +{ + my ($match) = @_; + state $page_offset = get_page_offset(); # only gets called once + + if ($match =~ '\b(0x)?(f|F){8}\b') { + return 1; + } + + my $addr32 = hex($match); + if ($addr32 < $page_offset) { + return 1; + } + + return 0; +} + +sub get_page_offset +{ + my $page_offset; + my $default_offset = hex("0xc0000000"); + my @config_files; + + # Allow --page-offset-32bit to override. + if ($page_offset_32bit != 0) { + return $page_offset_32bit; + } + + $page_offset = get_kernel_config_option('CONFIG_PAGE_OFFSET'); + return $default_offset; +} + +sub parse_kernel_config_file +{ + my ($file) = @_; + my $config = 'CONFIG_PAGE_OFFSET'; + my $str = ""; + my $val = ""; + + open(my $fh, "<", $file) or return ""; + while (my $line = <$fh> ) { + if ($line =~ /^$config/) { + ($str, $val) = split /=/, $line; + chomp($val); + last; + } + } + + close $fh; + return $val; +} + # True if argument potentially contains a kernel address. sub may_leak_address {
@@ -300,8 +423,6 @@ sub may_leak_address } $address_re = get_address_re(); - dprint("Kernel address regular expression: %s\n", $address_re); - while (/($address_re)/g) { if (!is_false_positive($1)) { return 1;
@@ -313,16 +434,17 @@ sub may_leak_address sub get_address_re { - my $re; + my $re = ""; if (is_x86_64()) { $re = get_x86_64_re(); } elsif (is_ppc64()) { $re = '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b'; - } - - if ($re eq "") { - print STDERR "$0: failed to build kernel address regular expression\n"; + ### + # Any special cases for other arch's go below this line + ### + } else { # nothing? then we assume it's a generic 32-bit + $re = '\b(0x)?[[:xdigit:]]{8}\b'; } return $re;
Signature · 8 lines
2.14.3 Thanks, Kaiwan. On Mon, 18 Dec 2017 16:57:46 +1100
Quoted 460 lines · Tobin C. Harding
Quoted 16 lines · On Mon, Dec 18, 2017 at 09:24:47AM +0530, [email protected] wrote:
The script attempts to detect the architecture it's running upon; as of now, we explicitly support
x86_64, PPC64 andx86_32. If it's one of them, we proceed "normally". If we fail to detect the arch, we fallback to 64-bit scanning, unless the user has passed either of these option switches: "--32-bit" and/or "--page-offset-32bit=<val>".If so, we switch to scanning for leaked addresses based on the value of
PAGE_OFFSET(via an auto-detected or fallback mechanism).As of now, we have code (or "rules") to detect special cases for
x86_64and ppc64 (in theget_address_resub). Also, we now have also builtin "stubs", for lack of a better term, where additional rules for other 64-bit arch's can be plugged in, in future, as applicable.Quoted 6 lines
Signed-off-by Kaiwan N Billimoria <[email protected]>---
This is a patch based on Tobin's latest tree, 'leaks' branch. Applies on top of commit
6c3942594657(leaking_addresses: add support for 5 page table levels (origin/leaks))That commit is not the tip of the branch. leaks branch is currently at
commit
266891c62bf0(leaking_addresses: add support for 5 page table levels)Quoted 83 lines
Thanks,
Kaiwan.scripts/leaking_addresses.pl | 213+++++++++++++++++++++++++++++++++++++------ 1 file changed, 184 insertions(+), 29 deletions(-)
(quoted diff) +0
-0
b/scripts/leaking_addresses.plindexa29e13e577a7..a667f243c95b100755 ---a/scripts/leaking_addresses.pl
+++b/scripts/leaking_addresses.pl(quoted diff) +1
-2@@ -1,10 +1,10 @@ #!/usr/bin/env perl # # (c) 2017 Tobin C. Harding <[email protected]> - +# (c) 2017 Kaiwan N Billimoria <[email protected]> # Licensed under the terms of the GNU GPL License version 2 # -# leaking_addresses.pl: Scan 64 bit kernel for potential leakingaddresses. +# leaking_addresses.pl: Scan kernel for potential leaking addresses. # - Scans dmesg output.
# - Walks directory tree and parses each file (for each directory
in @DIRS). #(quoted diff) +0
-0@@ -35,7 +35,7 @@ my $TIMEOUT = 10; # Script can only grep for kernel addresses on the followingarchitectures. If # your architecture is not listed here and has a grep'able kernel address please # consider submitting a patch.
(quoted diff) +1
-1-my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64'); +my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64', 'i[3456]86'); # Command line options. my $help = 0;@@ -48,7 +48,9 @@ my $suppress_dmesg = 0; # Don'tshow dmesg in output. my $squash_by_path = 0; #
Summary report grouped by absolute path. my $squash_by_filename =
0; # Summary report grouped by filename.(quoted diff) +1
-1-my $kernel_config_file = ""; # Kernel configuration file. +my $opt_32_bit = 0; # Detect 32-bit kernel leakingaddresses. +my $page_offset_32bit = 0; # 32-bit: value of
CONFIG_PAGE_OFFSET. +my $kernel_config_file = ""; # Kernel
configuration file.
# Do not parse these files (absolute path).
my @skip_parse_files_abs = ('/proc/kmsg',(quoted diff) +0
-1@@ -97,17 +99,19 @@ Version: $V Options: - -o, --output-raw=<file> Save results for futureprocessing.
- -i, --input-raw=<file> Read results from file
instead of scanning.(quoted diff) +0
-3- --raw Show raw results (default). - --suppress-dmesg Do not show dmesg results. - --squash-by-path Show one result per uniquepath.
- --squash-by-filename Show one result per unique
filename.
- --kernel-config-file=<file> Kernel configuration file
(e.g/boot/config)(quoted diff) +1
-2- -d, --debug Display debugging output. - -h, --help, --versionq Display this help and exit. + -o, --output-raw=<file> Save results for futureprocessing.
+ -i, --input-raw=<file> 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.
+ --32-bit Detect 32-bit kernel
leaking addresses.
+ --page-offset-32bit=<hex>PAGE_OFFSETvalue (for
32-bit kernels).
+ --kernel-config-file=<file> Kernel configuration file
(e.g/boot/config).(quoted diff) +2
-0+ -d, --debug Display debugging output. + -h, --help, --version Display this help andQuoted 1 line
exit.
We don't need this, it's already indented.
Review GetOptions( · line 132
Quoted 13 lines
(quoted diff) +4
-2-Scans the running (64 bit) kernel for potential leaking addresses. +Scans the running kernel for potential leaking addresses. EOM exit($exitcode);@@ -123,7 +127,9 @@ GetOptions( 'squash-by-path' => \$squash_by_path, 'squash-by-filename' => \$squash_by_filename, 'raw' => \$raw, - 'kernel-config-file=s' => \$kernel_config_file, + '32-bit' => \$opt_32_bit, + 'page-offset-32bit=o' => \$page_offset_32bit, + 'kernel-config-file=s' => \$kernel_config_file,Perhaps
$opt_32bit so as to be consistent.
Quoted 35 lines
(quoted diff) +8
-5) or help(1); help(0) if ($help);@@ -139,11 +145,16 @@ 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", $_; +show_detected_architecture() if $debug; + +if (!is_known_architecture()) { + printf STDERR "\n*** WARNING! Script does not recognize your architecture ***\n"; + if ($opt_32_bit or $page_offset_32bit) { + printf STDERR "Scanning for 32-bit leaking kernel addresses\n\n"; + } else { + printf STDERR "Scanning for 64-bit leaking kerneladdresses\n";
+ printf STDERR "If you\'d rather scan for 32-bit
addresses, use the ";
+ printf STDERR "--32-bit (and --page-offset-32bit=)
option switch(es).\n\n"; }my $archname = $Config{archname};(quoted diff) +0
-1@@ -168,9 +179,14 @@ sub dprint printf(STDERR @_) if $debug; } -sub is_supported_architectureReview sub dprint · line 190
Quoted 10 lines
(quoted diff) +7
-1+sub is_known_architecture +{ + return (is_64bit() or is_ix86_32()); +} + +sub is_64bit { - return (is_x86_64() or is_ppc64()); + return (is_x86_64() or is_ppc64() or is_arm64() or is_mips64()); }Perhaps we could have
sub
is_32bit{ if ($opt_32bit or $page_offset_32bit) { return 1; } if (is_ix86_32()) { return 1; } return 0; }Quoted 21 lines
(quoted diff) +15
-0sub is_x86_64@@ -193,6 +209,50 @@ sub is_ppc64 return 0; } +sub is_arm64 +{ + if (`uname -m` eq "aarch64") { + return 1; + } + return 0; +} + +sub is_mips64 +{ + if (`uname -m` eq "mips64") { + return 1; + } + return 0; +}Review sub is_ppc64 · line 236
Quoted 10 lines
(quoted diff) +10
-0+ +sub is_ix86_32 +{ + my $archname = $Config{archname}; + + if ($archname =~ m/i[3456]86-linux/) { + return 1; + } + return 0; +}Why do we use $Config{archname} here and
uname -mabove? Can we use just one of them? If so we could have a helper functionsub
is_arch(){ my ($desc) = @_; if (`uname -m` eq $desc) { return 1; } return 0; }and
is_mips64{is_arch("mips64"); }
...Review sub is_ppc64 · line 245
Quoted 9 lines
(quoted diff) +9
-0+sub show_detected_architecture +{ + printf "Detected architecture: "; + if (is_ix86_32()) { + printf "32 bit x86\n"; + } elsif (is_x86_64()) { + printf "x86_64\n"; + } elsif (is_ppc64()) { + printf "ppc64\n";We probably should use capitals for PPC64 since ARM and MIPS get capitals.
Quoted 13 lines
(quoted diff) +9
-0+ } elsif (is_arm64()) { + printf "ARM64\n"; + } elsif (is_mips64()) { + printf "MIPS64\n"; + } else { + printf "failed to detect architecture\n" + } +} + # gets config option value from kernel config file sub get_kernel_config_option {Review sub get_kernel_config_option · line 283
Quoted 5 lines
(quoted diff) +1
-1@@ -220,7 +280,8 @@ sub get_kernel_config_option } foreach my $file (@config_files) { - dprint("parsing config file: %s\n", $file); + printf("file: %s\n", $file) if $debug;We should actually just remove this debugging line all together, it will be overly verbose and not that useful (see below).
Review sub is_false_positive · line 326
Quoted 12 lines
(quoted diff) +5
-0$value = option_from_file($option, $file); if ($value ne "") { last;@@ -258,6 +319,14 @@ sub is_false_positive { my ($match) = @_; + # 32 bit architectures, actual or forced + + if (!is_64bit() and ($opt_32_bit or $page_offset_32bit)) { + return is_false_positive_32bit($match); + }and now we could have just
if (is_32_bit()) { ...Quoted 15 lines
(quoted diff) +7
-0+ + # 64 bit architectures + if ($match =~ '\b(0x)?(f|F){16}\b' or $match =~ '\b(0x)?0{16}\b') { return 1;@@ -281,6 +350,91 @@ sub is_in_vsyscall_memory_region return ($hex >= $region_min and $hex <= $region_max); } +sub is_false_positive_32bit +{ + my ($match) = @_; + state $page_offset = get_page_offset(); # only gets calledonce +
Review
Quoted 5 lines
(quoted diff) +5
-0+ if ($match =~ '\b(0x)?(f|F){8}\b') { + return 1; + } + + my $addr32 = eval hex($match);Remember we don't like 'eval' :) Just make sure your code does not generate warnings in the first place.
Quoted 7 lines
(quoted diff) +7
-0+ if ($addr32 < $page_offset) { + return 1; + } + + return 0; +} +Review
Quoted 10 lines
(quoted diff) +10
-0+sub get_page_offset +{ + my $page_offset; + my $default_offset = hex("0xc0000000"); + my @config_files; + + # Allow --page-offset-32bit to override. + if ($page_offset_32bit != 0) { + return $page_offset_32bit; + }We don't need the rest of this function since we now have
get_kernel_config_option('CONFIG_PAGE_OFFSET');And using this for
CONFIG_PAGE_OFFSETafter we have done so forCONFIG_PGTABLE_LEVELSis why I suggest above removing debugging line.Quoted 70 lines
(quoted diff) +12
-0+ + # Allow --kernel-config-file to override. + if ($kernel_config_file ne "") { + @config_files = ($kernel_config_file); + } else { + my $config_file = '/boot/config-' . `uname -r`; + @config_files = ($config_file, '/boot/config'); + } + + if (-R "/proc/config.gz") { + my $tmp_file = "/tmp/tmpkconf"; + if (system("gunzip < /proc/config.gz > $tmp_file")){
+ dprint "parse_kernel_config:
system(gunzip...) failed\n";(quoted diff) +3
-0+ system("rm -f $tmp_file 2>/dev/null"); + } else { + $page_offset =
parse_kernel_config_file($tmp_file);(quoted diff) +10
-0+ system("rm -f $tmp_file"); + if ($page_offset ne "") { + return hex($page_offset); + } + } + } + + foreach my $config_file (@config_files) { + chomp $config_file; + $page_offset =
parse_kernel_config_file($config_file);(quoted diff) +8
-0+ if ($page_offset ne "") { + return hex($page_offset); + } + } + + printf STDERR "\nFailed to parse kernel config files\n"; + printf STDERR "*** NOTE ***\n"; + printf STDERR "Falling back to PAGE_OFFSET = %#x\n\n",$default_offset; +
(quoted diff) +23
-1+ return $default_offset; +} + +sub parse_kernel_config_file +{ + my ($file) = @_; + my $config = 'CONFIG_PAGE_OFFSET'; + my $str = ""; + my $val = ""; + + open(my $fh, "<", $file) or return ""; + while (my $line = <$fh> ) { + if ($line =~ /^$config/) { + ($str, $val) = split /=/, $line; + chomp($val); + last; + } + } + + close $fh; + return $val; +} + # True if argument potentially contains a kernel address. sub may_leak_address {@@ -300,7 +454,7 @@ sub may_leak_address } $address_re = get_address_re(); - dprint("Kernel address regular expression: %s\n",Quoted 2 lines
$address_re); +# dprint("Kernel address regular expression:
%s\n", $address_re);Just remove this line altogether (I assume it annoyed you while debugging).
Review sub may_leak_address · line 475
Quoted 13 lines
while (/($address_re)/g) { if (!is_false_positive($1)) {(quoted diff) +1
-1@@ -313,16 +467,17 @@ sub may_leak_address sub get_address_re { - my $re; + my $re = ""; if (is_x86_64()) { $re = get_x86_64_re(); } elsif (is_ppc64()) { $re = '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b';} elsif (is_32bit()) $re = '\b(0x)?[[:xdigit:]]{8}\b';Review sub may_leak_address · line 476
Quoted 5 lines
(quoted diff) +0
-4- } - - if ($re eq "") { - print STDERR "$0: failed to build kernel address regular expression\n";And then we can leave this as is.
Quoted 10 lines
(quoted diff) +5
-0+ ### + # Any special cases for other arch's go below this line + ### + } else { # nothing? then we assume it's a generic 32-bit + $re = '\b(0x)?[[:xdigit:]]{8}\b'; }return $re;Signature · 1 line
2.14.3thanks,
Tobin.
Quoted 13 lines · Kaiwan N Billimoria
The script attempts to detect the architecture it's running upon; as of now, we explicitly support
x86_64, PPC64 andx86_32.
If it's one of them, we proceed "normally". If we fail to detect the arch, we fallback to 64-bit scanning, unless the user has passed either of these option switches: "--opt-32bit" and/or "--page-offset-32bit=<val>".If so, we switch to scanning for leaked addresses based on the value of
PAGE_OFFSET(via an auto-detected or fallback mechanism).As of now, we have code (or "rules") to detect special cases for
x86_64and PPC64
(in theget_address_resub). Also, we now have also builtin "stubs", for lack of a better term, where additional rules for other 64-bit arch's can be plugged into the code, in future, as applicable.
Quoted 7 lines
Signed-off-by Kaiwan N Billimoria <[email protected]>--- scripts/leaking_addresses.pl | 190 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 156 insertions(+), 34 deletions(-)This patch is based on Tobin's suggestions and my replies to them (see prev email in this thread).
Hi,
Can you resend this with a version number please. Also can you include what testing you have done please. I'm away on Christmas holidays at the moment but I'll review soon as I'm back.
Thanks,
Tobin.