diff --git a/MAINTAINERS b/MAINTAINERS
index 2f4e462aa4a2..a7995c737728 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7745,6 +7745,11 @@ S:	Maintained
 F:	Documentation/scsi/53c700.txt
 F:	drivers/scsi/53c700*
 
+LEAKING_ADDRESSES
+M:	Tobin C. Harding <me@tobin.cc>
+S:	Maintained
+F:	scripts/leaking_addresses.pl
+
 LED SUBSYSTEM
 M:	Richard Purdie <rpurdie@rpsys.net>
 M:	Jacek Anaszewski <jacek.anaszewski@gmail.com>
diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl
new file mode 100755
index 000000000000..282c0cc2bdea
--- /dev/null
+++ b/scripts/leaking_addresses.pl
@@ -0,0 +1,437 @@
+#!/usr/bin/env perl
+#
+# (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.
+#  - 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).
+
+use warnings;
+use strict;
+use POSIX;
+use File::Basename;
+use File::Spec;
+use Cwd 'abs_path';
+use Term::ANSIColor qw(:constants);
+use Getopt::Long qw(:config no_auto_abbrev);
+use File::Spec::Functions 'catfile';
+
+my $P = $0;
+my $V = '0.01';
+
+# Directories to scan (we scan `dmesg` also).
+my @DIRS = ('/proc', '/sys');
+
+# Output path for raw scan data, set by set_ouput_path().
+my $OUTPUT = "";
+
+# Command line options.
+my $output = "";
+my $suppress_dmesg = 0;
+my $squash_by_path = 0;
+my $raw = 0;
+my $send_report = 0;
+my $help = 0;
+my $debug = 0;
+
+# 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/kernel/debug/tracing/trace_pipe',
+			    '/sys/kernel/security/apparmor/revision');
+
+# Do not parse thes files under any subdirectory.
+my @skip_parse_files_any = ('0',
+			    '1',
+			    '2',
+			    'pagemap',
+			    'events',
+			    'access',
+			    'registers',
+			    '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',
+			  'fd',
+			  'stderr',
+			  'stdin',
+			  'stdout');
+
+sub help
+{
+	my ($exitcode) = @_;
+
+	print << "EOM";
+Usage: $P COMMAND [OPTIONS]
+Version: $V
+
+Commands:
+
+	scan	Scan the kernel (savesg raw results to file and runs `format`).
+	format	Parse results file and format output.
+
+Options:
+	-o, --output=<path>	 Accepts absolute or relative filename or directory name.
+	    --suppress-dmesg	 Don't show dmesg results.
+	    --squash-by-path	 Show one result per unique path.
+	    --raw	 	 Show raw results.
+	    --send-report	 Submit raw results for someone else to worry about.
+	-d, --debug              Display debugging output.
+	-h, --help, --version    Display this help and exit.
+
+Scans the running (64 bit) kernel for potential leaking addresses.
+}
+
+EOM
+	exit($exitcode);
+}
+
+GetOptions(
+        'o|output=s'		=> \$output,
+        'suppress-dmesg'	=> \$suppress_dmesg,
+        'squash-by-path'	=> \$squash_by_path,
+        'raw'			=> \$raw,
+        'send-report'		=> \$send_report,
+        'd|debug'		=> \$debug,
+        'h|help'		=> \$help,
+        'version'		=> \$help
+) or help(1);
+
+help(0) if ($help);
+
+my ($command) = @ARGV;
+if (not defined $command) {
+        help(128);
+}
+
+set_output_path($output);
+
+if ($command ne 'scan' and $command ne 'format') {
+        printf "\nUnknown command: %s\n\n", $command;
+        help(128);
+}
+
+if ($command eq 'scan') {
+        scan();
+}
+
+if ($send_report) {
+        send_report();
+        print "Raw scan results sent, thank you.\n";
+        exit(0);
+}
+
+format_output();
+
+exit 0;
+
+sub dprint
+{
+	printf(STDERR @_) if $debug;
+}
+
+# Sets global $OUTPUT, defaults to "./scan.out"
+# Accepts relative or absolute path (directory name or filename).
+sub set_output_path
+{
+        my ($path) = @_;
+        my $def_filename = "scan.out";
+        my $def_dirname = getcwd();
+
+        if ($path eq "") {
+                $OUTPUT = catfile($def_dirname, $def_filename);
+                return;
+        }
+
+        my($filename, $dirs, $suffix) = fileparse($path);
+
+        if ($filename eq "") {
+                $OUTPUT = catfile($dirs, $def_filename);
+        } elsif ($filename) {
+                $OUTPUT = catfile($dirs, $filename);
+        }
+}
+
+sub scan
+{
+        open (my $fh, '>', "$OUTPUT") or die "Cannot open $OUTPUT\n";
+        select $fh;
+
+        parse_dmesg();
+        walk(@DIRS);
+
+        select STDOUT;
+}
+
+sub send_report
+{
+        system("mail -s 'LEAK REPORT' leaks\@tobin.cc < $OUTPUT");
+}
+
+sub parse_dmesg
+{
+	open my $cmd, '-|', 'dmesg';
+	while (<$cmd>) {
+		if (may_leak_address($_)) {
+			print 'dmesg: ' . $_;
+		}
+	}
+	close $cmd;
+}
+
+# Recursively walk directory tree.
+sub walk
+{
+	my @dirs = @_;
+	my %seen;
+
+	while (my $pwd = shift @dirs) {
+		next if (skip_walk($pwd));
+		next if (!opendir(DIR, $pwd));
+		my @files = readdir(DIR);
+		closedir(DIR);
+
+		foreach my $file (@files) {
+			next if ($file eq '.' or $file eq '..');
+
+			my $path = "$pwd/$file";
+			next if (-l $path);
+
+			if (-d $path) {
+				push @dirs, $path;
+			} else {
+				parse_file($path);
+			}
+		}
+	}
+}
+
+# True if argument potentially contains a kernel address.
+sub may_leak_address
+{
+        my ($line) = @_;
+
+        my @addresses = extract_addresses($line);
+        return @addresses > 0;
+}
+
+# Return _all_ non false positive addresses from $line.
+sub extract_addresses
+{
+        my ($line) = @_;
+        my $address = '\b(0x)?ffff[[:xdigit:]]{12}\b';
+        my (@addresses, @empty);
+
+        # Signal masks.
+        if ($line =~ '^SigBlk:' or
+            $line =~ '^SigCgt:') {
+                return @empty;
+        }
+
+        if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or
+            $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') {
+                return @empty;
+        }
+
+        while ($line =~ /($address)/g) {
+                if (!is_false_positive($1)) {
+                        push @addresses, $1;
+                }
+        }
+
+        return @addresses;
+}
+
+# True if we should skip walking this directory.
+sub skip_walk
+{
+	my ($path) = @_;
+	return skip($path, \@skip_walk_dirs_abs, \@skip_walk_dirs_any)
+}
+
+sub parse_file
+{
+	my ($file) = @_;
+
+	if (! -R $file) {
+		return;
+	}
+
+	if (skip_parse($file)) {
+		dprint "skipping file: $file\n";
+		return;
+	}
+	dprint "parsing: $file\n";
+
+	open my $fh, "<", $file or return;
+	while ( <$fh> ) {
+		if (may_leak_address($_)) {
+			print $file . ': ' . $_;
+		}
+	}
+	close $fh;
+}
+
+sub is_false_positive
+{
+        my ($match) = @_;
+
+        if ($match =~ '\b(0x)?(f|F){16}\b' or
+            $match =~ '\b(0x)?0{16}\b') {
+                return 1;
+        }
+
+        # vsyscall memory region, we should probably check against a range here.
+        if ($match =~ '\bf{10}600000\b' or
+            $match =~ '\bf{10}601000\b') {
+                return 1;
+        }
+
+        return 0;
+}
+
+# True if we should skip this path.
+sub skip
+{
+	my ($path, $paths_abs, $paths_any) = @_;
+
+	foreach (@$paths_abs) {
+		return 1 if (/^$path$/);
+	}
+
+	my($filename, $dirs, $suffix) = fileparse($path);
+	foreach (@$paths_any) {
+		return 1 if (/^$filename$/);
+	}
+
+	return 0;
+}
+
+sub skip_parse
+{
+	my ($path) = @_;
+	return skip($path, \@skip_parse_files_abs, \@skip_parse_files_any);
+}
+
+sub format_output
+{
+        if ($raw) {
+                dump_raw_output();
+                return;
+        }
+
+        my ($total, $dmesg, $paths, $files) = parse_raw_file();
+
+        printf "\nTotal number of results from scan (incl dmesg): %d\n", $total;
+
+        if (!$suppress_dmesg) {
+                print_dmesg($dmesg);
+        }
+        squash_by($files, 'filename');
+
+        if ($squash_by_path) {
+                squash_by($paths, 'path');
+        }
+}
+
+sub dump_raw_output
+{
+        open (my $fh, '<', $OUTPUT) or die "Cannot open $OUTPUT\n";
+        while (<$fh>) {
+                print $_;
+        }
+        close $fh;
+}
+
+sub print_dmesg
+{
+        my ($dmesg) = @_;
+
+        print "\ndmesg output:\n";
+        foreach(@$dmesg) {
+                my $index = index($_, ':');
+                $index += 2;    # skid ': '
+                print substr($_, $index);
+        }
+}
+
+sub squash_by
+{
+        my ($ref, $desc) = @_;
+
+        print "\nResults squashed by $desc (excl dmesg). ";
+        print "Displaying <number of results>, <$desc>, <example result>\n";
+        foreach(keys %$ref) {
+                my $lines = $ref->{$_};
+                my $length = @$lines;
+                printf "[%d %s] %s", $length, $_, @$lines[0];
+        }
+}
+
+sub parse_raw_file
+{
+        my $total = 0;          # Total number of lines parsed.
+        my @dmesg;              # dmesg output.
+        my %files;              # Unique filenames containing leaks.
+        my %paths;              # Unique paths containing leaks.
+
+        open (my $fh, '<', $OUTPUT) or die "Cannot open $OUTPUT\n";
+
+        while (my $line = <$fh>) {
+                $total++;
+
+                if ("dmesg:" eq substr($line, 0, 6)) {
+                        push @dmesg, $line;
+                        next;
+                }
+
+                cache_path(\%paths, $line);
+                cache_filename(\%files, $line);
+        }
+
+        return $total, \@dmesg, \%paths, \%files;
+}
+
+sub cache_path
+{
+        my ($paths, $line) = @_;
+
+        my $index = index($line, ':');
+        my $path = substr($line, 0, $index);
+
+        if (!$paths->{$path}) {
+                $paths->{$path} = ();
+        }
+        push @{$paths->{$path}}, $line;
+}
+
+sub cache_filename
+{
+        my ($files, $line) = @_;
+
+        my $index = index($line, ':');
+        my $path = substr($line, 0, $index);
+        my $filename = basename($path);
+        if (!$files->{$filename}) {
+                $files->{$filename} = ();
+        }
+        $index += 2;            # skip ': '
+        push @{$files->{$filename}}, substr($line, $index);
+}
