1#!/usr/local/bin/perl
2# Report the hardening characterists of a set of binaries.
3# Copyright (C) 2009-2013 Kees Cook <kees@debian.org>
4# License: GPLv2 or newer
5use strict;
6use warnings;
7use Getopt::Long qw(:config no_ignore_case bundling);
8use Pod::Usage;
9use IPC::Open3;
10use Symbol qw(gensym);
11use Term::ANSIColor;
12
13my $skip_pie = 0;
14my $skip_stackprotector = 0;
15my $skip_fortify = 0;
16my $skip_relro = 0;
17my $skip_bindnow = 0;
18my $report_functions = 0;
19my $find_libc_functions = 0;
20my $color = 0;
21my $lintian = 0;
22my $verbose = 0;
23my $debug = 0;
24my $quiet = 0;
25my $help = 0;
26my $man = 0;
27
28GetOptions(
29        "nopie|p+" => \$skip_pie,
30        "nostackprotector|s+" => \$skip_stackprotector,
31        "nofortify|f+" => \$skip_fortify,
32        "norelro|r+" => \$skip_relro,
33        "nobindnow|b+" => \$skip_bindnow,
34        "report-functions|R!" => \$report_functions,
35        "find-libc-functions|F!" => \$find_libc_functions,
36        "color|c!" => \$color,
37        "lintian|l!" => \$lintian,
38        "verbose|v!" => \$verbose,
39        "debug!" => \$debug,
40        "quiet|q!" => \$quiet,
41        "help|h|?" => \$help,
42        "man|H" => \$man,
43    ) or pod2usage(2);
44pod2usage(1) if $help;
45pod2usage(-exitstatus => 0, -verbose => 2, -noperldoc => 1) if $man;
46
47my $overall = 0;
48my $rc = 0;
49my $report = "";
50my @tags;
51my %libc;
52
53# Report a good test.
54sub good {
55    my ($name, $msg_color, $msg) = @_;
56    $msg_color = colored($msg_color, 'green') if $color;
57    if (defined $msg) {
58        $msg_color .= $msg;
59    }
60    good_msg("$name: $msg_color");
61}
62sub good_msg($) {
63    my ($msg) = @_;
64    if ($quiet == 0) {
65        $report .= "\n$msg";
66    }
67}
68
69sub unknown {
70    my ($name, $msg) = @_;
71    $msg = colored($msg, 'yellow') if $color;
72    good_msg("$name: $msg");
73}
74
75# Report a failed test, possibly ignoring it.
76sub bad($$$$$) {
77    my ($name, $file, $long_name, $msg, $ignore) = @_;
78
79    $msg = colored($msg, 'red') if $color;
80
81    $msg = "$long_name: " . $msg;
82    if ($ignore) {
83        $msg .= " (ignored)";
84    }
85    else {
86        $rc = 1;
87        if ($lintian) {
88            push(@tags, "$name:$file");
89        }
90    }
91    $report .= "\n$msg";
92}
93
94# Safely run list-based command line and return stdout.
95sub output(@) {
96    my (@cmd) = @_;
97    my ($pid, $stdout, $stderr);
98    if ($debug) {
99        print join(" ", @cmd),"\n";
100    }
101    $stdout = gensym;
102    $stderr = gensym;
103    $pid = open3(gensym, $stdout, $stderr, @cmd);
104    my $collect = "";
105    while ( <$stdout> ) {
106        $collect .= $_;
107    }
108    waitpid($pid, 0);
109    my $rc = $?;
110    if ($rc != 0) {
111        while ( <$stderr> ) {
112            print STDERR;
113        }
114        return "";
115    }
116    return $collect;
117}
118
119# Find the libc used in this executable, if any.
120sub find_libc($) {
121    my ($file) = @_;
122    my $ldd = output("ldd", $file);
123    $ldd =~ /^\s*libc\.so\.\S+\s+\S+\s+(\S+)/m;
124    return $1 || "";
125}
126
127sub find_functions($$) {
128    my ($file, $undefined) = @_;
129    my (%funcs);
130
131    # Catch "NOTYPE" for object archives.
132    my $func_regex = " (I?FUNC|NOTYPE) ";
133
134    my $relocs = output("readelf", "-sW", $file);
135    for my $line (split("\n", $relocs)) {
136        next if ($line !~ /$func_regex/);
137        next if ($undefined && $line !~ /$func_regex.* UND /);
138
139        $line =~ s/ \([0-9]+\)$//;
140        $line =~ s/.* //;
141        $line =~ s/@.*//;
142        $funcs{$line} = 1;
143    }
144
145    return \%funcs;
146}
147
148
149$ENV{'LANG'} = "C";
150
151if ($find_libc_functions) {
152    pod2usage(1) if (!defined($ARGV[0]));
153    my $libc_path = find_libc($ARGV[0]);
154
155    my $funcs = find_functions($libc_path, 0);
156    for my $func (sort(keys(%{$funcs}))) {
157        if ($func =~ /^__(\S+)_chk$/) {
158            print "    '$1' => 1,\n";
159        }
160    }
161    exit(0);
162}
163#die "List of libc functions not defined!" if (scalar(keys %libc) < 1);
164
165my $name;
166foreach my $file (@ARGV) {
167    $rc = 0;
168    my $elf = 1;
169
170    $report = "$file:";
171    @tags = ();
172
173    # Get program headers.
174    my $PROG_REPORT=output("readelf", "-lW", $file);
175    if (length($PROG_REPORT) == 0) {
176        $overall = 1;
177        next;
178    }
179
180    # Get ELF headers.
181    my $DYN_REPORT=output("readelf", "-dW", $file);
182
183    # Get list of all symbols needing external resolution.
184    my $functions = find_functions($file, 1);
185
186    # PIE
187    # First, verify this is an executable, not a library. This seems to be
188    # best seen by checking for the PHDR program header.
189    $name = " Position Independent Executable";
190    $PROG_REPORT =~ /^Elf file type is (\S+)/m;
191    my $elftype = $1 || "";
192    if ($elftype eq "DYN") {
193        if ($PROG_REPORT =~ /^ *\bPHDR\b/m) {
194            # Executable, DYN ELF type.
195            good($name, "yes");
196        }
197        else {
198            # Shared library, DYN ELF type.
199            good($name, "no, regular shared library (ignored)");
200        }
201    }
202    elsif ($elftype eq "EXEC") {
203        # Executable, EXEC ELF type.
204        bad("no-pie", $file, $name,
205            "no, normal executable!", $skip_pie);
206    }
207    else {
208        $elf = 0;
209        # Is this an ar file with objects?
210        open(AR, "<$file");
211        my $header = <AR>;
212        close(AR);
213        if ($header eq "!<arch>\n") {
214            good($name, "no, object archive (ignored)");
215        }
216        else {
217            # ELF type is neither DYN nor EXEC.
218            bad("unknown-elf", $file, $name,
219                "not a known ELF type!? ($elftype)", 0);
220        }
221    }
222
223    # Stack-protected
224    $name = " Stack protected";
225    if (defined($functions->{'__stack_chk_fail'}) ||
226        (!$elf && defined($functions->{'__stack_chk_fail_local'}))) {
227        good($name, "yes")
228    }
229    else {
230        bad("no-stackprotector", $file, $name,
231            "no, not found!", $skip_stackprotector);
232    }
233
234    # Fortified Source
235    $name = " Fortify Source functions";
236    my @unprotected;
237    my @protected;
238    for my $name (keys(%libc)) {
239        if (defined($functions->{$name})) {
240            push(@unprotected, $name);
241        }
242        if (defined($functions->{"__${name}_chk"})) {
243            push(@protected, $name);
244        }
245    }
246    if ($#protected > -1) {
247        if ($#unprotected == -1) {
248            # Certain.
249            good($name, "yes");
250        }
251        else {
252            # Vague, due to possible compile-time optimization,
253            # multiple linkages, etc. Assume "yes" for now.
254            good($name, "yes", " (some protected functions found)");
255        }
256    }
257    else {
258        if ($#unprotected == -1) {
259            unknown($name, "unknown, no protectable libc functions used");
260        }
261        else {
262            # Vague, since it's possible to have the compile-time
263            # optimizations do away with them, or be unverifiable
264            # at runtime. Assume "no" for now.
265            bad("no-fortify-functions", $file, $name,
266                "no, only unprotected functions found!", $skip_fortify);
267        }
268    }
269    if ($verbose) {
270        for my $name (@unprotected) {
271            good_msg("\tunprotected: $name");
272        }
273        for my $name (@protected) {
274            good_msg("\tprotected: $name");
275        }
276    }
277
278    # Format
279    # Unfortunately, I haven't thought of a way to test for this after
280    # compilation. What it really needs is a lintian-like check that
281    # reviews the build logs and looks for the warnings, or that the
282    # argument is changed to use -Werror=format-security to stop the build.
283
284    # RELRO
285    $name = " Read-only relocations";
286    if ($PROG_REPORT =~ /^ *\bGNU_RELRO\b/m) {
287        good($name, "yes");
288    } else {
289        if ($elf) {
290            bad("no-relro", $file, $name, "no, not found!", $skip_relro);
291        } else {
292            good($name, "no", ", non-ELF (ignored)");
293        }
294    }
295
296    # BIND_NOW
297    # This marking keeps changing:
298    # 0x0000000000000018 (BIND_NOW)
299    # 0x000000006ffffffb (FLAGS)              Flags: BIND_NOW
300    # 0x000000006ffffffb (FLAGS_1)            Flags: NOW
301
302    $name = " Immediate binding";
303    if ($DYN_REPORT =~ /^\s*\S+\s+\(BIND_NOW\)/m ||
304        $DYN_REPORT =~ /^\s*\S+\s+\(FLAGS\).*\bBIND_NOW\b/m ||
305        $DYN_REPORT =~ /^\s*\S+\s+\(FLAGS_1\).*\bNOW\b/m) {
306        good($name, "yes");
307    } else {
308        if ($elf) {
309            bad("no-bindnow", $file, $name, "no, not found!", $skip_bindnow);
310        } else {
311            good($name, "no", ", non-ELF (ignored)");
312        }
313    }
314
315    if (!$lintian && (!$quiet || $rc != 0)) {
316        print $report,"\n";
317    }
318
319    if ($report_functions) {
320        for my $name (keys(%{$functions})) {
321            print $name,"\n";
322        }
323    }
324
325    if (!$lintian && $rc) {
326        $overall = $rc;
327    }
328
329    if ($lintian) {
330        for my $tag (@tags) {
331            print $tag, "\n";
332        }
333    }
334}
335
336exit($overall);
337
338__END__
339
340=pod
341
342=head1 NAME
343
344hardening-check - check binaries for security hardening features
345
346=head1 SYNOPSIS
347
348hardening-check [options] [ELF ...]
349
350Examine a given set of ELF binaries and check for several security hardening
351features, failing if they are not all found.
352
353=head1 DESCRIPTION
354
355This utility checks a given list of ELF binaries for several security
356hardening features that can be compiled into an executable. These
357features are:
358
359=over 8
360
361=item B<Position Independent Executable>
362
363This indicates that the executable was built in such a way (PIE) that
364the "text" section of the program can be relocated in memory. To take
365full advantage of this feature, the executing kernel must support text
366Address Space Layout Randomization (ASLR).
367
368=item B<Stack Protected>
369
370This indicates that there is evidence that the ELF was compiled with the
371L<gcc(1)> option B<-fstack-protector> (e.g. uses B<__stack_chk_fail>). The
372program will be resistant to having its stack overflowed.
373
374When an executable was built without any character arrays being allocated
375on the stack, this check will lead to false alarms (since there is no
376use of B<__stack_chk_fail>), even though it was compiled with the correct
377options.
378
379=item B<Fortify Source functions>
380
381This indicates that the executable was compiled with
382B<-D_FORTIFY_SOURCE=2> and B<-O1> or higher. This causes certain unsafe
383glibc functions with their safer counterparts (e.g. B<strncpy> instead
384of B<strcpy>), or replaces calls that are verifiable at runtime with the
385runtime-check version (e.g. B<__memcpy_chk> insteade of B<memcpy>).
386
387When an executable was built such that the fortified versions of the glibc
388functions are not useful (e.g. use is verified as safe at compile time, or
389use cannot be verified at runtime), this check will lead to false alarms.
390In an effort to mitigate this, the check will pass if any fortified function
391is found, and will fail if only unfortified functions are found. Uncheckable
392conditions also pass (e.g. no functions that could be fortified are found, or
393not linked against glibc). This function is currently Not supported on FreeBSD.
394
395=item B<Read-only relocations>
396
397This indicates that the executable was build with B<-Wl,-z,relro> to
398have ELF markings (RELRO) that ask the runtime linker to mark any
399regions of the relocation table as "read-only" if they were resolved
400before execution begins. This reduces the possible areas of memory in
401a program that can be used by an attacker that performs a successful
402memory corruption exploit.
403
404=item B<Immediate binding>
405
406This indicates that the executable was built with B<-Wl,-z,now> to have
407ELF markings (BIND_NOW) that ask the runtime linker to resolve all
408relocations before starting program execution. When combined with RELRO
409above, this further reduces the regions of memory available to memory
410corruption attacks.
411
412=back
413
414=head1 OPTIONS
415
416=over 8
417
418=item B<--nopie>, B<-p>
419
420No not require that the checked binaries be built as PIE.
421
422=item B<--nostackprotector>, B<-s>
423
424No not require that the checked binaries be built with the stack protector.
425
426=item B<--nofortify>, B<-f>
427
428No not require that the checked binaries be built with Fority Source.
429
430=item B<--norelro>, B<-r>
431
432No not require that the checked binaries be built with RELRO.
433
434=item B<--nobindnow>, B<-b>
435
436No not require that the checked binaries be built with BIND_NOW.
437
438=item B<--quiet>, B<-q>
439
440Only report failures.
441
442=item B<--verbose>, B<-v>
443
444Report verbosely on failures.
445
446=item B<--report-functions>, B<-R>
447
448After the report, display all external functions needed by the ELF.
449
450=item B<--find-libc-functions>, B<-F>
451
452Instead of the regular report, locate the libc for the first ELF on the
453command line and report all the known "fortified" functions exported by
454libc. Not supported on FreeBSD now.
455
456=item B<--color>, B<-c>
457
458Enable colorized status output.
459
460=item B<--lintian>, B<-l>
461
462Switch reporting to lintian-check-parsable output.
463
464=item B<--debug>
465
466Report some debugging during processing.
467
468=item B<--help>, B<-h>, B<-?>
469
470Print a brief help message and exit.
471
472=item B<--man>, B<-H>
473
474Print the manual page and exit.
475
476=back
477
478=head1 RETURN VALUE
479
480When all checked binaries have all checkable hardening features detected,
481this program will finish with an exit code of 0. If any check fails, the
482exit code with be 1. Individual checks can be disabled via command line
483options.
484
485=head1 AUTHOR
486
487Kees Cook <kees@debian.org>
488
489=head1 COPYRIGHT AND LICENSE
490
491Copyright 2009-2013 Kees Cook <kees@debian.org>.
492
493This program is free software; you can redistribute it and/or modify it
494under the terms of the GNU General Public License as published by the
495Free Software Foundation; version 2 or later.
496
497=head1 SEE ALSO
498
499L<gcc(1)>, L<hardening-wrapper(1)>
500
501=cut
502