Merge tag 'gpio-v4.15-1' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/linusw...
[sfrench/cifs-2.6.git] / scripts / leaking_addresses.pl
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 #
10 # You can configure the behaviour of the script;
11 #
12 #  - By adding paths, for directories you do not want to walk;
13 #     absolute paths: @skip_walk_dirs_abs
14 #     directory names: @skip_walk_dirs_any
15 #
16 #  - By adding paths, for files you do not want to parse;
17 #     absolute paths: @skip_parse_files_abs
18 #     file names: @skip_parse_files_any
19 #
20 # The use of @skip_xxx_xxx_any causes files to be skipped where ever they occur.
21 # For example adding 'fd' to @skip_walk_dirs_any causes the fd/ directory to be
22 # skipped for all PID sub-directories of /proc
23 #
24 # The same thing can be achieved by passing command line options to --dont-walk
25 # and --dont-parse. If absolute paths are supplied to these options they are
26 # appended to the @skip_xxx_xxx_abs arrays. If file names are supplied to these
27 # options, they are appended to the @skip_xxx_xxx_any arrays.
28 #
29 # Use --debug to output path before parsing, this is useful to find files that
30 # cause the script to choke.
31 #
32 # You may like to set kptr_restrict=2 before running script
33 # (see Documentation/sysctl/kernel.txt).
34
35 use warnings;
36 use strict;
37 use POSIX;
38 use File::Basename;
39 use File::Spec;
40 use Cwd 'abs_path';
41 use Term::ANSIColor qw(:constants);
42 use Getopt::Long qw(:config no_auto_abbrev);
43
44 my $P = $0;
45 my $V = '0.01';
46
47 # Directories to scan.
48 my @DIRS = ('/proc', '/sys');
49
50 # Command line options.
51 my $help = 0;
52 my $debug = 0;
53 my @dont_walk = ();
54 my @dont_parse = ();
55
56 # Do not parse these files (absolute path).
57 my @skip_parse_files_abs = ('/proc/kmsg',
58                             '/proc/kcore',
59                             '/proc/fs/ext4/sdb1/mb_groups',
60                             '/proc/1/fd/3',
61                             '/sys/kernel/debug/tracing/trace_pipe',
62                             '/sys/kernel/security/apparmor/revision');
63
64 # Do not parse thes files under any subdirectory.
65 my @skip_parse_files_any = ('0',
66                             '1',
67                             '2',
68                             'pagemap',
69                             'events',
70                             'access',
71                             'registers',
72                             'snapshot_raw',
73                             'trace_pipe_raw',
74                             'ptmx',
75                             'trace_pipe');
76
77 # Do not walk these directories (absolute path).
78 my @skip_walk_dirs_abs = ();
79
80 # Do not walk these directories under any subdirectory.
81 my @skip_walk_dirs_any = ('self',
82                           'thread-self',
83                           'cwd',
84                           'fd',
85                           'stderr',
86                           'stdin',
87                           'stdout');
88
89 sub help
90 {
91         my ($exitcode) = @_;
92
93         print << "EOM";
94 Usage: $P [OPTIONS]
95 Version: $V
96
97 Options:
98
99         --dont-walk=<dir>      Don't walk tree starting at <dir>.
100         --dont-parse=<file>    Don't parse <file>.
101         -d, --debug                Display debugging output.
102         -h, --help, --version      Display this help and exit.
103
104 If an absolute path is passed to --dont_XXX then this path is skipped. If a
105 single filename is passed then this file/directory will be skipped when
106 appearing under any subdirectory.
107
108 Example:
109
110         # Just scan dmesg output.
111         scripts/leaking_addresses.pl --dont_walk_abs /proc --dont_walk_abs /sys
112
113 Scans the running (64 bit) kernel for potential leaking addresses.
114
115 EOM
116         exit($exitcode);
117 }
118
119 GetOptions(
120         'dont-walk=s'           => \@dont_walk,
121         'dont-parse=s'          => \@dont_parse,
122         'd|debug'               => \$debug,
123         'h|help'                => \$help,
124         'version'               => \$help
125 ) or help(1);
126
127 help(0) if ($help);
128
129 push_to_global();
130
131 parse_dmesg();
132 walk(@DIRS);
133
134 exit 0;
135
136 sub debug_arrays
137 {
138         print 'dirs_any: ' . join(", ", @skip_walk_dirs_any) . "\n";
139         print 'dirs_abs: ' . join(", ", @skip_walk_dirs_abs) . "\n";
140         print 'parse_any: ' . join(", ", @skip_parse_files_any) . "\n";
141         print 'parse_abs: ' . join(", ", @skip_parse_files_abs) . "\n";
142 }
143
144 sub dprint
145 {
146         printf(STDERR @_) if $debug;
147 }
148
149 sub push_in_abs_any
150 {
151         my ($in, $abs, $any) = @_;
152
153         foreach my $path (@$in) {
154                 if (File::Spec->file_name_is_absolute($path)) {
155                         push @$abs, $path;
156                 } elsif (index($path,'/') == -1) {
157                         push @$any, $path;
158                 } else {
159                         print 'path error: ' . $path;
160                 }
161         }
162 }
163
164 # Push command line options to global arrays.
165 sub push_to_global
166 {
167         push_in_abs_any(\@dont_walk, \@skip_walk_dirs_abs, \@skip_walk_dirs_any);
168         push_in_abs_any(\@dont_parse, \@skip_parse_files_abs, \@skip_parse_files_any);
169 }
170
171 sub is_false_positive
172 {
173         my ($match) = @_;
174
175         if ($match =~ '\b(0x)?(f|F){16}\b' or
176             $match =~ '\b(0x)?0{16}\b') {
177                 return 1;
178         }
179
180         # vsyscall memory region, we should probably check against a range here.
181         if ($match =~ '\bf{10}600000\b' or
182             $match =~ '\bf{10}601000\b') {
183                 return 1;
184         }
185
186         return 0;
187 }
188
189 # True if argument potentially contains a kernel address.
190 sub may_leak_address
191 {
192         my ($line) = @_;
193         my $address = '\b(0x)?ffff[[:xdigit:]]{12}\b';
194
195         # Signal masks.
196         if ($line =~ '^SigBlk:' or
197             $line =~ '^SigCgt:') {
198                 return 0;
199         }
200
201         if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or
202             $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') {
203                 return 0;
204         }
205
206         while (/($address)/g) {
207                 if (!is_false_positive($1)) {
208                         return 1;
209                 }
210         }
211
212         return 0;
213 }
214
215 sub parse_dmesg
216 {
217         open my $cmd, '-|', 'dmesg';
218         while (<$cmd>) {
219                 if (may_leak_address($_)) {
220                         print 'dmesg: ' . $_;
221                 }
222         }
223         close $cmd;
224 }
225
226 # True if we should skip this path.
227 sub skip
228 {
229         my ($path, $paths_abs, $paths_any) = @_;
230
231         foreach (@$paths_abs) {
232                 return 1 if (/^$path$/);
233         }
234
235         my($filename, $dirs, $suffix) = fileparse($path);
236         foreach (@$paths_any) {
237                 return 1 if (/^$filename$/);
238         }
239
240         return 0;
241 }
242
243 sub skip_parse
244 {
245         my ($path) = @_;
246         return skip($path, \@skip_parse_files_abs, \@skip_parse_files_any);
247 }
248
249 sub parse_file
250 {
251         my ($file) = @_;
252
253         if (! -R $file) {
254                 return;
255         }
256
257         if (skip_parse($file)) {
258                 dprint "skipping file: $file\n";
259                 return;
260         }
261         dprint "parsing: $file\n";
262
263         open my $fh, "<", $file or return;
264         while ( <$fh> ) {
265                 if (may_leak_address($_)) {
266                         print $file . ': ' . $_;
267                 }
268         }
269         close $fh;
270 }
271
272
273 # True if we should skip walking this directory.
274 sub skip_walk
275 {
276         my ($path) = @_;
277         return skip($path, \@skip_walk_dirs_abs, \@skip_walk_dirs_any)
278 }
279
280 # Recursively walk directory tree.
281 sub walk
282 {
283         my @dirs = @_;
284         my %seen;
285
286         while (my $pwd = shift @dirs) {
287                 next if (skip_walk($pwd));
288                 next if (!opendir(DIR, $pwd));
289                 my @files = readdir(DIR);
290                 closedir(DIR);
291
292                 foreach my $file (@files) {
293                         next if ($file eq '.' or $file eq '..');
294
295                         my $path = "$pwd/$file";
296                         next if (-l $path);
297
298                         if (-d $path) {
299                                 push @dirs, $path;
300                         } else {
301                                 parse_file($path);
302                         }
303                 }
304         }
305 }