#!/usr/bin/env perl # SPDX-License-Identifier: GPL-2.0-only # # (c) 2017 Tobin C. Harding <me@tobin.cc> # # leaking_addresses.pl: Scan the kernel for potential leaking addresses. # - Scans dmesg output. # - Walks directory tree and parses each file (for each directory in @DIRS). # # Use --debug to output path before parsing, this is useful to find files that # cause the script to choke.
# # When the system is idle it is likely that most files under /proc/PID will be # identical for various processes. Scanning _all_ the PIDs under /proc is # unnecessary and implies that we are thoroughly scanning /proc. This is _not_ # the case because there may be ways userspace can trigger creation of /proc # files that leak addresses but were not present during a scan. For these two # reasons we exclude all PID directories under /proc except '1/'
use warnings;
use strict;
use POSIX;
use File::Basename;
use File::Spec;
use File::Temp qw/tempfile/;
use Cwd 'abs_path';
use Term::ANSIColor qw(:constants);
use Getopt::Long qw(:config no_auto_abbrev);
use Config;
use bigint qw/hex/;
use feature 'state';
my $P = $0;
# Directories to scan.
my @DIRS = ('/proc', '/sys');
# Timer for parsing each file, in seconds.
my $TIMEOUT = 10;
# Kernel addresses vary by architecture. We can only auto-detect the following # architectures (using `uname -m`). (flag --32-bit overrides auto-detection.)
my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64', 'x86');
# Command line options.
my $help = 0;
my $debug = 0;
my $raw = 0;
my $output_raw = ""; # Write raw results to file.
my $input_raw = ""; # Read raw results from file instead of scanning.
my $suppress_dmesg = 0; # Don't show dmesg in output.
my $squash_by_path = 0; # Summary report grouped by absolute path.
my $squash_by_filename = 0; # Summary report grouped by filename.
my $kallsyms_file = ""; # Kernel symbols file.
my $kernel_config_file = ""; # Kernel configuration file.
my $opt_32bit = 0; # Scan 32-bit kernel.
my $page_offset_32bit = 0; # Page offset for 32-bit kernel.
my @kallsyms = ();
# Skip these absolute paths.
my @skip_abs = ( '/proc/kmsg', '/proc/device-tree', '/proc/1/syscall', '/sys/firmware/devicetree', '/sys/kernel/tracing/trace_pipe', '/sys/kernel/debug/tracing/trace_pipe', '/sys/kernel/security/apparmor/revision');
# Skip these under any subdirectory.
my @skip_any = ( 'pagemap', 'events', 'access', 'registers', 'snapshot_raw', 'trace_pipe_raw', 'ptmx', 'trace_pipe', 'fd', 'usbmon');
sub help
{
my ($exitcode) = @_;
print << "EOM";
Usage: $P [OPTIONS]
Options:
-o, --output-raw=<file> Save results for future processing.
-i, --input-raw=<file> Read results from file instead of scanning.
--raw Show raw results (default).
--suppress-dmesg Do not show dmesg results.
--squash-by-path Show one result per unique path.
--squash-by-filename Show one result per unique filename.
--kernel-config-file=<file> Kernel configuration file (e.g /boot/config)
--kallsyms=<file> Read kernel symbol addresses from file (for
scanning binary files).
--32-bit Scan 32-bit kernel.
--page-offset-32-bit=o Page offset (for 32-bit kernel 0xABCD1234).
-d, --debug Display debugging output.
-h, --help Display this help and exit.
Scans the running kernel for potential leaking addresses.
if ($input_raw) {
format_output($input_raw);
exit(0);
}
if (!$input_raw and ($squash_by_path or $squash_by_filename)) {
printf "\nSummary reporting only available with --input-raw=<file>\n";
printf "(First run scan with --output-raw=<file>.)\n";
exit(128);
}
if (!(is_supported_architecture() or $opt_32bit or $page_offset_32bit)) {
printf "\nScript does not support your architecture, sorry.\n";
printf "\nCurrently we support: \n\n"; foreach(@SUPPORTED_ARCHITECTURES) {
printf "\t%s\n", $_;
}
printf("\n");
printf("If you are running a 32-bit architecture you may use:\n");
printf("\n\t--32-bit or --page-offset-32-bit=<page offset>\n\n");
my $archname = `uname -m`;
printf("Machine hardware name (`uname -m`): %s\n", $archname);
exit(129);
}
if ($output_raw) {
open my $fh, '>', $output_raw or die "$0: $output_raw: $!\n";
select $fh;
}
if ($kallsyms_file) {
open my $fh, '<', $kallsyms_file or die "$0: $kallsyms_file: $!\n"; while (<$fh>) {
chomp;
my @entry = split / /, $_;
my $addr_text = $entry[0]; if ($addr_text !~ /^0/) { # TODO: Why is hex() so impossibly slow?
my $addr = hex($addr_text);
my $symbol = $entry[2]; # Only keep kernel text addresses.
my $long = pack("J", $addr);
my $entry = [$long, $symbol];
push @kallsyms, $entry;
}
}
close $fh;
}
parse_dmesg();
walk(@DIRS);
exit 0;
sub dprint
{
printf(STDERR @_) if $debug;
}
sub is_supported_architecture
{
return (is_x86_64() or is_ppc64() or is_ix86_32());
}
sub is_32bit
{ # Allow --32-bit or --page-offset-32-bit to override if ($opt_32bit or $page_offset_32bit) {
return 1;
}
sub is_x86_64
{
state $is = is_arch('x86_64');
return $is;
}
sub is_ppc64
{
state $is = is_arch('ppc64');
return $is;
}
# Gets config option value from kernel config file. # Returns "" on error or if config option not found.
sub get_kernel_config_option
{
my ($option) = @_;
my $value = "";
my $tmp_fh;
my $tmp_file = "";
my @config_files;
# Allow --kernel-config-file to override. if ($kernel_config_file ne "") {
@config_files = ($kernel_config_file);
} elsif (-R "/proc/config.gz") {
($tmp_fh, $tmp_file) = tempfile("config.gz-XXXXXX",
UNLINK => 1);
$address_re = get_address_re(); while ($line =~ /($address_re)/g) { if (!is_false_positive($1)) {
return 1;
}
}
return 0;
}
sub get_address_re
{ if (is_ppc64()) {
return '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b';
} elsif (is_32bit()) {
return '\b(0x)?[[:xdigit:]]{8}\b';
}
return get_x86_64_re();
}
sub get_x86_64_re
{ # We handle page table levels but only if explicitly configured using # CONFIG_PGTABLE_LEVELS. If config file parsing fails or config option # is not found we default to using address regular expression suitable # for 4 page table levels.
state $ptl = get_kernel_config_option('CONFIG_PGTABLE_LEVELS');
# Default is to show raw results. if ($raw or (!$squash_by_path and !$squash_by_filename)) {
dump_raw_output($file);
return;
}
my ($total, $dmesg, $paths, $files) = parse_raw_file($file);
printf "\nTotal number of results from scan (incl dmesg): %d\n", $total;
if (!$suppress_dmesg) {
print_dmesg($dmesg);
}
if ($squash_by_filename) {
squash_by($files, 'filename');
}
if ($squash_by_path) {
squash_by($paths, 'path');
}
}
sub dump_raw_output
{
my ($file) = @_;
open (my $fh, '<', $file) or die "$0: $file: $!\n"; while (<$fh>) { if ($suppress_dmesg) { if ("dmesg:" eq substr($_, 0, 6)) {
next;
}
}
print $_;
}
close $fh;
}
sub parse_raw_file
{
my ($file) = @_;
my $total = 0; # Total number of lines parsed.
my @dmesg; # dmesg output.
my %files; # Unique filenames containing leaks.
my %paths; # Unique paths containing leaks.
open (my $fh, '<', $file) or die "$0: $file: $!\n"; while (my $line = <$fh>) {
$total++;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.