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# Use --debug to output path before parsing, this is useful to find files that
11136fc5c4STobin C. Harding# cause the script to choke.
12136fc5c4STobin C. Harding#
13136fc5c4STobin C. Harding# You may like to set kptr_restrict=2 before running script
14136fc5c4STobin C. Harding# (see Documentation/sysctl/kernel.txt).
15136fc5c4STobin C. Harding
16136fc5c4STobin C. Hardinguse warnings;
17136fc5c4STobin C. Hardinguse strict;
18136fc5c4STobin C. Hardinguse POSIX;
19136fc5c4STobin C. Hardinguse File::Basename;
20136fc5c4STobin C. Hardinguse File::Spec;
21136fc5c4STobin C. Hardinguse Cwd 'abs_path';
22136fc5c4STobin C. Hardinguse Term::ANSIColor qw(:constants);
23136fc5c4STobin C. Hardinguse Getopt::Long qw(:config no_auto_abbrev);
24136fc5c4STobin C. Harding
25136fc5c4STobin C. Hardingmy $P = $0;
26136fc5c4STobin C. Hardingmy $V = '0.01';
27136fc5c4STobin C. Harding
28136fc5c4STobin C. Harding# Directories to scan.
29136fc5c4STobin C. Hardingmy @DIRS = ('/proc', '/sys');
30136fc5c4STobin C. Harding
31136fc5c4STobin C. Harding# Command line options.
32136fc5c4STobin C. Hardingmy $help = 0;
33136fc5c4STobin C. Hardingmy $debug = 0;
34136fc5c4STobin C. Harding
35136fc5c4STobin C. Harding# Do not parse these files (absolute path).
36136fc5c4STobin C. Hardingmy @skip_parse_files_abs = ('/proc/kmsg',
37136fc5c4STobin C. Harding			    '/proc/kcore',
38136fc5c4STobin C. Harding			    '/proc/fs/ext4/sdb1/mb_groups',
39136fc5c4STobin C. Harding			    '/proc/1/fd/3',
40136fc5c4STobin C. Harding			    '/sys/kernel/debug/tracing/trace_pipe',
41136fc5c4STobin C. Harding			    '/sys/kernel/security/apparmor/revision');
42136fc5c4STobin C. Harding
43a284733eSTobin C. Harding# Do not parse these files under any subdirectory.
44136fc5c4STobin C. Hardingmy @skip_parse_files_any = ('0',
45136fc5c4STobin C. Harding			    '1',
46136fc5c4STobin C. Harding			    '2',
47136fc5c4STobin C. Harding			    'pagemap',
48136fc5c4STobin C. Harding			    'events',
49136fc5c4STobin C. Harding			    'access',
50136fc5c4STobin C. Harding			    'registers',
51136fc5c4STobin C. Harding			    'snapshot_raw',
52136fc5c4STobin C. Harding			    'trace_pipe_raw',
53136fc5c4STobin C. Harding			    'ptmx',
54136fc5c4STobin C. Harding			    'trace_pipe');
55136fc5c4STobin C. Harding
56136fc5c4STobin C. Harding# Do not walk these directories (absolute path).
57136fc5c4STobin C. Hardingmy @skip_walk_dirs_abs = ();
58136fc5c4STobin C. Harding
59136fc5c4STobin C. Harding# Do not walk these directories under any subdirectory.
60136fc5c4STobin C. Hardingmy @skip_walk_dirs_any = ('self',
61136fc5c4STobin C. Harding			  'thread-self',
62136fc5c4STobin C. Harding			  'cwd',
63136fc5c4STobin C. Harding			  'fd',
64136fc5c4STobin C. Harding			  'stderr',
65136fc5c4STobin C. Harding			  'stdin',
66136fc5c4STobin C. Harding			  'stdout');
67136fc5c4STobin C. Harding
68136fc5c4STobin C. Hardingsub help
69136fc5c4STobin C. Harding{
70136fc5c4STobin C. Harding	my ($exitcode) = @_;
71136fc5c4STobin C. Harding
72136fc5c4STobin C. Harding	print << "EOM";
73136fc5c4STobin C. HardingUsage: $P [OPTIONS]
74136fc5c4STobin C. HardingVersion: $V
75136fc5c4STobin C. Harding
76136fc5c4STobin C. HardingOptions:
77136fc5c4STobin C. Harding
78136fc5c4STobin C. Harding	-d, --debug                Display debugging output.
79136fc5c4STobin C. Harding	-h, --help, --version      Display this help and exit.
80136fc5c4STobin C. Harding
81136fc5c4STobin C. HardingScans the running (64 bit) kernel for potential leaking addresses.
82136fc5c4STobin C. Harding
83136fc5c4STobin C. HardingEOM
84136fc5c4STobin C. Harding	exit($exitcode);
85136fc5c4STobin C. Harding}
86136fc5c4STobin C. Harding
87136fc5c4STobin C. HardingGetOptions(
88136fc5c4STobin C. Harding	'd|debug'		=> \$debug,
89136fc5c4STobin C. Harding	'h|help'		=> \$help,
90136fc5c4STobin C. Harding	'version'		=> \$help
91136fc5c4STobin C. Harding) or help(1);
92136fc5c4STobin C. Harding
93136fc5c4STobin C. Hardinghelp(0) if ($help);
94136fc5c4STobin C. Harding
95136fc5c4STobin C. Hardingparse_dmesg();
96136fc5c4STobin C. Hardingwalk(@DIRS);
97136fc5c4STobin C. Harding
98136fc5c4STobin C. Hardingexit 0;
99136fc5c4STobin C. Harding
100136fc5c4STobin C. Hardingsub dprint
101136fc5c4STobin C. Harding{
102136fc5c4STobin C. Harding	printf(STDERR @_) if $debug;
103136fc5c4STobin C. Harding}
104136fc5c4STobin C. Harding
105136fc5c4STobin C. Hardingsub is_false_positive
106136fc5c4STobin C. Harding{
107136fc5c4STobin C. Harding	my ($match) = @_;
108136fc5c4STobin C. Harding
109136fc5c4STobin C. Harding	if ($match =~ '\b(0x)?(f|F){16}\b' or
110136fc5c4STobin C. Harding	    $match =~ '\b(0x)?0{16}\b') {
111136fc5c4STobin C. Harding		return 1;
112136fc5c4STobin C. Harding	}
113136fc5c4STobin C. Harding
1147e5758f7STobin C. Harding
1157e5758f7STobin C. Harding	if ($match =~ '\bf{10}600000\b' or# vsyscall memory region, we should probably check against a range here.
116136fc5c4STobin C. Harding	    $match =~ '\bf{10}601000\b') {
117136fc5c4STobin C. Harding		return 1;
118136fc5c4STobin C. Harding	}
119136fc5c4STobin C. Harding
120136fc5c4STobin C. Harding	return 0;
121136fc5c4STobin C. Harding}
122136fc5c4STobin C. Harding
123136fc5c4STobin C. Harding# True if argument potentially contains a kernel address.
124136fc5c4STobin C. Hardingsub may_leak_address
125136fc5c4STobin C. Harding{
126136fc5c4STobin C. Harding	my ($line) = @_;
127136fc5c4STobin C. Harding	my $address = '\b(0x)?ffff[[:xdigit:]]{12}\b';
128136fc5c4STobin C. Harding
129136fc5c4STobin C. Harding	# Signal masks.
130136fc5c4STobin C. Harding	if ($line =~ '^SigBlk:' or
131136fc5c4STobin C. Harding	    $line =~ '^SigCgt:') {
132136fc5c4STobin C. Harding		return 0;
133136fc5c4STobin C. Harding	}
134136fc5c4STobin C. Harding
135136fc5c4STobin C. Harding	if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or
136136fc5c4STobin C. Harding	    $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') {
137136fc5c4STobin C. Harding		return 0;
138136fc5c4STobin C. Harding	}
139136fc5c4STobin C. Harding
140136fc5c4STobin C. Harding	while (/($address)/g) {
141136fc5c4STobin C. Harding		if (!is_false_positive($1)) {
142136fc5c4STobin C. Harding			return 1;
143136fc5c4STobin C. Harding		}
144136fc5c4STobin C. Harding	}
145136fc5c4STobin C. Harding
146136fc5c4STobin C. Harding	return 0;
147136fc5c4STobin C. Harding}
148136fc5c4STobin C. Harding
149136fc5c4STobin C. Hardingsub parse_dmesg
150136fc5c4STobin C. Harding{
151136fc5c4STobin C. Harding	open my $cmd, '-|', 'dmesg';
152136fc5c4STobin C. Harding	while (<$cmd>) {
153136fc5c4STobin C. Harding		if (may_leak_address($_)) {
154136fc5c4STobin C. Harding			print 'dmesg: ' . $_;
155136fc5c4STobin C. Harding		}
156136fc5c4STobin C. Harding	}
157136fc5c4STobin C. Harding	close $cmd;
158136fc5c4STobin C. Harding}
159136fc5c4STobin C. Harding
160136fc5c4STobin C. Harding# True if we should skip this path.
161136fc5c4STobin C. Hardingsub skip
162136fc5c4STobin C. Harding{
163136fc5c4STobin C. Harding	my ($path, $paths_abs, $paths_any) = @_;
164136fc5c4STobin C. Harding
165136fc5c4STobin C. Harding	foreach (@$paths_abs) {
166136fc5c4STobin C. Harding		return 1 if (/^$path$/);
167136fc5c4STobin C. Harding	}
168136fc5c4STobin C. Harding
169136fc5c4STobin C. Harding	my($filename, $dirs, $suffix) = fileparse($path);
170136fc5c4STobin C. Harding	foreach (@$paths_any) {
171136fc5c4STobin C. Harding		return 1 if (/^$filename$/);
172136fc5c4STobin C. Harding	}
173136fc5c4STobin C. Harding
174136fc5c4STobin C. Harding	return 0;
175136fc5c4STobin C. Harding}
176136fc5c4STobin C. Harding
177136fc5c4STobin C. Hardingsub skip_parse
178136fc5c4STobin C. Harding{
179136fc5c4STobin C. Harding	my ($path) = @_;
180136fc5c4STobin C. Harding	return skip($path, \@skip_parse_files_abs, \@skip_parse_files_any);
181136fc5c4STobin C. Harding}
182136fc5c4STobin C. Harding
183136fc5c4STobin C. Hardingsub parse_file
184136fc5c4STobin C. Harding{
185136fc5c4STobin C. Harding	my ($file) = @_;
186136fc5c4STobin C. Harding
187136fc5c4STobin C. Harding	if (! -R $file) {
188136fc5c4STobin C. Harding		return;
189136fc5c4STobin C. Harding	}
190136fc5c4STobin C. Harding
191136fc5c4STobin C. Harding	if (skip_parse($file)) {
192136fc5c4STobin C. Harding		dprint "skipping file: $file\n";
193136fc5c4STobin C. Harding		return;
194136fc5c4STobin C. Harding	}
195136fc5c4STobin C. Harding	dprint "parsing: $file\n";
196136fc5c4STobin C. Harding
197136fc5c4STobin C. Harding	open my $fh, "<", $file or return;
198136fc5c4STobin C. Harding	while ( <$fh> ) {
199136fc5c4STobin C. Harding		if (may_leak_address($_)) {
200136fc5c4STobin C. Harding			print $file . ': ' . $_;
201136fc5c4STobin C. Harding		}
202136fc5c4STobin C. Harding	}
203136fc5c4STobin C. Harding	close $fh;
204136fc5c4STobin C. Harding}
205136fc5c4STobin C. Harding
206136fc5c4STobin C. Harding
207136fc5c4STobin C. Harding# True if we should skip walking this directory.
208136fc5c4STobin C. Hardingsub skip_walk
209136fc5c4STobin C. Harding{
210136fc5c4STobin C. Harding	my ($path) = @_;
211136fc5c4STobin C. Harding	return skip($path, \@skip_walk_dirs_abs, \@skip_walk_dirs_any)
212136fc5c4STobin C. Harding}
213136fc5c4STobin C. Harding
214136fc5c4STobin C. Harding# Recursively walk directory tree.
215136fc5c4STobin C. Hardingsub walk
216136fc5c4STobin C. Harding{
217136fc5c4STobin C. Harding	my @dirs = @_;
218136fc5c4STobin C. Harding
219136fc5c4STobin C. Harding	while (my $pwd = shift @dirs) {
220136fc5c4STobin C. Harding		next if (skip_walk($pwd));
221136fc5c4STobin C. Harding		next if (!opendir(DIR, $pwd));
222136fc5c4STobin C. Harding		my @files = readdir(DIR);
223136fc5c4STobin C. Harding		closedir(DIR);
224136fc5c4STobin C. Harding
225136fc5c4STobin C. Harding		foreach my $file (@files) {
226136fc5c4STobin C. Harding			next if ($file eq '.' or $file eq '..');
227136fc5c4STobin C. Harding
228136fc5c4STobin C. Harding			my $path = "$pwd/$file";
229136fc5c4STobin C. Harding			next if (-l $path);
230136fc5c4STobin C. Harding
231136fc5c4STobin C. Harding			if (-d $path) {
232136fc5c4STobin C. Harding				push @dirs, $path;
233136fc5c4STobin C. Harding			} else {
234136fc5c4STobin C. Harding				parse_file($path);
235136fc5c4STobin C. Harding			}
236136fc5c4STobin C. Harding		}
237136fc5c4STobin C. Harding	}
238136fc5c4STobin C. Harding}
239