Commit | Line | Data |
---|---|---|
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 | ||
16 | use warnings; | |
17 | use strict; | |
18 | use POSIX; | |
19 | use File::Basename; | |
20 | use File::Spec; | |
21 | use Cwd 'abs_path'; | |
22 | use Term::ANSIColor qw(:constants); | |
23 | use Getopt::Long qw(:config no_auto_abbrev); | |
24 | ||
25 | my $P = $0; | |
26 | my $V = '0.01'; | |
27 | ||
28 | # Directories to scan. | |
29 | my @DIRS = ('/proc', '/sys'); | |
30 | ||
31 | # Command line options. | |
32 | my $help = 0; | |
33 | my $debug = 0; | |
136fc5c4 TH |
34 | |
35 | # Do not parse these files (absolute path). | |
36 | my @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 |
46 | my @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). | |
59 | my @skip_walk_dirs_abs = (); | |
60 | ||
61 | # Do not walk these directories under any subdirectory. | |
62 | my @skip_walk_dirs_any = ('self', | |
63 | 'thread-self', | |
64 | 'cwd', | |
65 | 'fd', | |
1c1e3be0 | 66 | 'usbmon', |
136fc5c4 TH |
67 | 'stderr', |
68 | 'stdin', | |
69 | 'stdout'); | |
70 | ||
71 | sub help | |
72 | { | |
73 | my ($exitcode) = @_; | |
74 | ||
75 | print << "EOM"; | |
76 | Usage: $P [OPTIONS] | |
77 | Version: $V | |
78 | ||
79 | Options: | |
80 | ||
136fc5c4 TH |
81 | -d, --debug Display debugging output. |
82 | -h, --help, --version Display this help and exit. | |
83 | ||
136fc5c4 TH |
84 | Scans the running (64 bit) kernel for potential leaking addresses. |
85 | ||
86 | EOM | |
87 | exit($exitcode); | |
88 | } | |
89 | ||
90 | GetOptions( | |
136fc5c4 TH |
91 | 'd|debug' => \$debug, |
92 | 'h|help' => \$help, | |
93 | 'version' => \$help | |
94 | ) or help(1); | |
95 | ||
96 | help(0) if ($help); | |
97 | ||
136fc5c4 TH |
98 | parse_dmesg(); |
99 | walk(@DIRS); | |
100 | ||
101 | exit 0; | |
102 | ||
136fc5c4 TH |
103 | sub dprint |
104 | { | |
105 | printf(STDERR @_) if $debug; | |
106 | } | |
107 | ||
136fc5c4 TH |
108 | sub 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. | |
127 | sub 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 | ||
152 | sub 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. | |
164 | sub 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 | ||
180 | sub skip_parse | |
181 | { | |
182 | my ($path) = @_; | |
183 | return skip($path, \@skip_parse_files_abs, \@skip_parse_files_any); | |
184 | } | |
185 | ||
186 | sub 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. | |
211 | sub 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. | |
218 | sub 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 | } |