leaking_addresses: add to exclude files/paths list
[linux-block.git] / scripts / leaking_addresses.pl
CommitLineData
136fc5c4
TH
1#!/usr/bin/env perl
2#
3# (c) 2017 Tobin C. Harding <me@tobin.cc>
4# Licensed under the terms of the GNU GPL License version 2
5#
6# leaking_addresses.pl: Scan 64 bit kernel for potential leaking addresses.
7# - Scans dmesg output.
8# - Walks directory tree and parses each file (for each directory in @DIRS).
9#
136fc5c4
TH
10# Use --debug to output path before parsing, this is useful to find files that
11# cause the script to choke.
12#
13# You may like to set kptr_restrict=2 before running script
14# (see Documentation/sysctl/kernel.txt).
15
16use warnings;
17use strict;
18use POSIX;
19use File::Basename;
20use File::Spec;
21use Cwd 'abs_path';
22use Term::ANSIColor qw(:constants);
23use Getopt::Long qw(:config no_auto_abbrev);
24
25my $P = $0;
26my $V = '0.01';
27
28# Directories to scan.
29my @DIRS = ('/proc', '/sys');
30
31# Command line options.
32my $help = 0;
33my $debug = 0;
136fc5c4
TH
34
35# Do not parse these files (absolute path).
36my @skip_parse_files_abs = ('/proc/kmsg',
37 '/proc/kcore',
38 '/proc/fs/ext4/sdb1/mb_groups',
39 '/proc/1/fd/3',
1c1e3be0
TH
40 '/sys/firmware/devicetree',
41 '/proc/device-tree',
136fc5c4
TH
42 '/sys/kernel/debug/tracing/trace_pipe',
43 '/sys/kernel/security/apparmor/revision');
44
a284733e 45# Do not parse these files under any subdirectory.
136fc5c4
TH
46my @skip_parse_files_any = ('0',
47 '1',
48 '2',
49 'pagemap',
50 'events',
51 'access',
52 'registers',
53 'snapshot_raw',
54 'trace_pipe_raw',
55 'ptmx',
56 'trace_pipe');
57
58# Do not walk these directories (absolute path).
59my @skip_walk_dirs_abs = ();
60
61# Do not walk these directories under any subdirectory.
62my @skip_walk_dirs_any = ('self',
63 'thread-self',
64 'cwd',
65 'fd',
1c1e3be0 66 'usbmon',
136fc5c4
TH
67 'stderr',
68 'stdin',
69 'stdout');
70
71sub help
72{
73 my ($exitcode) = @_;
74
75 print << "EOM";
76Usage: $P [OPTIONS]
77Version: $V
78
79Options:
80
136fc5c4
TH
81 -d, --debug Display debugging output.
82 -h, --help, --version Display this help and exit.
83
136fc5c4
TH
84Scans the running (64 bit) kernel for potential leaking addresses.
85
86EOM
87 exit($exitcode);
88}
89
90GetOptions(
136fc5c4
TH
91 'd|debug' => \$debug,
92 'h|help' => \$help,
93 'version' => \$help
94) or help(1);
95
96help(0) if ($help);
97
136fc5c4
TH
98parse_dmesg();
99walk(@DIRS);
100
101exit 0;
102
136fc5c4
TH
103sub dprint
104{
105 printf(STDERR @_) if $debug;
106}
107
136fc5c4
TH
108sub is_false_positive
109{
7e5758f7
TH
110 my ($match) = @_;
111
112 if ($match =~ '\b(0x)?(f|F){16}\b' or
113 $match =~ '\b(0x)?0{16}\b') {
114 return 1;
115 }
136fc5c4 116
136fc5c4 117
7e5758f7
TH
118 if ($match =~ '\bf{10}600000\b' or# vsyscall memory region, we should probably check against a range here.
119 $match =~ '\bf{10}601000\b') {
120 return 1;
121 }
136fc5c4 122
7e5758f7 123 return 0;
136fc5c4
TH
124}
125
126# True if argument potentially contains a kernel address.
127sub may_leak_address
128{
7e5758f7
TH
129 my ($line) = @_;
130 my $address = '\b(0x)?ffff[[:xdigit:]]{12}\b';
136fc5c4 131
7e5758f7
TH
132 # Signal masks.
133 if ($line =~ '^SigBlk:' or
134 $line =~ '^SigCgt:') {
135 return 0;
136 }
136fc5c4 137
7e5758f7
TH
138 if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or
139 $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') {
136fc5c4 140 return 0;
7e5758f7 141 }
136fc5c4 142
7e5758f7
TH
143 while (/($address)/g) {
144 if (!is_false_positive($1)) {
145 return 1;
146 }
147 }
136fc5c4 148
7e5758f7 149 return 0;
136fc5c4
TH
150}
151
152sub parse_dmesg
153{
154 open my $cmd, '-|', 'dmesg';
155 while (<$cmd>) {
156 if (may_leak_address($_)) {
157 print 'dmesg: ' . $_;
158 }
159 }
160 close $cmd;
161}
162
163# True if we should skip this path.
164sub skip
165{
166 my ($path, $paths_abs, $paths_any) = @_;
167
168 foreach (@$paths_abs) {
169 return 1 if (/^$path$/);
170 }
171
172 my($filename, $dirs, $suffix) = fileparse($path);
173 foreach (@$paths_any) {
174 return 1 if (/^$filename$/);
175 }
176
177 return 0;
178}
179
180sub skip_parse
181{
182 my ($path) = @_;
183 return skip($path, \@skip_parse_files_abs, \@skip_parse_files_any);
184}
185
186sub parse_file
187{
188 my ($file) = @_;
189
190 if (! -R $file) {
191 return;
192 }
193
194 if (skip_parse($file)) {
195 dprint "skipping file: $file\n";
196 return;
197 }
198 dprint "parsing: $file\n";
199
200 open my $fh, "<", $file or return;
201 while ( <$fh> ) {
202 if (may_leak_address($_)) {
203 print $file . ': ' . $_;
204 }
205 }
206 close $fh;
207}
208
209
210# True if we should skip walking this directory.
211sub skip_walk
212{
213 my ($path) = @_;
214 return skip($path, \@skip_walk_dirs_abs, \@skip_walk_dirs_any)
215}
216
217# Recursively walk directory tree.
218sub walk
219{
220 my @dirs = @_;
136fc5c4
TH
221
222 while (my $pwd = shift @dirs) {
223 next if (skip_walk($pwd));
224 next if (!opendir(DIR, $pwd));
225 my @files = readdir(DIR);
226 closedir(DIR);
227
228 foreach my $file (@files) {
229 next if ($file eq '.' or $file eq '..');
230
231 my $path = "$pwd/$file";
232 next if (-l $path);
233
234 if (-d $path) {
235 push @dirs, $path;
236 } else {
237 parse_file($path);
238 }
239 }
240 }
241}