Leaking-addresses patches for 4.17-rc1

Here is the patch set for the 4.17-rc1 merge window.  This set
 represents improvements to the scripts/leaking_addresses.pl script.  The
 major improvement is that with this set applied the script actually runs
 in a reasonable amount of time (less than a minute on a standard stock
 Ubuntu user desktop).  Also, we have a second maintainer now and a tree
 hosted on kernel.org
 
 We do a few code clean ups.  We fix the command help output.  Handling
 of the vsyscall address range is fixed to check the whole range instead
 of just the start/end addresses.  We add support for 5 page table levels
 (suggested on LKML).  We use a system command to get the machine
 architecture instead of using Perl.  Calling this command for every
 regex comparison is what previously choked the script, caching the
 result of this call gave the major speed improvement.  We add support
 for scanning 32-bit kernels using the user/kernel memory split.  Path
 skipping code refactored and simplified (meaning easier script
 configuration).  We remove version numbering.  We add a variable name to
 improve readability of a regex and finally we check filenames for
 leaking addresses.
 
 Currently script scans /proc/PID for all PID.  With this set applied we
 only scan for PID==1. It was observed that on an idle system files under
 /proc/PID are predominantly the same for all processes.  Also it was
 noted that the script does not scan _all_ the kernel since it only scans
 active processes.  Scanning only for PID==1 makes explicit the inherent
 flaw in the script that the scan is only partial and also speeds things up.
 
 Signed-off-by: Tobin C. Harding <me@tobin.cc>
 -----BEGIN PGP SIGNATURE-----
 
 iQIcBAABCgAGBQJayARVAAoJEEC/nkwmnWYHSAAQALETjSg2h16dfAm2OxTvUemm
 re1zbyzhwxCeVJuBXusMcA0BTwRonmnh6FJhdcOBs0mb1F6bUaKIJpNwU17XKbOj
 1ni0SiFBjDQA46E2ek7d1FC4E+1P72GSykDq6N+GmOAattIVn+SxAHv8MokyIyTT
 7F1Qd0HOQZEF3UU6YUl3M4JfCdp7jaKxbjjXzJ5vnTvVBkgesx6Ccf5+D04xHXFD
 Eps7DZbUz646jI84eq+VgM77Uk9YzMCkoh2fEwoqe6o6HwNj5i96ifnCw5uIuopk
 lq40J7Wc59hK/Cz4rU52G9Ml5P2KY9Uv4CRL9JB/ZYEx+c246NF43ewrX5uzfrsd
 wXAO8FqcZA99YW8XGWKHC/bToSjbiMPtwx1IRn6sOuOS3l7NN8afpWsLpqPk8ECA
 ImzugUf82vrhCWGOBzNFFMAIHTN+BM54v+foJOdxAqQVveW+Ze7uBRY2ZIEq7ViT
 XXgOqDQz7Ub6N0C3cRAqmRc1Yv2n8QGg56uqam5MrMGtz6NrBMROTgafQMRFrf90
 q+KfBvr6ofzuTWyfnUL0UXiHKvRmVro8hk/mdeJqqdS6dxng5bMT1ODK7SXlzyZQ
 Uf6ePo1pN3TpZRUKdwcyDA0+sHNHbXoE/NsC5UuwAnbE5u6m1FuqeqoysVJTKq5d
 /1IejdG15RYMh8YSYu5L
 =9BLH
 -----END PGP SIGNATURE-----

Merge tag 'leaks-4.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tobin/leaks

Pull leaking-addresses updates from Tobin Harding:
 "This set represents improvements to the scripts/leaking_addresses.pl
  script.

  The major improvement is that with this set applied the script
  actually runs in a reasonable amount of time (less than a minute on a
  standard stock Ubuntu user desktop). Also, we have a second maintainer
  now and a tree hosted on kernel.org

  We do a few code clean ups. We fix the command help output. Handling
  of the vsyscall address range is fixed to check the whole range
  instead of just the start/end addresses. We add support for 5 page
  table levels (suggested on LKML). We use a system command to get the
  machine architecture instead of using Perl. Calling this command for
  every regex comparison is what previously choked the script, caching
  the result of this call gave the major speed improvement. We add
  support for scanning 32-bit kernels using the user/kernel memory
  split. Path skipping code refactored and simplified (meaning easier
  script configuration). We remove version numbering. We add a variable
  name to improve readability of a regex and finally we check filenames
  for leaking addresses.

  Currently script scans /proc/PID for all PID. With this set applied we
  only scan for PID==1. It was observed that on an idle system files
  under /proc/PID are predominantly the same for all processes. Also it
  was noted that the script does not scan _all_ the kernel since it only
  scans active processes. Scanning only for PID==1 makes explicit the
  inherent flaw in the script that the scan is only partial and also
  speeds things up"

