xref: /OK3568_Linux_fs/kernel/scripts/leaking_addresses.pl (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun#!/usr/bin/env perl
2*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only
3*4882a593Smuzhiyun#
4*4882a593Smuzhiyun# (c) 2017 Tobin C. Harding <me@tobin.cc>
5*4882a593Smuzhiyun#
6*4882a593Smuzhiyun# leaking_addresses.pl: Scan the kernel for potential leaking addresses.
7*4882a593Smuzhiyun#  - Scans dmesg output.
8*4882a593Smuzhiyun#  - Walks directory tree and parses each file (for each directory in @DIRS).
9*4882a593Smuzhiyun#
10*4882a593Smuzhiyun# Use --debug to output path before parsing, this is useful to find files that
11*4882a593Smuzhiyun# cause the script to choke.
12*4882a593Smuzhiyun
13*4882a593Smuzhiyun#
14*4882a593Smuzhiyun# When the system is idle it is likely that most files under /proc/PID will be
15*4882a593Smuzhiyun# identical for various processes.  Scanning _all_ the PIDs under /proc is
16*4882a593Smuzhiyun# unnecessary and implies that we are thoroughly scanning /proc.  This is _not_
17*4882a593Smuzhiyun# the case because there may be ways userspace can trigger creation of /proc
18*4882a593Smuzhiyun# files that leak addresses but were not present during a scan.  For these two
19*4882a593Smuzhiyun# reasons we exclude all PID directories under /proc except '1/'
20*4882a593Smuzhiyun
21*4882a593Smuzhiyunuse warnings;
22*4882a593Smuzhiyunuse strict;
23*4882a593Smuzhiyunuse POSIX;
24*4882a593Smuzhiyunuse File::Basename;
25*4882a593Smuzhiyunuse File::Spec;
26*4882a593Smuzhiyunuse Cwd 'abs_path';
27*4882a593Smuzhiyunuse Term::ANSIColor qw(:constants);
28*4882a593Smuzhiyunuse Getopt::Long qw(:config no_auto_abbrev);
29*4882a593Smuzhiyunuse Config;
30*4882a593Smuzhiyunuse bigint qw/hex/;
31*4882a593Smuzhiyunuse feature 'state';
32*4882a593Smuzhiyun
33*4882a593Smuzhiyunmy $P = $0;
34*4882a593Smuzhiyun
35*4882a593Smuzhiyun# Directories to scan.
36*4882a593Smuzhiyunmy @DIRS = ('/proc', '/sys');
37*4882a593Smuzhiyun
38*4882a593Smuzhiyun# Timer for parsing each file, in seconds.
39*4882a593Smuzhiyunmy $TIMEOUT = 10;
40*4882a593Smuzhiyun
41*4882a593Smuzhiyun# Kernel addresses vary by architecture.  We can only auto-detect the following
42*4882a593Smuzhiyun# architectures (using `uname -m`).  (flag --32-bit overrides auto-detection.)
43*4882a593Smuzhiyunmy @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64', 'x86');
44*4882a593Smuzhiyun
45*4882a593Smuzhiyun# Command line options.
46*4882a593Smuzhiyunmy $help = 0;
47*4882a593Smuzhiyunmy $debug = 0;
48*4882a593Smuzhiyunmy $raw = 0;
49*4882a593Smuzhiyunmy $output_raw = "";	# Write raw results to file.
50*4882a593Smuzhiyunmy $input_raw = "";	# Read raw results from file instead of scanning.
51*4882a593Smuzhiyunmy $suppress_dmesg = 0;		# Don't show dmesg in output.
52*4882a593Smuzhiyunmy $squash_by_path = 0;		# Summary report grouped by absolute path.
53*4882a593Smuzhiyunmy $squash_by_filename = 0;	# Summary report grouped by filename.
54*4882a593Smuzhiyunmy $kernel_config_file = "";	# Kernel configuration file.
55*4882a593Smuzhiyunmy $opt_32bit = 0;		# Scan 32-bit kernel.
56*4882a593Smuzhiyunmy $page_offset_32bit = 0;	# Page offset for 32-bit kernel.
57*4882a593Smuzhiyun
58*4882a593Smuzhiyun# Skip these absolute paths.
59*4882a593Smuzhiyunmy @skip_abs = (
60*4882a593Smuzhiyun	'/proc/kmsg',
61*4882a593Smuzhiyun	'/proc/device-tree',
62*4882a593Smuzhiyun	'/proc/1/syscall',
63*4882a593Smuzhiyun	'/sys/firmware/devicetree',
64*4882a593Smuzhiyun	'/sys/kernel/debug/tracing/trace_pipe',
65*4882a593Smuzhiyun	'/sys/kernel/security/apparmor/revision');
66*4882a593Smuzhiyun
67*4882a593Smuzhiyun# Skip these under any subdirectory.
68*4882a593Smuzhiyunmy @skip_any = (
69*4882a593Smuzhiyun	'pagemap',
70*4882a593Smuzhiyun	'events',
71*4882a593Smuzhiyun	'access',
72*4882a593Smuzhiyun	'registers',
73*4882a593Smuzhiyun	'snapshot_raw',
74*4882a593Smuzhiyun	'trace_pipe_raw',
75*4882a593Smuzhiyun	'ptmx',
76*4882a593Smuzhiyun	'trace_pipe',
77*4882a593Smuzhiyun	'fd',
78*4882a593Smuzhiyun	'usbmon');
79*4882a593Smuzhiyun
80*4882a593Smuzhiyunsub help
81*4882a593Smuzhiyun{
82*4882a593Smuzhiyun	my ($exitcode) = @_;
83*4882a593Smuzhiyun
84*4882a593Smuzhiyun	print << "EOM";
85*4882a593Smuzhiyun
86*4882a593SmuzhiyunUsage: $P [OPTIONS]
87*4882a593Smuzhiyun
88*4882a593SmuzhiyunOptions:
89*4882a593Smuzhiyun
90*4882a593Smuzhiyun	-o, --output-raw=<file>		Save results for future processing.
91*4882a593Smuzhiyun	-i, --input-raw=<file>		Read results from file instead of scanning.
92*4882a593Smuzhiyun	      --raw			Show raw results (default).
93*4882a593Smuzhiyun	      --suppress-dmesg		Do not show dmesg results.
94*4882a593Smuzhiyun	      --squash-by-path		Show one result per unique path.
95*4882a593Smuzhiyun	      --squash-by-filename	Show one result per unique filename.
96*4882a593Smuzhiyun	--kernel-config-file=<file>     Kernel configuration file (e.g /boot/config)
97*4882a593Smuzhiyun	--32-bit			Scan 32-bit kernel.
98*4882a593Smuzhiyun	--page-offset-32-bit=o		Page offset (for 32-bit kernel 0xABCD1234).
99*4882a593Smuzhiyun	-d, --debug			Display debugging output.
100*4882a593Smuzhiyun	-h, --help			Display this help and exit.
101*4882a593Smuzhiyun
102*4882a593SmuzhiyunScans the running kernel for potential leaking addresses.
103*4882a593Smuzhiyun
104*4882a593SmuzhiyunEOM
105*4882a593Smuzhiyun	exit($exitcode);
106*4882a593Smuzhiyun}
107*4882a593Smuzhiyun
108*4882a593SmuzhiyunGetOptions(
109*4882a593Smuzhiyun	'd|debug'		=> \$debug,
110*4882a593Smuzhiyun	'h|help'		=> \$help,
111*4882a593Smuzhiyun	'o|output-raw=s'        => \$output_raw,
112*4882a593Smuzhiyun	'i|input-raw=s'         => \$input_raw,
113*4882a593Smuzhiyun	'suppress-dmesg'        => \$suppress_dmesg,
114*4882a593Smuzhiyun	'squash-by-path'        => \$squash_by_path,
115*4882a593Smuzhiyun	'squash-by-filename'    => \$squash_by_filename,
116*4882a593Smuzhiyun	'raw'                   => \$raw,
117*4882a593Smuzhiyun	'kernel-config-file=s'	=> \$kernel_config_file,
118*4882a593Smuzhiyun	'32-bit'		=> \$opt_32bit,
119*4882a593Smuzhiyun	'page-offset-32-bit=o'	=> \$page_offset_32bit,
120*4882a593Smuzhiyun) or help(1);
121*4882a593Smuzhiyun
122*4882a593Smuzhiyunhelp(0) if ($help);
123*4882a593Smuzhiyun
124*4882a593Smuzhiyunif ($input_raw) {
125*4882a593Smuzhiyun	format_output($input_raw);
126*4882a593Smuzhiyun	exit(0);
127*4882a593Smuzhiyun}
128*4882a593Smuzhiyun
129*4882a593Smuzhiyunif (!$input_raw and ($squash_by_path or $squash_by_filename)) {
130*4882a593Smuzhiyun	printf "\nSummary reporting only available with --input-raw=<file>\n";
131*4882a593Smuzhiyun	printf "(First run scan with --output-raw=<file>.)\n";
132*4882a593Smuzhiyun	exit(128);
133*4882a593Smuzhiyun}
134*4882a593Smuzhiyun
135*4882a593Smuzhiyunif (!(is_supported_architecture() or $opt_32bit or $page_offset_32bit)) {
136*4882a593Smuzhiyun	printf "\nScript does not support your architecture, sorry.\n";
137*4882a593Smuzhiyun	printf "\nCurrently we support: \n\n";
138*4882a593Smuzhiyun	foreach(@SUPPORTED_ARCHITECTURES) {
139*4882a593Smuzhiyun		printf "\t%s\n", $_;
140*4882a593Smuzhiyun	}
141*4882a593Smuzhiyun	printf("\n");
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun	printf("If you are running a 32-bit architecture you may use:\n");
144*4882a593Smuzhiyun	printf("\n\t--32-bit or --page-offset-32-bit=<page offset>\n\n");
145*4882a593Smuzhiyun
146*4882a593Smuzhiyun	my $archname = `uname -m`;
147*4882a593Smuzhiyun	printf("Machine hardware name (`uname -m`): %s\n", $archname);
148*4882a593Smuzhiyun
149*4882a593Smuzhiyun	exit(129);
150*4882a593Smuzhiyun}
151*4882a593Smuzhiyun
152*4882a593Smuzhiyunif ($output_raw) {
153*4882a593Smuzhiyun	open my $fh, '>', $output_raw or die "$0: $output_raw: $!\n";
154*4882a593Smuzhiyun	select $fh;
155*4882a593Smuzhiyun}
156*4882a593Smuzhiyun
157*4882a593Smuzhiyunparse_dmesg();
158*4882a593Smuzhiyunwalk(@DIRS);
159*4882a593Smuzhiyun
160*4882a593Smuzhiyunexit 0;
161*4882a593Smuzhiyun
162*4882a593Smuzhiyunsub dprint
163*4882a593Smuzhiyun{
164*4882a593Smuzhiyun	printf(STDERR @_) if $debug;
165*4882a593Smuzhiyun}
166*4882a593Smuzhiyun
167*4882a593Smuzhiyunsub is_supported_architecture
168*4882a593Smuzhiyun{
169*4882a593Smuzhiyun	return (is_x86_64() or is_ppc64() or is_ix86_32());
170*4882a593Smuzhiyun}
171*4882a593Smuzhiyun
172*4882a593Smuzhiyunsub is_32bit
173*4882a593Smuzhiyun{
174*4882a593Smuzhiyun	# Allow --32-bit or --page-offset-32-bit to override
175*4882a593Smuzhiyun	if ($opt_32bit or $page_offset_32bit) {
176*4882a593Smuzhiyun		return 1;
177*4882a593Smuzhiyun	}
178*4882a593Smuzhiyun
179*4882a593Smuzhiyun	return is_ix86_32();
180*4882a593Smuzhiyun}
181*4882a593Smuzhiyun
182*4882a593Smuzhiyunsub is_ix86_32
183*4882a593Smuzhiyun{
184*4882a593Smuzhiyun       state $arch = `uname -m`;
185*4882a593Smuzhiyun
186*4882a593Smuzhiyun       chomp $arch;
187*4882a593Smuzhiyun       if ($arch =~ m/i[3456]86/) {
188*4882a593Smuzhiyun               return 1;
189*4882a593Smuzhiyun       }
190*4882a593Smuzhiyun       return 0;
191*4882a593Smuzhiyun}
192*4882a593Smuzhiyun
193*4882a593Smuzhiyunsub is_arch
194*4882a593Smuzhiyun{
195*4882a593Smuzhiyun       my ($desc) = @_;
196*4882a593Smuzhiyun       my $arch = `uname -m`;
197*4882a593Smuzhiyun
198*4882a593Smuzhiyun       chomp $arch;
199*4882a593Smuzhiyun       if ($arch eq $desc) {
200*4882a593Smuzhiyun               return 1;
201*4882a593Smuzhiyun       }
202*4882a593Smuzhiyun       return 0;
203*4882a593Smuzhiyun}
204*4882a593Smuzhiyun
205*4882a593Smuzhiyunsub is_x86_64
206*4882a593Smuzhiyun{
207*4882a593Smuzhiyun	state $is = is_arch('x86_64');
208*4882a593Smuzhiyun	return $is;
209*4882a593Smuzhiyun}
210*4882a593Smuzhiyun
211*4882a593Smuzhiyunsub is_ppc64
212*4882a593Smuzhiyun{
213*4882a593Smuzhiyun	state $is = is_arch('ppc64');
214*4882a593Smuzhiyun	return $is;
215*4882a593Smuzhiyun}
216*4882a593Smuzhiyun
217*4882a593Smuzhiyun# Gets config option value from kernel config file.
218*4882a593Smuzhiyun# Returns "" on error or if config option not found.
219*4882a593Smuzhiyunsub get_kernel_config_option
220*4882a593Smuzhiyun{
221*4882a593Smuzhiyun	my ($option) = @_;
222*4882a593Smuzhiyun	my $value = "";
223*4882a593Smuzhiyun	my $tmp_file = "";
224*4882a593Smuzhiyun	my @config_files;
225*4882a593Smuzhiyun
226*4882a593Smuzhiyun	# Allow --kernel-config-file to override.
227*4882a593Smuzhiyun	if ($kernel_config_file ne "") {
228*4882a593Smuzhiyun		@config_files = ($kernel_config_file);
229*4882a593Smuzhiyun	} elsif (-R "/proc/config.gz") {
230*4882a593Smuzhiyun		my $tmp_file = "/tmp/tmpkconf";
231*4882a593Smuzhiyun
232*4882a593Smuzhiyun		if (system("gunzip < /proc/config.gz > $tmp_file")) {
233*4882a593Smuzhiyun			dprint("system(gunzip < /proc/config.gz) failed\n");
234*4882a593Smuzhiyun			return "";
235*4882a593Smuzhiyun		} else {
236*4882a593Smuzhiyun			@config_files = ($tmp_file);
237*4882a593Smuzhiyun		}
238*4882a593Smuzhiyun	} else {
239*4882a593Smuzhiyun		my $file = '/boot/config-' . `uname -r`;
240*4882a593Smuzhiyun		chomp $file;
241*4882a593Smuzhiyun		@config_files = ($file, '/boot/config');
242*4882a593Smuzhiyun	}
243*4882a593Smuzhiyun
244*4882a593Smuzhiyun	foreach my $file (@config_files) {
245*4882a593Smuzhiyun		dprint("parsing config file: $file\n");
246*4882a593Smuzhiyun		$value = option_from_file($option, $file);
247*4882a593Smuzhiyun		if ($value ne "") {
248*4882a593Smuzhiyun			last;
249*4882a593Smuzhiyun		}
250*4882a593Smuzhiyun	}
251*4882a593Smuzhiyun
252*4882a593Smuzhiyun	if ($tmp_file ne "") {
253*4882a593Smuzhiyun		system("rm -f $tmp_file");
254*4882a593Smuzhiyun	}
255*4882a593Smuzhiyun
256*4882a593Smuzhiyun	return $value;
257*4882a593Smuzhiyun}
258*4882a593Smuzhiyun
259*4882a593Smuzhiyun# Parses $file and returns kernel configuration option value.
260*4882a593Smuzhiyunsub option_from_file
261*4882a593Smuzhiyun{
262*4882a593Smuzhiyun	my ($option, $file) = @_;
263*4882a593Smuzhiyun	my $str = "";
264*4882a593Smuzhiyun	my $val = "";
265*4882a593Smuzhiyun
266*4882a593Smuzhiyun	open(my $fh, "<", $file) or return "";
267*4882a593Smuzhiyun	while (my $line = <$fh> ) {
268*4882a593Smuzhiyun		if ($line =~ /^$option/) {
269*4882a593Smuzhiyun			($str, $val) = split /=/, $line;
270*4882a593Smuzhiyun			chomp $val;
271*4882a593Smuzhiyun			last;
272*4882a593Smuzhiyun		}
273*4882a593Smuzhiyun	}
274*4882a593Smuzhiyun
275*4882a593Smuzhiyun	close $fh;
276*4882a593Smuzhiyun	return $val;
277*4882a593Smuzhiyun}
278*4882a593Smuzhiyun
279*4882a593Smuzhiyunsub is_false_positive
280*4882a593Smuzhiyun{
281*4882a593Smuzhiyun	my ($match) = @_;
282*4882a593Smuzhiyun
283*4882a593Smuzhiyun	if (is_32bit()) {
284*4882a593Smuzhiyun		return is_false_positive_32bit($match);
285*4882a593Smuzhiyun	}
286*4882a593Smuzhiyun
287*4882a593Smuzhiyun	# 64 bit false positives.
288*4882a593Smuzhiyun
289*4882a593Smuzhiyun	if ($match =~ '\b(0x)?(f|F){16}\b' or
290*4882a593Smuzhiyun	    $match =~ '\b(0x)?0{16}\b') {
291*4882a593Smuzhiyun		return 1;
292*4882a593Smuzhiyun	}
293*4882a593Smuzhiyun
294*4882a593Smuzhiyun	if (is_x86_64() and is_in_vsyscall_memory_region($match)) {
295*4882a593Smuzhiyun		return 1;
296*4882a593Smuzhiyun	}
297*4882a593Smuzhiyun
298*4882a593Smuzhiyun	return 0;
299*4882a593Smuzhiyun}
300*4882a593Smuzhiyun
301*4882a593Smuzhiyunsub is_false_positive_32bit
302*4882a593Smuzhiyun{
303*4882a593Smuzhiyun       my ($match) = @_;
304*4882a593Smuzhiyun       state $page_offset = get_page_offset();
305*4882a593Smuzhiyun
306*4882a593Smuzhiyun       if ($match =~ '\b(0x)?(f|F){8}\b') {
307*4882a593Smuzhiyun               return 1;
308*4882a593Smuzhiyun       }
309*4882a593Smuzhiyun
310*4882a593Smuzhiyun       if (hex($match) < $page_offset) {
311*4882a593Smuzhiyun               return 1;
312*4882a593Smuzhiyun       }
313*4882a593Smuzhiyun
314*4882a593Smuzhiyun       return 0;
315*4882a593Smuzhiyun}
316*4882a593Smuzhiyun
317*4882a593Smuzhiyun# returns integer value
318*4882a593Smuzhiyunsub get_page_offset
319*4882a593Smuzhiyun{
320*4882a593Smuzhiyun       my $page_offset;
321*4882a593Smuzhiyun       my $default_offset = 0xc0000000;
322*4882a593Smuzhiyun
323*4882a593Smuzhiyun       # Allow --page-offset-32bit to override.
324*4882a593Smuzhiyun       if ($page_offset_32bit != 0) {
325*4882a593Smuzhiyun               return $page_offset_32bit;
326*4882a593Smuzhiyun       }
327*4882a593Smuzhiyun
328*4882a593Smuzhiyun       $page_offset = get_kernel_config_option('CONFIG_PAGE_OFFSET');
329*4882a593Smuzhiyun       if (!$page_offset) {
330*4882a593Smuzhiyun	       return $default_offset;
331*4882a593Smuzhiyun       }
332*4882a593Smuzhiyun       return $page_offset;
333*4882a593Smuzhiyun}
334*4882a593Smuzhiyun
335*4882a593Smuzhiyunsub is_in_vsyscall_memory_region
336*4882a593Smuzhiyun{
337*4882a593Smuzhiyun	my ($match) = @_;
338*4882a593Smuzhiyun
339*4882a593Smuzhiyun	my $hex = hex($match);
340*4882a593Smuzhiyun	my $region_min = hex("0xffffffffff600000");
341*4882a593Smuzhiyun	my $region_max = hex("0xffffffffff601000");
342*4882a593Smuzhiyun
343*4882a593Smuzhiyun	return ($hex >= $region_min and $hex <= $region_max);
344*4882a593Smuzhiyun}
345*4882a593Smuzhiyun
346*4882a593Smuzhiyun# True if argument potentially contains a kernel address.
347*4882a593Smuzhiyunsub may_leak_address
348*4882a593Smuzhiyun{
349*4882a593Smuzhiyun	my ($line) = @_;
350*4882a593Smuzhiyun	my $address_re;
351*4882a593Smuzhiyun
352*4882a593Smuzhiyun	# Signal masks.
353*4882a593Smuzhiyun	if ($line =~ '^SigBlk:' or
354*4882a593Smuzhiyun	    $line =~ '^SigIgn:' or
355*4882a593Smuzhiyun	    $line =~ '^SigCgt:') {
356*4882a593Smuzhiyun		return 0;
357*4882a593Smuzhiyun	}
358*4882a593Smuzhiyun
359*4882a593Smuzhiyun	if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or
360*4882a593Smuzhiyun	    $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') {
361*4882a593Smuzhiyun		return 0;
362*4882a593Smuzhiyun	}
363*4882a593Smuzhiyun
364*4882a593Smuzhiyun	$address_re = get_address_re();
365*4882a593Smuzhiyun	while ($line =~ /($address_re)/g) {
366*4882a593Smuzhiyun		if (!is_false_positive($1)) {
367*4882a593Smuzhiyun			return 1;
368*4882a593Smuzhiyun		}
369*4882a593Smuzhiyun	}
370*4882a593Smuzhiyun
371*4882a593Smuzhiyun	return 0;
372*4882a593Smuzhiyun}
373*4882a593Smuzhiyun
374*4882a593Smuzhiyunsub get_address_re
375*4882a593Smuzhiyun{
376*4882a593Smuzhiyun	if (is_ppc64()) {
377*4882a593Smuzhiyun		return '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b';
378*4882a593Smuzhiyun	} elsif (is_32bit()) {
379*4882a593Smuzhiyun		return '\b(0x)?[[:xdigit:]]{8}\b';
380*4882a593Smuzhiyun	}
381*4882a593Smuzhiyun
382*4882a593Smuzhiyun	return get_x86_64_re();
383*4882a593Smuzhiyun}
384*4882a593Smuzhiyun
385*4882a593Smuzhiyunsub get_x86_64_re
386*4882a593Smuzhiyun{
387*4882a593Smuzhiyun	# We handle page table levels but only if explicitly configured using
388*4882a593Smuzhiyun	# CONFIG_PGTABLE_LEVELS.  If config file parsing fails or config option
389*4882a593Smuzhiyun	# is not found we default to using address regular expression suitable
390*4882a593Smuzhiyun	# for 4 page table levels.
391*4882a593Smuzhiyun	state $ptl = get_kernel_config_option('CONFIG_PGTABLE_LEVELS');
392*4882a593Smuzhiyun
393*4882a593Smuzhiyun	if ($ptl == 5) {
394*4882a593Smuzhiyun		return '\b(0x)?ff[[:xdigit:]]{14}\b';
395*4882a593Smuzhiyun	}
396*4882a593Smuzhiyun	return '\b(0x)?ffff[[:xdigit:]]{12}\b';
397*4882a593Smuzhiyun}
398*4882a593Smuzhiyun
399*4882a593Smuzhiyunsub parse_dmesg
400*4882a593Smuzhiyun{
401*4882a593Smuzhiyun	open my $cmd, '-|', 'dmesg';
402*4882a593Smuzhiyun	while (<$cmd>) {
403*4882a593Smuzhiyun		if (may_leak_address($_)) {
404*4882a593Smuzhiyun			print 'dmesg: ' . $_;
405*4882a593Smuzhiyun		}
406*4882a593Smuzhiyun	}
407*4882a593Smuzhiyun	close $cmd;
408*4882a593Smuzhiyun}
409*4882a593Smuzhiyun
410*4882a593Smuzhiyun# True if we should skip this path.
411*4882a593Smuzhiyunsub skip
412*4882a593Smuzhiyun{
413*4882a593Smuzhiyun	my ($path) = @_;
414*4882a593Smuzhiyun
415*4882a593Smuzhiyun	foreach (@skip_abs) {
416*4882a593Smuzhiyun		return 1 if (/^$path$/);
417*4882a593Smuzhiyun	}
418*4882a593Smuzhiyun
419*4882a593Smuzhiyun	my($filename, $dirs, $suffix) = fileparse($path);
420*4882a593Smuzhiyun	foreach (@skip_any) {
421*4882a593Smuzhiyun		return 1 if (/^$filename$/);
422*4882a593Smuzhiyun	}
423*4882a593Smuzhiyun
424*4882a593Smuzhiyun	return 0;
425*4882a593Smuzhiyun}
426*4882a593Smuzhiyun
427*4882a593Smuzhiyunsub timed_parse_file
428*4882a593Smuzhiyun{
429*4882a593Smuzhiyun	my ($file) = @_;
430*4882a593Smuzhiyun
431*4882a593Smuzhiyun	eval {
432*4882a593Smuzhiyun		local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required.
433*4882a593Smuzhiyun		alarm $TIMEOUT;
434*4882a593Smuzhiyun		parse_file($file);
435*4882a593Smuzhiyun		alarm 0;
436*4882a593Smuzhiyun	};
437*4882a593Smuzhiyun
438*4882a593Smuzhiyun	if ($@) {
439*4882a593Smuzhiyun		die unless $@ eq "alarm\n";	# Propagate unexpected errors.
440*4882a593Smuzhiyun		printf STDERR "timed out parsing: %s\n", $file;
441*4882a593Smuzhiyun	}
442*4882a593Smuzhiyun}
443*4882a593Smuzhiyun
444*4882a593Smuzhiyunsub parse_file
445*4882a593Smuzhiyun{
446*4882a593Smuzhiyun	my ($file) = @_;
447*4882a593Smuzhiyun
448*4882a593Smuzhiyun	if (! -R $file) {
449*4882a593Smuzhiyun		return;
450*4882a593Smuzhiyun	}
451*4882a593Smuzhiyun
452*4882a593Smuzhiyun	if (! -T $file) {
453*4882a593Smuzhiyun		return;
454*4882a593Smuzhiyun	}
455*4882a593Smuzhiyun
456*4882a593Smuzhiyun	open my $fh, "<", $file or return;
457*4882a593Smuzhiyun	while ( <$fh> ) {
458*4882a593Smuzhiyun		chomp;
459*4882a593Smuzhiyun		if (may_leak_address($_)) {
460*4882a593Smuzhiyun			printf("$file: $_\n");
461*4882a593Smuzhiyun		}
462*4882a593Smuzhiyun	}
463*4882a593Smuzhiyun	close $fh;
464*4882a593Smuzhiyun}
465*4882a593Smuzhiyun
466*4882a593Smuzhiyun# Checks if the actual path name is leaking a kernel address.
467*4882a593Smuzhiyunsub check_path_for_leaks
468*4882a593Smuzhiyun{
469*4882a593Smuzhiyun	my ($path) = @_;
470*4882a593Smuzhiyun
471*4882a593Smuzhiyun	if (may_leak_address($path)) {
472*4882a593Smuzhiyun		printf("Path name may contain address: $path\n");
473*4882a593Smuzhiyun	}
474*4882a593Smuzhiyun}
475*4882a593Smuzhiyun
476*4882a593Smuzhiyun# Recursively walk directory tree.
477*4882a593Smuzhiyunsub walk
478*4882a593Smuzhiyun{
479*4882a593Smuzhiyun	my @dirs = @_;
480*4882a593Smuzhiyun
481*4882a593Smuzhiyun	while (my $pwd = shift @dirs) {
482*4882a593Smuzhiyun		next if (!opendir(DIR, $pwd));
483*4882a593Smuzhiyun		my @files = readdir(DIR);
484*4882a593Smuzhiyun		closedir(DIR);
485*4882a593Smuzhiyun
486*4882a593Smuzhiyun		foreach my $file (@files) {
487*4882a593Smuzhiyun			next if ($file eq '.' or $file eq '..');
488*4882a593Smuzhiyun
489*4882a593Smuzhiyun			my $path = "$pwd/$file";
490*4882a593Smuzhiyun			next if (-l $path);
491*4882a593Smuzhiyun
492*4882a593Smuzhiyun			# skip /proc/PID except /proc/1
493*4882a593Smuzhiyun			next if (($path =~ /^\/proc\/[0-9]+$/) &&
494*4882a593Smuzhiyun				 ($path !~ /^\/proc\/1$/));
495*4882a593Smuzhiyun
496*4882a593Smuzhiyun			next if (skip($path));
497*4882a593Smuzhiyun
498*4882a593Smuzhiyun			check_path_for_leaks($path);
499*4882a593Smuzhiyun
500*4882a593Smuzhiyun			if (-d $path) {
501*4882a593Smuzhiyun				push @dirs, $path;
502*4882a593Smuzhiyun				next;
503*4882a593Smuzhiyun			}
504*4882a593Smuzhiyun
505*4882a593Smuzhiyun			dprint("parsing: $path\n");
506*4882a593Smuzhiyun			timed_parse_file($path);
507*4882a593Smuzhiyun		}
508*4882a593Smuzhiyun	}
509*4882a593Smuzhiyun}
510*4882a593Smuzhiyun
511*4882a593Smuzhiyunsub format_output
512*4882a593Smuzhiyun{
513*4882a593Smuzhiyun	my ($file) = @_;
514*4882a593Smuzhiyun
515*4882a593Smuzhiyun	# Default is to show raw results.
516*4882a593Smuzhiyun	if ($raw or (!$squash_by_path and !$squash_by_filename)) {
517*4882a593Smuzhiyun		dump_raw_output($file);
518*4882a593Smuzhiyun		return;
519*4882a593Smuzhiyun	}
520*4882a593Smuzhiyun
521*4882a593Smuzhiyun	my ($total, $dmesg, $paths, $files) = parse_raw_file($file);
522*4882a593Smuzhiyun
523*4882a593Smuzhiyun	printf "\nTotal number of results from scan (incl dmesg): %d\n", $total;
524*4882a593Smuzhiyun
525*4882a593Smuzhiyun	if (!$suppress_dmesg) {
526*4882a593Smuzhiyun		print_dmesg($dmesg);
527*4882a593Smuzhiyun	}
528*4882a593Smuzhiyun
529*4882a593Smuzhiyun	if ($squash_by_filename) {
530*4882a593Smuzhiyun		squash_by($files, 'filename');
531*4882a593Smuzhiyun	}
532*4882a593Smuzhiyun
533*4882a593Smuzhiyun	if ($squash_by_path) {
534*4882a593Smuzhiyun		squash_by($paths, 'path');
535*4882a593Smuzhiyun	}
536*4882a593Smuzhiyun}
537*4882a593Smuzhiyun
538*4882a593Smuzhiyunsub dump_raw_output
539*4882a593Smuzhiyun{
540*4882a593Smuzhiyun	my ($file) = @_;
541*4882a593Smuzhiyun
542*4882a593Smuzhiyun	open (my $fh, '<', $file) or die "$0: $file: $!\n";
543*4882a593Smuzhiyun	while (<$fh>) {
544*4882a593Smuzhiyun		if ($suppress_dmesg) {
545*4882a593Smuzhiyun			if ("dmesg:" eq substr($_, 0, 6)) {
546*4882a593Smuzhiyun				next;
547*4882a593Smuzhiyun			}
548*4882a593Smuzhiyun		}
549*4882a593Smuzhiyun		print $_;
550*4882a593Smuzhiyun	}
551*4882a593Smuzhiyun	close $fh;
552*4882a593Smuzhiyun}
553*4882a593Smuzhiyun
554*4882a593Smuzhiyunsub parse_raw_file
555*4882a593Smuzhiyun{
556*4882a593Smuzhiyun	my ($file) = @_;
557*4882a593Smuzhiyun
558*4882a593Smuzhiyun	my $total = 0;          # Total number of lines parsed.
559*4882a593Smuzhiyun	my @dmesg;              # dmesg output.
560*4882a593Smuzhiyun	my %files;              # Unique filenames containing leaks.
561*4882a593Smuzhiyun	my %paths;              # Unique paths containing leaks.
562*4882a593Smuzhiyun
563*4882a593Smuzhiyun	open (my $fh, '<', $file) or die "$0: $file: $!\n";
564*4882a593Smuzhiyun	while (my $line = <$fh>) {
565*4882a593Smuzhiyun		$total++;
566*4882a593Smuzhiyun
567*4882a593Smuzhiyun		if ("dmesg:" eq substr($line, 0, 6)) {
568*4882a593Smuzhiyun			push @dmesg, $line;
569*4882a593Smuzhiyun			next;
570*4882a593Smuzhiyun		}
571*4882a593Smuzhiyun
572*4882a593Smuzhiyun		cache_path(\%paths, $line);
573*4882a593Smuzhiyun		cache_filename(\%files, $line);
574*4882a593Smuzhiyun	}
575*4882a593Smuzhiyun
576*4882a593Smuzhiyun	return $total, \@dmesg, \%paths, \%files;
577*4882a593Smuzhiyun}
578*4882a593Smuzhiyun
579*4882a593Smuzhiyunsub print_dmesg
580*4882a593Smuzhiyun{
581*4882a593Smuzhiyun	my ($dmesg) = @_;
582*4882a593Smuzhiyun
583*4882a593Smuzhiyun	print "\ndmesg output:\n";
584*4882a593Smuzhiyun
585*4882a593Smuzhiyun	if (@$dmesg == 0) {
586*4882a593Smuzhiyun		print "<no results>\n";
587*4882a593Smuzhiyun		return;
588*4882a593Smuzhiyun	}
589*4882a593Smuzhiyun
590*4882a593Smuzhiyun	foreach(@$dmesg) {
591*4882a593Smuzhiyun		my $index = index($_, ': ');
592*4882a593Smuzhiyun		$index += 2;    # skid ': '
593*4882a593Smuzhiyun		print substr($_, $index);
594*4882a593Smuzhiyun	}
595*4882a593Smuzhiyun}
596*4882a593Smuzhiyun
597*4882a593Smuzhiyunsub squash_by
598*4882a593Smuzhiyun{
599*4882a593Smuzhiyun	my ($ref, $desc) = @_;
600*4882a593Smuzhiyun
601*4882a593Smuzhiyun	print "\nResults squashed by $desc (excl dmesg). ";
602*4882a593Smuzhiyun	print "Displaying [<number of results> <$desc>], <example result>\n";
603*4882a593Smuzhiyun
604*4882a593Smuzhiyun	if (keys %$ref == 0) {
605*4882a593Smuzhiyun		print "<no results>\n";
606*4882a593Smuzhiyun		return;
607*4882a593Smuzhiyun	}
608*4882a593Smuzhiyun
609*4882a593Smuzhiyun	foreach(keys %$ref) {
610*4882a593Smuzhiyun		my $lines = $ref->{$_};
611*4882a593Smuzhiyun		my $length = @$lines;
612*4882a593Smuzhiyun		printf "[%d %s] %s", $length, $_, @$lines[0];
613*4882a593Smuzhiyun	}
614*4882a593Smuzhiyun}
615*4882a593Smuzhiyun
616*4882a593Smuzhiyunsub cache_path
617*4882a593Smuzhiyun{
618*4882a593Smuzhiyun	my ($paths, $line) = @_;
619*4882a593Smuzhiyun
620*4882a593Smuzhiyun	my $index = index($line, ': ');
621*4882a593Smuzhiyun	my $path = substr($line, 0, $index);
622*4882a593Smuzhiyun
623*4882a593Smuzhiyun	$index += 2;            # skip ': '
624*4882a593Smuzhiyun	add_to_cache($paths, $path, substr($line, $index));
625*4882a593Smuzhiyun}
626*4882a593Smuzhiyun
627*4882a593Smuzhiyunsub cache_filename
628*4882a593Smuzhiyun{
629*4882a593Smuzhiyun	my ($files, $line) = @_;
630*4882a593Smuzhiyun
631*4882a593Smuzhiyun	my $index = index($line, ': ');
632*4882a593Smuzhiyun	my $path = substr($line, 0, $index);
633*4882a593Smuzhiyun	my $filename = basename($path);
634*4882a593Smuzhiyun
635*4882a593Smuzhiyun	$index += 2;            # skip ': '
636*4882a593Smuzhiyun	add_to_cache($files, $filename, substr($line, $index));
637*4882a593Smuzhiyun}
638*4882a593Smuzhiyun
639*4882a593Smuzhiyunsub add_to_cache
640*4882a593Smuzhiyun{
641*4882a593Smuzhiyun	my ($cache, $key, $value) = @_;
642*4882a593Smuzhiyun
643*4882a593Smuzhiyun	if (!$cache->{$key}) {
644*4882a593Smuzhiyun		$cache->{$key} = ();
645*4882a593Smuzhiyun	}
646*4882a593Smuzhiyun	push @{$cache->{$key}}, $value;
647*4882a593Smuzhiyun}
648