diff mbox

leaking_addresses: add generic 32-bit support

Message ID 1513569287.8041.8.camel@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Kaiwan N Billimoria Dec. 18, 2017, 3:54 a.m. UTC
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.

Signed-off-by: Kaiwan N Billimoria <kaiwan.billimoria@gmail.com>
---

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(-)

Comments

Tobin Harding Dec. 18, 2017, 5:57 a.m. UTC | #1
On Mon, Dec 18, 2017 at 09:24:47AM +0530, kaiwan.billimoria@gmail.com wrote:
> 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.
> 
> Signed-off-by: Kaiwan N Billimoria <kaiwan.billimoria@gmail.com>
> ---
> 
> 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)

> 
> Thanks,
> Kaiwan.
> 
>  scripts/leaking_addresses.pl | 213 +++++++++++++++++++++++++++++++++++++------
>  1 file changed, 184 insertions(+), 29 deletions(-)
> 
> diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl
> index a29e13e577a7..a667f243c95b 100755
> --- a/scripts/leaking_addresses.pl
> +++ b/scripts/leaking_addresses.pl
> @@ -1,10 +1,10 @@
>  #!/usr/bin/env perl
>  #
>  # (c) 2017 Tobin C. Harding <me@tobin.cc>
> -
> +# (c) 2017 Kaiwan N Billimoria <kaiwan.billimoria@gmail.com>
>  # 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.

We don't need this, it's already indented.

> -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.

>  ) 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());
>  }

Perhaps we could have

sub is_32bit
{
	if ($opt_32bit or $page_offset_32bit) {
		return 1;
	}

	if (is_ix86_32()) {
		return 1;
	}
	return 0;
}

>  
>  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;
> +}

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"); }
...

> +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.

> +       } 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;

We should actually just remove this debugging line all together, it will
be overly verbose and not that useful (see below).

>                 $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()) {
		...

> +
> +       # 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);

Remember we don't like 'eval' :) Just make sure your code does not
generate warnings in the first place.

> +       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;
> +       }

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.

> +
> +       # 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);

Just remove this line altogether (I assume it annoyed you while
debugging).

>         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';
	
	} elsif (is_32bit())
		$re = '\b(0x)?[[:xdigit:]]{8}\b';
> -       }
> -
> -       if ($re eq "") {
> -               print STDERR "$0: failed to build kernel address regular expression\n";

And then we can leave this as is.

> +       ###
> +       # 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;
> -- 
> 2.14.3

thanks,
Tobin.
Kaiwan N Billimoria Dec. 26, 2017, 2:02 a.m. UTC | #2
Hey, Merry Xmas all !!   :-)

Re inline below,
Updated patch to follow..

On Mon, 18 Dec 2017 16:57:46 +1100
"Tobin C. Harding" <me@tobin.cc> wrote:

> On Mon, Dec 18, 2017 at 09:24:47AM +0530, kaiwan.billimoria@gmail.com
> wrote:
> > 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.
> > 
> > Signed-off-by: Kaiwan N Billimoria <kaiwan.billimoria@gmail.com>
> > ---
> > 
> > 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)
> 
> > 
> > Thanks,
> > Kaiwan.
> > 
> >  scripts/leaking_addresses.pl | 213
> > +++++++++++++++++++++++++++++++++++++------ 1 file changed, 184
> > insertions(+), 29 deletions(-)
> > 
> > diff --git a/scripts/leaking_addresses.pl
> > b/scripts/leaking_addresses.pl index a29e13e577a7..a667f243c95b
> > 100755 --- a/scripts/leaking_addresses.pl
> > +++ b/scripts/leaking_addresses.pl
> > @@ -1,10 +1,10 @@
> >  #!/usr/bin/env perl
> >  #
> >  # (c) 2017 Tobin C. Harding <me@tobin.cc>
> > -
> > +# (c) 2017 Kaiwan N Billimoria <kaiwan.billimoria@gmail.com>
> >  # 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.  
> 
> We don't need this, it's already indented.

Righto.
> 
> > -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.
Ok.
> 
> >  ) 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()); }  
> 
> Perhaps we could have
> 
> sub is_32bit
> {
> 	if ($opt_32bit or $page_offset_32bit) {
> 		return 1;
> 	}
> 
> 	if (is_ix86_32()) {
> 		return 1;
> 	}
> 	return 0;
> }

Yes, can see the logic in that..

> >  
> >  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;
> > +}  
> 
> 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"); }
> ...
> 
> > +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.
> 
> > +       } 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;  
> 
> We should actually just remove this debugging line all together, it
> will be overly verbose and not that useful (see below).
> 
> >                 $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()) {
> 		...
> 
> > +
> > +       # 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);  
> 
> Remember we don't like 'eval' :) Just make sure your code does not
> generate warnings in the first place.

Ok..

> 
> > +       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;
> > +       }  
> 
> 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.

Ah of course. Sorry..

> 
> > +
> > +       # 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);  
> 
> Just remove this line altogether (I assume it annoyed you while
> debugging).
yeah :-)

> 
> >         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';  
> 	
> 	} elsif (is_32bit())
> 		$re = '\b(0x)?[[:xdigit:]]{8}\b';
> > -       }
> > -
> > -       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.

> 
> > +       ###
> > +       # 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;
> > -- 
> > 2.14.3  
> 
> thanks,
> Tobin.

Patch follows this email,

Thanks,
Kaiwan.
diff mbox

Patch

diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl
index a29e13e577a7..a667f243c95b 100755
--- a/scripts/leaking_addresses.pl
+++ b/scripts/leaking_addresses.pl
@@ -1,10 +1,10 @@ 
 #!/usr/bin/env perl
 #
 # (c) 2017 Tobin C. Harding <me@tobin.cc>
-
+# (c) 2017 Kaiwan N Billimoria <kaiwan.billimoria@gmail.com>
 # 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;