* tag 'leaks-4.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tobin/leaks:
  MAINTAINERS: Update LEAKING_ADDRESSES
  leaking_addresses: check if file name contains address
  leaking_addresses: explicitly name variable used in regex
  leaking_addresses: remove version number
  leaking_addresses: skip '/proc/1/syscall'
  leaking_addresses: skip all /proc/PID except /proc/1
  leaking_addresses: cache architecture name
  leaking_addresses: simplify path skipping
  leaking_addresses: do not parse binary files
  leaking_addresses: add 32-bit support
  leaking_addresses: add is_arch() wrapper subroutine
  leaking_addresses: use system command to get arch
  leaking_addresses: add support for 5 page table levels
  leaking_addresses: add support for kernel config file
  leaking_addresses: add range check for vsyscall memory
  leaking_addresses: indent dependant options
  leaking_addresses: remove command examples
  leaking_addresses: remove mention of kptr_restrict
  leaking_addresses: fix typo function not called
This commit is contained in:
Linus Torvalds 2018-04-07 11:56:33 -07:00
commit 299f89d53e
2 changed files with 261 additions and 112 deletions

View File

@ -7919,7 +7919,10 @@ F: drivers/scsi/53c700*
LEAKING_ADDRESSES
M: Tobin C. Harding <me@tobin.cc>
M: Tycho Andersen <tycho@tycho.ws>
L: kernel-hardening@lists.openwall.com
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/tobin/leaks.git
F: scripts/leaking_addresses.pl
LED SUBSYSTEM

View File

