#!/usr/bin/perl
# hardening-info-helper -- lintian collection script helper

# Copyright (C) 2012 Niels Thykier
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, you can find it on the World Wide
# Web at http://www.gnu.org/copyleft/gpl.html, or write to the Free
# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
# MA 02110-1301, USA.

use strict;
use warnings;

use FileHandle;

use lib "$ENV{'LINTIAN_ROOT'}/lib";
use Lintian::Command qw(spawn reap);
use Lintian::Util qw(fail);

# To reduce the number of false-positives in hardening-check for
# fortify-functions, we have to "double-check" its output in some
# cases (like we do with file-info).
#
# Basic idea - fork and pipe to child in up to two passes.
# - The parent will filter "hardening-check --lintian" input in first
#   pass.
#   - Filter out (and collect) all no-fortify-function tags
#   - Work around bug #677530
# - The parent will (in second pass) pipe the verbose hardening-check
#   output to the child.
#   - Only binaries with a no-foritfy-function tag in the first pass
#     will be re-checked with --verbose.
#
# - In the first pass, the child will behave like cat.
# - In the second pass, the child will parse hardening-check --verbose
#   output.
#
# Implied by the above - the second pass is only done if needed.


my ($in, $out);
my ($cread, $cwrite);
my $cpid;
my @recheck = ();
# Work around bug in hardening-check when it proceses multiple binaries
# with --lintian (see #677530).
my %seen = ();

pipe ($cread, $cwrite) or fail "pipe failed: $!";
$cpid = fork();
fail "fork failed: $!" unless defined $cpid;
if ($cpid) {
    # parent
    close $cread; # read end not needed
    $in = \*STDIN;
    $out = $cwrite;
} else {
    # child
    close $cwrite; # write end not needed.
    $in = $cread;
    $out = \*STDOUT;
}

while (my $line = <$in>) {
    chomp $line;
    if ($cpid) {
        # The parent filters the duplicate tags (#677530)
        next if $seen{$line}++;
        if ($line =~ m/^no-fortify-functions:(.*)$/o) {
            my $bin = $1;
            push @recheck, $bin;
            next;
        }
    } else {
        # End of "first pass" marker (for the child).
        last if $line eq '__VERBOSE__';
    }
    print $out "$line\n";
}

undef %seen;

if (not $cpid) {
    # child's second pass
    my $bin;
    my $infsf = 0;
    my $emit = 0;
    while (my $line = <$in>) {
        chomp $line;
        # At this point we are reading "verbose" hardening-check output
        if ($line =~ m,^(\S.+):$,) {
            if ($emit) {
                print $out "no-fortify-functions:$bin\n";
            }
            $bin = $1;
            $infsf = 0;
            $emit = 0;
        } elsif ($line =~ m/^\s+Fortify Source functions:/) {
            $infsf = 1;
        } elsif ($infsf and $line =~ m/^\s+unprotected:\s*(\S+)/) {
            next if $1 eq 'memcpy';
            $emit = 1;
        } else {
            $infsf = 0;
        }
    }
    if ($emit) {
        print $out "no-fortify-functions:$bin\n";
    }
    # ensure $out is flushed before exiting.
    close $out or fail "close output: $!";
    require POSIX;
    POSIX::_exit (0);
} elsif (@recheck) {
    # (optionally) parent's second pass.
    my %opts = (
        pipe_in => FileHandle->new,
        out => $out,
        fail => 'never'
    );
    # End the first pass for the child
    print $out "__VERBOSE__\n";
    spawn(\%opts, ['xargs', '-0r', 'hardening-check', '--verbose', '--']);
    $opts{pipe_in}->blocking (1);
    foreach my $file (@recheck) {
        printf {$opts{pipe_in}} "%s\0", $file;
    }
    close $opts{pipe_in};
    reap (\%opts);
}

# Close the out handle, else the child process will wait for
# ever.
close $out;
# wait for the child process.
wait();
exit $?;

