summaryrefslogtreecommitdiffstats
path: root/scripts/leaking_addresses.pl
diff options
context:
space:
mode:
authorTobin C. Harding <me@tobin.cc>2017-11-06 00:19:27 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2017-11-06 14:46:42 -0500
commit136fc5c41f349296db1910677bb7402b0eeff376 (patch)
treeeb9005942d3ac0327edb58b6bdc2bcbad5915f3d /scripts/leaking_addresses.pl
parentaf903dcd31e1b345d858ca2af9a84ed61c960b57 (diff)
scripts: add leaking_addresses.pl
Currently we are leaking addresses from the kernel to user space. This script is an attempt to find some of those leakages. Script parses `dmesg` output and /proc and /sys files for hex strings that look like kernel addresses. Only works for 64 bit kernels, the reason being that kernel addresses on 64 bit kernels have 'ffff' as the leading bit pattern making greping possible. On 32 kernels we don't have this luxury. Scripts is _slightly_ smarter than a straight grep, we check for false positives (all 0's or all 1's, and vsyscall start/finish addresses). [ I think there is a lot of room for improvement here, but it's already useful, so I'm merging it as-is. The whole "hash %p format" series is expected to go into 4.15, but will not fix %x users, and will not incentivize people to look at what they are leaking. - Linus ] Signed-off-by: Tobin C. Harding <me@tobin.cc> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'scripts/leaking_addresses.pl')
-rwxr-xr-xscripts/leaking_addresses.pl305
1 files changed, 305 insertions, 0 deletions
diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl
new file mode 100755
index 000000000000..2977371b2956
--- /dev/null
+++ b/scripts/leaking_addresses.pl
@@ -0,0 +1,305 @@
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
35use warnings;
36use strict;
37use POSIX;
38use File::Basename;
39use File::Spec;
40use Cwd 'abs_path';
41use Term::ANSIColor qw(:constants);
42use Getopt::Long qw(:config no_auto_abbrev);
43
44my $P = $0;
45my $V = '0.01';
46
47# Directories to scan.
48my @DIRS = ('/proc', '/sys');
49
50# Command line options.
51my $help = 0;
52my $debug = 0;
53my @dont_walk = ();
54my @dont_parse = ();
55
56# Do not parse these files (absolute path).
57my @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.
65my @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).
78my @skip_walk_dirs_abs = ();
79
80# Do not walk these directories under any subdirectory.
81my @skip_walk_dirs_any = ('self',
82 'thread-self',
83 'cwd',
84 'fd',
85 'stderr',
86 'stdin',
87 'stdout');
88
89sub help
90{
91 my ($exitcode) = @_;
92
93 print << "EOM";
94Usage: $P [OPTIONS]
95Version: $V
96
97Options:
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
104If an absolute path is passed to --dont_XXX then this path is skipped. If a
105single filename is passed then this file/directory will be skipped when
106appearing under any subdirectory.
107
108Example:
109
110 # Just scan dmesg output.
111 scripts/leaking_addresses.pl --dont_walk_abs /proc --dont_walk_abs /sys
112
113Scans the running (64 bit) kernel for potential leaking addresses.
114
115EOM
116 exit($exitcode);
117}
118
119GetOptions(
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
127help(0) if ($help);
128
129push_to_global();
130
131parse_dmesg();
132walk(@DIRS);
133
134exit 0;
135
136sub 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
144sub dprint
145{
146 printf(STDERR @_) if $debug;
147}
148
149sub 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.
165sub 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
171sub 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.
190sub 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
215sub 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.
227sub 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
243sub skip_parse
244{
245 my ($path) = @_;
246 return skip($path, \@skip_parse_files_abs, \@skip_parse_files_any);
247}
248
249sub 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.
274sub 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.
281sub 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}