@ -3,15 +3,20 @@
# (c) 2017 Tobin C. Harding <me@tobin.cc>
# 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 the kernel for potential leaking addresses.
# - Scans dmesg output.
# - Walks directory tree and parses each file (for each directory in @DIRS).
#
# Use --debug to output path before parsing, this is useful to find files that
# cause the script to choke.
#
# You may like to set kptr_restrict=2 before running script
# (see Documentation/sysctl/kernel.txt).
# When the system is idle it is likely that most files under /proc/PID will be
# identical for various processes. Scanning _all_ the PIDs under /proc is
# unnecessary and implies that we are thoroughly scanning /proc. This is _not_
# the case because there may be ways userspace can trigger creation of /proc
# files that leak addresses but were not present during a scan. For these two
# reasons we exclude all PID directories under /proc except '1/'
use warnings;
use strict;
@ -22,9 +27,10 @@ use Cwd 'abs_path';
use Term::ANSIColor qw(:constants);
use Getopt::Long qw(:config no_auto_abbrev);
use Config;
use bigint qw/hex/;
use feature 'state';
my $P = $0;
my $V = '0.01';
# Directories to scan.
my @DIRS = ('/proc', '/sys');
@ -32,10 +38,9 @@ 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');
# Kernel addresses vary by architecture. We can only auto-detect the following
# architectures (using `uname -m`). (flag --32-bit overrides auto-detection.)
my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64', 'x86');
# Command line options.
my $help = 0;
@ -43,25 +48,24 @@ my $debug = 0;
my $raw = 0;
my $output_raw = ""; # Write raw results to file.
my $input_raw = ""; # Read raw results from file instead of scanning.
my $suppress_dmesg = 0; # Don't show dmesg in output.
my $squash_by_path = 0; # Summary report grouped by absolute path.
my $squash_by_filename = 0; # Summary report grouped by filename.
my $kernel_config_file = ""; # Kernel configuration file.
my $opt_32bit = 0; # Scan 32-bit kernel.
my $page_offset_32bit = 0; # Page offset for 32-bit kernel.
# Do not parse these files (absolute path).
my @skip_parse_files_abs = ('/proc/kmsg',
'/proc/kcore',
'/proc/fs/ext4/sdb1/mb_groups',
'/proc/1/fd/3',
'/sys/firmware/devicetree',
# Skip these absolute paths.
my @skip_abs = (
'/proc/kmsg',
'/proc/device-tree',
'/proc/1/syscall',
'/sys/firmware/devicetree',
'/sys/kernel/debug/tracing/trace_pipe',
'/sys/kernel/security/apparmor/revision');
# Do not parse these files under any subdirectory.
my @skip_parse_files_any = ('0',
'1',
'2',
# Skip these under any subdirectory.
my @skip_any = (
'pagemap',
'events',
'access',
@ -69,20 +73,9 @@ my @skip_parse_files_any = ('0',
'snapshot_raw',
'trace_pipe_raw',
'ptmx',
'trace_pipe');
# Do not walk these directories (absolute path).
my @skip_walk_dirs_abs = ();
# Do not walk these directories under any subdirectory.
my @skip_walk_dirs_any = ('self',
'thread-self',
'cwd',
'trace_pipe',
'fd',
'usbmon',
'stderr',
'stdin',
'stdout');
'usbmon');
sub help
{
@ -91,7 +84,6 @@ sub help
print << "EOM";
Usage: $P [OPTIONS]
Version: $V
Options:
@ -101,21 +93,13 @@ Options:
--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)
--32-bit Scan 32-bit kernel.
--page-offset-32-bit=o Page offset (for 32-bit kernel 0xABCD1234).
-d, --debug Display debugging output.
-h, --help, --version Display this help and exit.
Examples:
# Scan kernel and dump raw results.
$0
# Scan kernel and save results to file.
$0 --output-raw scan.out
# View summary report.
$0 --input-raw scan.out --squash-by-filename
Scans the running (64 bit) kernel for potential leaking addresses.
Scans the running kernel for potential leaking addresses.
EOM
exit($exitcode);
@ -131,6 +115,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_32bit,
'page-offset-32-bit=o' => \$page_offset_32bit,
) or help(1);
help(0) if ($help);
@ -146,16 +133,19 @@ if (!$input_raw and ($squash_by_path or $squash_by_filename)) {
exit(128);
}
if (!is_supported_architecture()) {
if (!(is_supported_architecture() or $opt_32bit or $page_offset_32bit)) {
printf "\nScript does not support your architecture, sorry.\n";
printf "\nCurrently we support: \n\n";
foreach(@SUPPORTED_ARCHITECTURES) {
printf "\t%s\n", $_;
}
printf("\n");
my $archname = $Config{archname};
printf "\n\$ perl -MConfig -e \'print \"\$Config{archname}\\n\"\'\n";
printf "%s\n", $archname;
printf("If you are running a 32-bit architecture you may use:\n");
printf("\n\t--32-bit or --page-offset-32-bit=<page offset>\n\n");
my $archname = `uname -m`;
printf("Machine hardware name (`uname -m`): %s\n", $archname);
exit(129);
}
@ -177,49 +167,183 @@ sub dprint
sub is_supported_architecture
{
return (is_x86_64() or is_ppc64());
return (is_x86_64() or is_ppc64() or is_ix86_32());
}
sub is_32bit
{
# Allow --32-bit or --page-offset-32-bit to override
if ($opt_32bit or $page_offset_32bit) {
return 1;
}
return is_ix86_32();
}
sub is_ix86_32
{
state $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 is_x86_64
{
my $archname = $Config{archname};
if ($archname =~ m/x86_64/) {
return 1;
}
return 0;
state $is = is_arch('x86_64');
return $is;
}
sub is_ppc64
{
my $archname = $Config{archname};
if ($archname =~ m/powerpc/ and $archname =~ m/64/) {
return 1;
state $is = is_arch('ppc64');
return $is;
}
return 0;
# Gets config option value from kernel config file.
# Returns "" on error or if config option not found.
sub get_kernel_config_option
{
my ($option) = @_;
my $value = "";
my $tmp_file = "";
my @config_files;
# Allow --kernel-config-file to override.
if ($kernel_config_file ne "") {
@config_files = ($kernel_config_file);
} elsif (-R "/proc/config.gz") {
my $tmp_file = "/tmp/tmpkconf";
if (system("gunzip < /proc/config.gz > $tmp_file")) {
dprint "$0: system(gunzip < /proc/config.gz) failed\n";
return "";
} else {
@config_files = ($tmp_file);
}
} else {
my $file = '/boot/config-' . `uname -r`;
chomp $file;
@config_files = ($file, '/boot/config');
}
foreach my $file (@config_files) {
dprint("parsing config file: %s\n", $file);
$value = option_from_file($option, $file);
if ($value ne "") {
last;
}
}
if ($tmp_file ne "") {
system("rm -f $tmp_file");
}
return $value;
}
# Parses $file and returns kernel configuration option value.
sub option_from_file
{
my ($option, $file) = @_;
my $str = "";
my $val = "";
open(my $fh, "<", $file) or return "";
while (my $line = <$fh> ) {
if ($line =~ /^$option/) {
($str, $val) = split /=/, $line;
chomp $val;
last;
}
}
close $fh;
return $val;
}
sub is_false_positive
{
my ($match) = @_;
if (is_32bit()) {
return is_false_positive_32bit($match);
}
# 64 bit false positives.
if ($match =~ '\b(0x)?(f|F){16}\b' or
$match =~ '\b(0x)?0{16}\b') {
return 1;
}
if (is_x86_64) {
# vsyscall memory region, we should probably check against a range here.
if ($match =~ '\bf{10}600000\b' or
$match =~ '\bf{10}601000\b') {
if (is_x86_64() and is_in_vsyscall_memory_region($match)) {
return 1;
}
}
return 0;
}
sub is_false_positive_32bit
{
my ($match) = @_;
state $page_offset = get_page_offset();
if ($match =~ '\b(0x)?(f|F){8}\b') {
return 1;
}
if (hex($match) < $page_offset) {
return 1;
}
return 0;
}
# returns integer value
sub get_page_offset
{
my $page_offset;
my $default_offset = 0xc0000000;
# Allow --page-offset-32bit to override.
if ($page_offset_32bit != 0) {
return $page_offset_32bit;
}
$page_offset = get_kernel_config_option('CONFIG_PAGE_OFFSET');
if (!$page_offset) {
return $default_offset;
}
return $page_offset;
}
sub is_in_vsyscall_memory_region
{
my ($match) = @_;
my $hex = hex($match);
my $region_min = hex("0xffffffffff600000");
my $region_max = hex("0xffffffffff601000");
return ($hex >= $region_min and $hex <= $region_max);
}
# True if argument potentially contains a kernel address.
sub may_leak_address
{
@ -238,14 +362,8 @@ sub may_leak_address
return 0;
}
# One of these is guaranteed to be true.
if (is_x86_64()) {
$address_re = '\b(0x)?ffff[[:xdigit:]]{12}\b';
} elsif (is_ppc64()) {
$address_re = '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b';
}
while (/($address_re)/g) {
$address_re = get_address_re();
while ($line =~ /($address_re)/g) {
if (!is_false_positive($1)) {
return 1;
}
@ -254,6 +372,31 @@ sub may_leak_address
return 0;
}
sub get_address_re
{
if (is_ppc64()) {
return '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b';
} elsif (is_32bit()) {
return '\b(0x)?[[:xdigit:]]{8}\b';
}
return get_x86_64_re();
}
sub get_x86_64_re
{
# We handle page table levels but only if explicitly configured using
# CONFIG_PGTABLE_LEVELS. If config file parsing fails or config option
# is not found we default to using address regular expression suitable
# for 4 page table levels.
state $ptl = get_kernel_config_option('CONFIG_PGTABLE_LEVELS');
if ($ptl == 5) {
return '\b(0x)?ff[[:xdigit:]]{14}\b';
}
return '\b(0x)?ffff[[:xdigit:]]{12}\b';
}
sub parse_dmesg
{
open my $cmd, '-|', 'dmesg';
@ -268,26 +411,20 @@ sub parse_dmesg
# True if we should skip this path.
sub skip
{
my ($path, $paths_abs, $paths_any) = @_;
my ($path) = @_;
foreach (@$paths_abs) {
foreach (@skip_abs) {
return 1 if (/^$path$/);
}
my($filename, $dirs, $suffix) = fileparse($path);
foreach (@$paths_any) {
foreach (@skip_any) {
return 1 if (/^$filename$/);
}
return 0;
}
sub skip_parse
{
my ($path) = @_;
return skip($path, \@skip_parse_files_abs, \@skip_parse_files_any);
}
sub timed_parse_file
{
my ($file) = @_;
@ -313,11 +450,9 @@ sub parse_file
return;
}
if (skip_parse($file)) {
dprint "skipping file: $file\n";
if (! -T $file) {
return;
}
dprint "parsing: $file\n";
open my $fh, "<", $file or return;
while ( <$fh> ) {
@ -328,12 +463,14 @@ sub parse_file
close $fh;
}
# True if we should skip walking this directory.
sub skip_walk
# Checks if the actual path name is leaking a kernel address.
sub check_path_for_leaks
{
my ($path) = @_;
return skip($path, \@skip_walk_dirs_abs, \@skip_walk_dirs_any)
if (may_leak_address($path)) {
printf("Path name may contain address: $path\n");
}
}
# Recursively walk directory tree.
@ -342,7 +479,6 @@ sub walk
my @dirs = @_;
while (my $pwd = shift @dirs) {
next if (skip_walk($pwd));
next if (!opendir(DIR, $pwd));
my @files = readdir(DIR);
closedir(DIR);
@ -353,11 +489,21 @@ sub walk
my $path = "$pwd/$file";
next if (-l $path);
# skip /proc/PID except /proc/1
next if (($path =~ /^\/proc\/[0-9]+$/) &&
($path !~ /^\/proc\/1$/));
next if (skip($path));
check_path_for_leaks($path);
if (-d $path) {
push @dirs, $path;
} else {
timed_parse_file($path);
next;
}
dprint "parsing: $path\n";
timed_parse_file($path);
}
}
}