1136fc5c4STobin C. Harding#!/usr/bin/env perl
2136fc5c4STobin C. Harding#
3136fc5c4STobin C. Harding# (c) 2017 Tobin C. Harding <me@tobin.cc>
4136fc5c4STobin C. Harding# Licensed under the terms of the GNU GPL License version 2
5136fc5c4STobin C. Harding#
6136fc5c4STobin C. Harding# leaking_addresses.pl: Scan 64 bit kernel for potential leaking addresses.
7136fc5c4STobin C. Harding#  - Scans dmesg output.
8136fc5c4STobin C. Harding#  - Walks directory tree and parses each file (for each directory in @DIRS).
9136fc5c4STobin C. Harding#
10136fc5c4STobin C. Harding# You can configure the behaviour of the script;
11136fc5c4STobin C. Harding#
12136fc5c4STobin C. Harding#  - By adding paths, for directories you do not want to walk;
13136fc5c4STobin C. Harding#     absolute paths: @skip_walk_dirs_abs
14136fc5c4STobin C. Harding#     directory names: @skip_walk_dirs_any
15136fc5c4STobin C. Harding#
16136fc5c4STobin C. Harding#  - By adding paths, for files you do not want to parse;
17136fc5c4STobin C. Harding#     absolute paths: @skip_parse_files_abs
18136fc5c4STobin C. Harding#     file names: @skip_parse_files_any
19136fc5c4STobin C. Harding#
20136fc5c4STobin C. Harding# The use of @skip_xxx_xxx_any causes files to be skipped where ever they occur.
21136fc5c4STobin C. Harding# For example adding 'fd' to @skip_walk_dirs_any causes the fd/ directory to be
22136fc5c4STobin C. Harding# skipped for all PID sub-directories of /proc
23136fc5c4STobin C. Harding#
24136fc5c4STobin C. Harding# The same thing can be achieved by passing command line options to --dont-walk
25136fc5c4STobin C. Harding# and --dont-parse. If absolute paths are supplied to these options they are
26136fc5c4STobin C. Harding# appended to the @skip_xxx_xxx_abs arrays. If file names are supplied to these
27136fc5c4STobin C. Harding# options, they are appended to the @skip_xxx_xxx_any arrays.
28136fc5c4STobin C. Harding#
29136fc5c4STobin C. Harding# Use --debug to output path before parsing, this is useful to find files that
30136fc5c4STobin C. Harding# cause the script to choke.
31136fc5c4STobin C. Harding#
32136fc5c4STobin C. Harding# You may like to set kptr_restrict=2 before running script
33136fc5c4STobin C. Harding# (see Documentation/sysctl/kernel.txt).
34136fc5c4STobin C. Harding
35136fc5c4STobin C. Hardinguse warnings;
36136fc5c4STobin C. Hardinguse strict;
37136fc5c4STobin C. Hardinguse POSIX;
38136fc5c4STobin C. Hardinguse File::Basename;
39136fc5c4STobin C. Hardinguse File::Spec;
40136fc5c4STobin C. Hardinguse Cwd 'abs_path';
41136fc5c4STobin C. Hardinguse Term::ANSIColor qw(:constants);
42136fc5c4STobin C. Hardinguse Getopt::Long qw(:config no_auto_abbrev);
43136fc5c4STobin C. Harding
44136fc5c4STobin C. Hardingmy $P = $0;
45136fc5c4STobin C. Hardingmy $V = '0.01';
46136fc5c4STobin C. Harding
47136fc5c4STobin C. Harding# Directories to scan.
48136fc5c4STobin C. Hardingmy @DIRS = ('/proc', '/sys');
49136fc5c4STobin C. Harding
50136fc5c4STobin C. Harding# Command line options.
51136fc5c4STobin C. Hardingmy $help = 0;
52136fc5c4STobin C. Hardingmy $debug = 0;
53136fc5c4STobin C. Hardingmy @dont_walk = ();
54136fc5c4STobin C. Hardingmy @dont_parse = ();
55136fc5c4STobin C. Harding
56136fc5c4STobin C. Harding# Do not parse these files (absolute path).
57136fc5c4STobin C. Hardingmy @skip_parse_files_abs = ('/proc/kmsg',
58136fc5c4STobin C. Harding			    '/proc/kcore',
59136fc5c4STobin C. Harding			    '/proc/fs/ext4/sdb1/mb_groups',
60136fc5c4STobin C. Harding			    '/proc/1/fd/3',
61136fc5c4STobin C. Harding			    '/sys/kernel/debug/tracing/trace_pipe',
62136fc5c4STobin C. Harding			    '/sys/kernel/security/apparmor/revision');
63136fc5c4STobin C. Harding
64136fc5c4STobin C. Harding# Do not parse thes files under any subdirectory.
65136fc5c4STobin C. Hardingmy @skip_parse_files_any = ('0',
66136fc5c4STobin C. Harding			    '1',
67136fc5c4STobin C. Harding			    '2',
68136fc5c4STobin C. Harding			    'pagemap',
69136fc5c4STobin C. Harding			    'events',
70136fc5c4STobin C. Harding			    'access',
71136fc5c4STobin C. Harding			    'registers',
72136fc5c4STobin C. Harding			    'snapshot_raw',
73136fc5c4STobin C. Harding			    'trace_pipe_raw',
74136fc5c4STobin C. Harding			    'ptmx',
75136fc5c4STobin C. Harding			    'trace_pipe');
76136fc5c4STobin C. Harding
77136fc5c4STobin C. Harding# Do not walk these directories (absolute path).
78136fc5c4STobin C. Hardingmy @skip_walk_dirs_abs = ();
79136fc5c4STobin C. Harding
80136fc5c4STobin C. Harding# Do not walk these directories under any subdirectory.
81136fc5c4STobin C. Hardingmy @skip_walk_dirs_any = ('self',
82136fc5c4STobin C. Harding			  'thread-self',
83136fc5c4STobin C. Harding			  'cwd',
84136fc5c4STobin C. Harding			  'fd',
85136fc5c4STobin C. Harding			  'stderr',
86136fc5c4STobin C. Harding			  'stdin',
87136fc5c4STobin C. Harding			  'stdout');
88136fc5c4STobin C. Harding
89136fc5c4STobin C. Hardingsub help
90136fc5c4STobin C. Harding{
91136fc5c4STobin C. Harding	my ($exitcode) = @_;
92136fc5c4STobin C. Harding
93136fc5c4STobin C. Harding	print << "EOM";
94136fc5c4STobin C. HardingUsage: $P [OPTIONS]
95136fc5c4STobin C. HardingVersion: $V
96136fc5c4STobin C. Harding
97136fc5c4STobin C. HardingOptions:
98136fc5c4STobin C. Harding
99136fc5c4STobin C. Harding	--dont-walk=<dir>      Don't walk tree starting at <dir>.
100136fc5c4STobin C. Harding	--dont-parse=<file>    Don't parse <file>.
101136fc5c4STobin C. Harding	-d, --debug                Display debugging output.
102136fc5c4STobin C. Harding	-h, --help, --version      Display this help and exit.
103136fc5c4STobin C. Harding
104136fc5c4STobin C. HardingIf an absolute path is passed to --dont_XXX then this path is skipped. If a
105136fc5c4STobin C. Hardingsingle filename is passed then this file/directory will be skipped when
106136fc5c4STobin C. Hardingappearing under any subdirectory.
107136fc5c4STobin C. Harding
108136fc5c4STobin C. HardingExample:
109136fc5c4STobin C. Harding
110136fc5c4STobin C. Harding	# Just scan dmesg output.
111136fc5c4STobin C. Harding	scripts/leaking_addresses.pl --dont_walk_abs /proc --dont_walk_abs /sys
112136fc5c4STobin C. Harding
113136fc5c4STobin C. HardingScans the running (64 bit) kernel for potential leaking addresses.
114136fc5c4STobin C. Harding
115136fc5c4STobin C. HardingEOM
116136fc5c4STobin C. Harding	exit($exitcode);
117136fc5c4STobin C. Harding}
118136fc5c4STobin C. Harding
119136fc5c4STobin C. HardingGetOptions(
120136fc5c4STobin C. Harding	'dont-walk=s'		=> \@dont_walk,
121136fc5c4STobin C. Harding	'dont-parse=s'		=> \@dont_parse,
122136fc5c4STobin C. Harding	'd|debug'		=> \$debug,
123136fc5c4STobin C. Harding	'h|help'		=> \$help,
124136fc5c4STobin C. Harding	'version'		=> \$help
125136fc5c4STobin C. Harding) or help(1);
126136fc5c4STobin C. Harding
127136fc5c4STobin C. Hardinghelp(0) if ($help);
128136fc5c4STobin C. Harding
129136fc5c4STobin C. Hardingpush_to_global();
130136fc5c4STobin C. Harding
131136fc5c4STobin C. Hardingparse_dmesg();
132136fc5c4STobin C. Hardingwalk(@DIRS);
133136fc5c4STobin C. Harding
134136fc5c4STobin C. Hardingexit 0;
135136fc5c4STobin C. Harding
136136fc5c4STobin C. Hardingsub debug_arrays
137136fc5c4STobin C. Harding{
138136fc5c4STobin C. Harding	print 'dirs_any: ' . join(", ", @skip_walk_dirs_any) . "\n";
139136fc5c4STobin C. Harding	print 'dirs_abs: ' . join(", ", @skip_walk_dirs_abs) . "\n";
140136fc5c4STobin C. Harding	print 'parse_any: ' . join(", ", @skip_parse_files_any) . "\n";
141136fc5c4STobin C. Harding	print 'parse_abs: ' . join(", ", @skip_parse_files_abs) . "\n";
142136fc5c4STobin C. Harding}
143136fc5c4STobin C. Harding
144136fc5c4STobin C. Hardingsub dprint
145136fc5c4STobin C. Harding{
146136fc5c4STobin C. Harding	printf(STDERR @_) if $debug;
147136fc5c4STobin C. Harding}
148136fc5c4STobin C. Harding
149136fc5c4STobin C. Hardingsub push_in_abs_any
150136fc5c4STobin C. Harding{
151136fc5c4STobin C. Harding	my ($in, $abs, $any) = @_;
152136fc5c4STobin C. Harding
153136fc5c4STobin C. Harding	foreach my $path (@$in) {
154136fc5c4STobin C. Harding		if (File::Spec->file_name_is_absolute($path)) {
155136fc5c4STobin C. Harding			push @$abs, $path;
156136fc5c4STobin C. Harding		} elsif (index($path,'/') == -1) {
157136fc5c4STobin C. Harding			push @$any, $path;
158136fc5c4STobin C. Harding		} else {
159136fc5c4STobin C. Harding			print 'path error: ' . $path;
160136fc5c4STobin C. Harding		}
161136fc5c4STobin C. Harding	}
162136fc5c4STobin C. Harding}
163136fc5c4STobin C. Harding
164136fc5c4STobin C. Harding# Push command line options to global arrays.
165136fc5c4STobin C. Hardingsub push_to_global
166136fc5c4STobin C. Harding{
167136fc5c4STobin C. Harding	push_in_abs_any(\@dont_walk, \@skip_walk_dirs_abs, \@skip_walk_dirs_any);
168136fc5c4STobin C. Harding	push_in_abs_any(\@dont_parse, \@skip_parse_files_abs, \@skip_parse_files_any);
169136fc5c4STobin C. Harding}
170136fc5c4STobin C. Harding
171136fc5c4STobin C. Hardingsub is_false_positive
172136fc5c4STobin C. Harding{
173136fc5c4STobin C. Harding	my ($match) = @_;
174136fc5c4STobin C. Harding
175136fc5c4STobin C. Harding	if ($match =~ '\b(0x)?(f|F){16}\b' or
176136fc5c4STobin C. Harding	    $match =~ '\b(0x)?0{16}\b') {
177136fc5c4STobin C. Harding		return 1;
178136fc5c4STobin C. Harding	}
179136fc5c4STobin C. Harding
1807e5758f7STobin C. Harding
1817e5758f7STobin C. Harding	if ($match =~ '\bf{10}600000\b' or# vsyscall memory region, we should probably check against a range here.
182136fc5c4STobin C. Harding	    $match =~ '\bf{10}601000\b') {
183136fc5c4STobin C. Harding		return 1;
184136fc5c4STobin C. Harding	}
185136fc5c4STobin C. Harding
186136fc5c4STobin C. Harding	return 0;
187136fc5c4STobin C. Harding}
188136fc5c4STobin C. Harding
189136fc5c4STobin C. Harding# True if argument potentially contains a kernel address.
190136fc5c4STobin C. Hardingsub may_leak_address
191136fc5c4STobin C. Harding{
192136fc5c4STobin C. Harding	my ($line) = @_;
193136fc5c4STobin C. Harding	my $address = '\b(0x)?ffff[[:xdigit:]]{12}\b';
194136fc5c4STobin C. Harding
195136fc5c4STobin C. Harding	# Signal masks.
196136fc5c4STobin C. Harding	if ($line =~ '^SigBlk:' or
197136fc5c4STobin C. Harding	    $line =~ '^SigCgt:') {
198136fc5c4STobin C. Harding		return 0;
199136fc5c4STobin C. Harding	}
200136fc5c4STobin C. Harding
201136fc5c4STobin C. Harding	if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or
202136fc5c4STobin C. Harding	    $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') {
203136fc5c4STobin C. Harding		return 0;
204136fc5c4STobin C. Harding	}
205136fc5c4STobin C. Harding
206136fc5c4STobin C. Harding	while (/($address)/g) {
207136fc5c4STobin C. Harding		if (!is_false_positive($1)) {
208136fc5c4STobin C. Harding			return 1;
209136fc5c4STobin C. Harding		}
210136fc5c4STobin C. Harding	}
211136fc5c4STobin C. Harding
212136fc5c4STobin C. Harding	return 0;
213136fc5c4STobin C. Harding}
214136fc5c4STobin C. Harding
215136fc5c4STobin C. Hardingsub parse_dmesg
216136fc5c4STobin C. Harding{
217136fc5c4STobin C. Harding	open my $cmd, '-|', 'dmesg';
218136fc5c4STobin C. Harding	while (<$cmd>) {
219136fc5c4STobin C. Harding		if (may_leak_address($_)) {
220136fc5c4STobin C. Harding			print 'dmesg: ' . $_;
221136fc5c4STobin C. Harding		}
222136fc5c4STobin C. Harding	}
223136fc5c4STobin C. Harding	close $cmd;
224136fc5c4STobin C. Harding}
225136fc5c4STobin C. Harding
226136fc5c4STobin C. Harding# True if we should skip this path.
227136fc5c4STobin C. Hardingsub skip
228136fc5c4STobin C. Harding{
229136fc5c4STobin C. Harding	my ($path, $paths_abs, $paths_any) = @_;
230136fc5c4STobin C. Harding
231136fc5c4STobin C. Harding	foreach (@$paths_abs) {
232136fc5c4STobin C. Harding		return 1 if (/^$path$/);
233136fc5c4STobin C. Harding	}
234136fc5c4STobin C. Harding
235136fc5c4STobin C. Harding	my($filename, $dirs, $suffix) = fileparse($path);
236136fc5c4STobin C. Harding	foreach (@$paths_any) {
237136fc5c4STobin C. Harding		return 1 if (/^$filename$/);
238136fc5c4STobin C. Harding	}
239136fc5c4STobin C. Harding
240136fc5c4STobin C. Harding	return 0;
241136fc5c4STobin C. Harding}
242136fc5c4STobin C. Harding
243136fc5c4STobin C. Hardingsub skip_parse
244136fc5c4STobin C. Harding{
245136fc5c4STobin C. Harding	my ($path) = @_;
246136fc5c4STobin C. Harding	return skip($path, \@skip_parse_files_abs, \@skip_parse_files_any);
247136fc5c4STobin C. Harding}
248136fc5c4STobin C. Harding
249136fc5c4STobin C. Hardingsub parse_file
250136fc5c4STobin C. Harding{
251136fc5c4STobin C. Harding	my ($file) = @_;
252136fc5c4STobin C. Harding
253136fc5c4STobin C. Harding	if (! -R $file) {
254136fc5c4STobin C. Harding		return;
255136fc5c4STobin C. Harding	}
256136fc5c4STobin C. Harding
257136fc5c4STobin C. Harding	if (skip_parse($file)) {
258136fc5c4STobin C. Harding		dprint "skipping file: $file\n";
259136fc5c4STobin C. Harding		return;
260136fc5c4STobin C. Harding	}
261136fc5c4STobin C. Harding	dprint "parsing: $file\n";
262136fc5c4STobin C. Harding
263136fc5c4STobin C. Harding	open my $fh, "<", $file or return;
264136fc5c4STobin C. Harding	while ( <$fh> ) {
265136fc5c4STobin C. Harding		if (may_leak_address($_)) {
266136fc5c4STobin C. Harding			print $file . ': ' . $_;
267136fc5c4STobin C. Harding		}
268136fc5c4STobin C. Harding	}
269136fc5c4STobin C. Harding	close $fh;
270136fc5c4STobin C. Harding}
271136fc5c4STobin C. Harding
272136fc5c4STobin C. Harding
273136fc5c4STobin C. Harding# True if we should skip walking this directory.
274136fc5c4STobin C. Hardingsub skip_walk
275136fc5c4STobin C. Harding{
276136fc5c4STobin C. Harding	my ($path) = @_;
277136fc5c4STobin C. Harding	return skip($path, \@skip_walk_dirs_abs, \@skip_walk_dirs_any)
278136fc5c4STobin C. Harding}
279136fc5c4STobin C. Harding
280136fc5c4STobin C. Harding# Recursively walk directory tree.
281136fc5c4STobin C. Hardingsub walk
282136fc5c4STobin C. Harding{
283136fc5c4STobin C. Harding	my @dirs = @_;
284136fc5c4STobin C. Harding	my %seen;
285136fc5c4STobin C. Harding
286136fc5c4STobin C. Harding	while (my $pwd = shift @dirs) {
287136fc5c4STobin C. Harding		next if (skip_walk($pwd));
288136fc5c4STobin C. Harding		next if (!opendir(DIR, $pwd));
289136fc5c4STobin C. Harding		my @files = readdir(DIR);
290136fc5c4STobin C. Harding		closedir(DIR);
291136fc5c4STobin C. Harding
292136fc5c4STobin C. Harding		foreach my $file (@files) {
293136fc5c4STobin C. Harding			next if ($file eq '.' or $file eq '..');
294136fc5c4STobin C. Harding
295136fc5c4STobin C. Harding			my $path = "$pwd/$file";
296136fc5c4STobin C. Harding			next if (-l $path);
297136fc5c4STobin C. Harding
298136fc5c4STobin C. Harding			if (-d $path) {
299136fc5c4STobin C. Harding				push @dirs, $path;
300136fc5c4STobin C. Harding			} else {
301136fc5c4STobin C. Harding				parse_file($path);
302136fc5c4STobin C. Harding			}
303136fc5c4STobin C. Harding		}
304136fc5c4STobin C. Harding	}
305136fc5c4STobin C. Harding}
306