leaking_addresses.pl 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  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. #
  10. # You can configure the behaviour of the script;
  11. #
  12. # - By adding paths, for directories you do not want to walk;
  13. # absolute paths: @skip_walk_dirs_abs
  14. # directory names: @skip_walk_dirs_any
  15. #
  16. # - By adding paths, for files you do not want to parse;
  17. # absolute paths: @skip_parse_files_abs
  18. # file names: @skip_parse_files_any
  19. #
  20. # The use of @skip_xxx_xxx_any causes files to be skipped where ever they occur.
  21. # For example adding 'fd' to @skip_walk_dirs_any causes the fd/ directory to be
  22. # skipped for all PID sub-directories of /proc
  23. #
  24. # The same thing can be achieved by passing command line options to --dont-walk
  25. # and --dont-parse. If absolute paths are supplied to these options they are
  26. # appended to the @skip_xxx_xxx_abs arrays. If file names are supplied to these
  27. # options, they are appended to the @skip_xxx_xxx_any arrays.
  28. #
  29. # Use --debug to output path before parsing, this is useful to find files that
  30. # cause the script to choke.
  31. #
  32. # You may like to set kptr_restrict=2 before running script
  33. # (see Documentation/sysctl/kernel.txt).
  34. use warnings;
  35. use strict;
  36. use POSIX;
  37. use File::Basename;
  38. use File::Spec;
  39. use Cwd 'abs_path';
  40. use Term::ANSIColor qw(:constants);
  41. use Getopt::Long qw(:config no_auto_abbrev);
  42. my $P = $0;
  43. my $V = '0.01';
  44. # Directories to scan.
  45. my @DIRS = ('/proc', '/sys');
  46. # Command line options.
  47. my $help = 0;
  48. my $debug = 0;
  49. my @dont_walk = ();
  50. my @dont_parse = ();
  51. # Do not parse these files (absolute path).
  52. my @skip_parse_files_abs = ('/proc/kmsg',
  53. '/proc/kcore',
  54. '/proc/fs/ext4/sdb1/mb_groups',
  55. '/proc/1/fd/3',
  56. '/sys/kernel/debug/tracing/trace_pipe',
  57. '/sys/kernel/security/apparmor/revision');
  58. # Do not parse thes files under any subdirectory.
  59. my @skip_parse_files_any = ('0',
  60. '1',
  61. '2',
  62. 'pagemap',
  63. 'events',
  64. 'access',
  65. 'registers',
  66. 'snapshot_raw',
  67. 'trace_pipe_raw',
  68. 'ptmx',
  69. 'trace_pipe');
  70. # Do not walk these directories (absolute path).
  71. my @skip_walk_dirs_abs = ();
  72. # Do not walk these directories under any subdirectory.
  73. my @skip_walk_dirs_any = ('self',
  74. 'thread-self',
  75. 'cwd',
  76. 'fd',
  77. 'stderr',
  78. 'stdin',
  79. 'stdout');
  80. sub help
  81. {
  82. my ($exitcode) = @_;
  83. print << "EOM";
  84. Usage: $P [OPTIONS]
  85. Version: $V
  86. Options:
  87. --dont-walk=<dir> Don't walk tree starting at <dir>.
  88. --dont-parse=<file> Don't parse <file>.
  89. -d, --debug Display debugging output.
  90. -h, --help, --version Display this help and exit.
  91. If an absolute path is passed to --dont_XXX then this path is skipped. If a
  92. single filename is passed then this file/directory will be skipped when
  93. appearing under any subdirectory.
  94. Example:
  95. # Just scan dmesg output.
  96. scripts/leaking_addresses.pl --dont_walk_abs /proc --dont_walk_abs /sys
  97. Scans the running (64 bit) kernel for potential leaking addresses.
  98. EOM
  99. exit($exitcode);
  100. }
  101. GetOptions(
  102. 'dont-walk=s' => \@dont_walk,
  103. 'dont-parse=s' => \@dont_parse,
  104. 'd|debug' => \$debug,
  105. 'h|help' => \$help,
  106. 'version' => \$help
  107. ) or help(1);
  108. help(0) if ($help);
  109. push_to_global();
  110. parse_dmesg();
  111. walk(@DIRS);
  112. exit 0;
  113. sub debug_arrays
  114. {
  115. print 'dirs_any: ' . join(", ", @skip_walk_dirs_any) . "\n";
  116. print 'dirs_abs: ' . join(", ", @skip_walk_dirs_abs) . "\n";
  117. print 'parse_any: ' . join(", ", @skip_parse_files_any) . "\n";
  118. print 'parse_abs: ' . join(", ", @skip_parse_files_abs) . "\n";
  119. }
  120. sub dprint
  121. {
  122. printf(STDERR @_) if $debug;
  123. }
  124. sub push_in_abs_any
  125. {
  126. my ($in, $abs, $any) = @_;
  127. foreach my $path (@$in) {
  128. if (File::Spec->file_name_is_absolute($path)) {
  129. push @$abs, $path;
  130. } elsif (index($path,'/') == -1) {
  131. push @$any, $path;
  132. } else {
  133. print 'path error: ' . $path;
  134. }
  135. }
  136. }
  137. # Push command line options to global arrays.
  138. sub push_to_global
  139. {
  140. push_in_abs_any(\@dont_walk, \@skip_walk_dirs_abs, \@skip_walk_dirs_any);
  141. push_in_abs_any(\@dont_parse, \@skip_parse_files_abs, \@skip_parse_files_any);
  142. }
  143. sub is_false_positive
  144. {
  145. my ($match) = @_;
  146. if ($match =~ '\b(0x)?(f|F){16}\b' or
  147. $match =~ '\b(0x)?0{16}\b') {
  148. return 1;
  149. }
  150. # vsyscall memory region, we should probably check against a range here.
  151. if ($match =~ '\bf{10}600000\b' or
  152. $match =~ '\bf{10}601000\b') {
  153. return 1;
  154. }
  155. return 0;
  156. }
  157. # True if argument potentially contains a kernel address.
  158. sub may_leak_address
  159. {
  160. my ($line) = @_;
  161. my $address = '\b(0x)?ffff[[:xdigit:]]{12}\b';
  162. # Signal masks.
  163. if ($line =~ '^SigBlk:' or
  164. $line =~ '^SigCgt:') {
  165. return 0;
  166. }
  167. if ($line =~ '\bKEY=[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or
  168. $line =~ '\b[[:xdigit:]]{14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b') {
  169. return 0;
  170. }
  171. while (/($address)/g) {
  172. if (!is_false_positive($1)) {
  173. return 1;
  174. }
  175. }
  176. return 0;
  177. }
  178. sub parse_dmesg
  179. {
  180. open my $cmd, '-|', 'dmesg';
  181. while (<$cmd>) {
  182. if (may_leak_address($_)) {
  183. print 'dmesg: ' . $_;
  184. }
  185. }
  186. close $cmd;
  187. }
  188. # True if we should skip this path.
  189. sub skip
  190. {
  191. my ($path, $paths_abs, $paths_any) = @_;
  192. foreach (@$paths_abs) {
  193. return 1 if (/^$path$/);
  194. }
  195. my($filename, $dirs, $suffix) = fileparse($path);
  196. foreach (@$paths_any) {
  197. return 1 if (/^$filename$/);
  198. }
  199. return 0;
  200. }
  201. sub skip_parse
  202. {
  203. my ($path) = @_;
  204. return skip($path, \@skip_parse_files_abs, \@skip_parse_files_any);
  205. }
  206. sub parse_file
  207. {
  208. my ($file) = @_;
  209. if (! -R $file) {
  210. return;
  211. }
  212. if (skip_parse($file)) {
  213. dprint "skipping file: $file\n";
  214. return;
  215. }
  216. dprint "parsing: $file\n";
  217. open my $fh, "<", $file or return;
  218. while ( <$fh> ) {
  219. if (may_leak_address($_)) {
  220. print $file . ': ' . $_;
  221. }
  222. }
  223. close $fh;
  224. }
  225. # True if we should skip walking this directory.
  226. sub skip_walk
  227. {
  228. my ($path) = @_;
  229. return skip($path, \@skip_walk_dirs_abs, \@skip_walk_dirs_any)
  230. }
  231. # Recursively walk directory tree.
  232. sub walk
  233. {
  234. my @dirs = @_;
  235. my %seen;
  236. while (my $pwd = shift @dirs) {
  237. next if (skip_walk($pwd));
  238. next if (!opendir(DIR, $pwd));
  239. my @files = readdir(DIR);
  240. closedir(DIR);
  241. foreach my $file (@files) {
  242. next if ($file eq '.' or $file eq '..');
  243. my $path = "$pwd/$file";
  244. next if (-l $path);
  245. if (-d $path) {
  246. push @dirs, $path;
  247. } else {
  248. parse_file($path);
  249. }
  250. }
  251. }
  252. }