[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Introducing rlintian ("restricted lintian")



On 2013-04-20 21:52, Niels Thykier wrote:
> [...]
> 

So, in light of a discussion on #debian-devel today, I believe we should
add a new frontend to run Lintian in a restricted mode.

> [...]
> Command-line/frontend changes <CLI>
> ===================================
> 
> [...]
>   * --[no-]user-dirs
> 
>     Whether to include user dirs ($HOME, $XDG_CONFIG_* and /etc) dirs.
> 
>   * --ignore-lintian-env
> 
>     Ignore environment variables starting with "LINTIAN_".  Together
>     with --no-user-dirs, this will avoid most "indirect" influence
>     from the user and ease running Lintian with "higher privileges"
>     than normal.  Note that TMPDIR/PATH/etc. are still used as-is.
> 
> [...]

While these options are "cute", they are not sufficient to provide an
easy way to run Lintian in trusted setting that is "secure by default".
 The first problem occurs already before loading Lintian; namely at the
point where perl parses PERL5LIB/PERLLIB and PERL5OPT.

We could change lintian to be "secure by default", but the problem is
"security" depends on the usage pattern of Lintian.  Particularly, in
aptdaemon's case Lintian is used to enforce some (local?) policy
requirements for installing packages.  If you can choose the profile or
visibility settings, you can change/bypass the desired policy.
  So a "secure by default" Lintian could not load the user config files
(without an explicit --cfg or --user-dirs).  It would also require taint
mode to protect us from PERL5LIB/PERLLIB and PERL5OPT.  To be honest, I
doubt our users would appreciate a "--no-user-dirs" by default because
of this.

I propose we include a new frontend, rlintian (or some other name).  Its
sole purpose would be to provide people with an honest chance of using
Lintian "right" in these cases.  I have attached a prototype of such a
frontend.

~Niels

#!/usr/bin/perl -T
#
# Yes, use taint mode.  To protect us from PERL5LIB, PERLLIB and
# PERL5OPT.  Without taint mode we would lose by default if an
# untrusted individual could set any of those options.
#

# {{{ Legal stuff
# R(estricted )Lintian -- Debian package checker
#
# Copyright (C) 2013 Niels Thykier
#
# This program is free software.  It is distributed 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 File::Basename qw(basename);
use Getopt::Long::Descriptive;
use List::MoreUtils qw(any first_index);

# Skip --ignore-lintian-env, rlintian is much more aggressive at purging
# %ENV and it would complicate --keep-env.
my @DEFAULT_LINTIAN_OPTIONS = qw(
    --no-user-dirs
);
my @ENV_WHITELIST = qw();
my %ENV_SET = (
    'PATH'  => '/bin:/usr/bin',
    'SHELL' => '/bin/sh',
);
my @OPTION_SPECS = (
    ['help|h', 'Show some help information and exit 0'],
    ['debug-setup', 'Show the ENV and options for Lintian and exit 0'],
    ['keep-env=s@', 'Preserve the list of environment variables listed',
     { default => [] }],
    ['lintian-cmd=s', 'Use the given command as lintian frontend',
     { default => 'lintian'} ]
);
my @LINTIAN_OPTIONS;
my @LINTIAN_FILE_ARGS;

$0 = basename($0);

# Untaint all of our arguments (or describe_options would croak on it)
@ARGV = map { untaint_arg($_); } @ARGV;

my $had_sep = any { $_ eq '--' } @ARGV;

my ($opt, $usage) = describe_options(
    'Syntax: %c [rlintian options] [-- [lintian options]] -- file [...]',
    [], # inserts a blank line
    @OPTION_SPECS,
    { 'getopt_conf' => ['bundling', 'no_getopt_compat', 'no_auto_abbrev'] },
);

if ($opt->help) {
    print $usage;
    print << "EOF" ;

$0 is "restricted Lintian" and aims to run Lintian in situation where
the caller may not trusted.  $0 will purge or reset ALL environment
variables except those explicitly requested (via --keep-env).

Example usage:

  # Run Lintian on some-file.changes keeping TMPDIR as-is.
  \$ $0 --keep-env TMPDIR -- some-file.changes

  # Run Lintian on some-file.changes, passing --profile '{VENDOR}/my-profile'
  # to Lintian and keeping TMPDIR and DEB_VENDOR as-is.
  \$ $0 --keep-env TMPDIR,DEB_VENDOR -- \\
           --profile '{VENDOR}/my-profile' -- \\
           some-file.changes

EOF
    exit 0;
}

if (!$had_sep) {
    print STDERR "At least one \"--\" is required before the file names\n";
    exit 2;
}

my $sep_index = first_index { $_ eq '--' } @ARGV;
if ($sep_index >= 0) {
    # If there is still a "--" present, that means the caller wants to
    # pass options to Lintian itself.
    @LINTIAN_FILE_ARGS = splice(@ARGV, $sep_index + 1);
    # pop the "--" separator
    pop(@ARGV);
    @LINTIAN_OPTIONS = (@DEFAULT_LINTIAN_OPTIONS, @ARGV);
} else {
    # Otherwise, all remaining arguments should be file names.
    @LINTIAN_OPTIONS = @DEFAULT_LINTIAN_OPTIONS;
    @LINTIAN_FILE_ARGS = @ARGV;
}

# Purge impurities from our environment.
my %copy = %ENV_SET;
for my $env (@ENV_WHITELIST, @{ $opt->keep_env }) {
    $copy{$env} = untaint($ENV{$env}) if exists $ENV{$env};
}
%ENV = %copy;

if ($opt->debug_setup) {
    debug_setup();
    exit 0;
}

# Stop processing if one of the file arguments are not files
if (!check_file_args()) {
    print STDERR "Invalid file arguments given, aborting.\n";
    exit 2;
}

exec $opt->lintian_cmd, @LINTIAN_OPTIONS, '--', @LINTIAN_FILE_ARGS
    or die "Cannot exec lintian: $!";

sub debug_setup {
    my $full_lint_cmd = join(' ', $opt->lintian_cmd, @LINTIAN_OPTIONS,
                             '--', @LINTIAN_FILE_ARGS);
    my @user_env = sort(@ENV_WHITELIST, keys(%ENV_SET), @{ $opt->keep_env });
    print "Setup information:\n\n";
    print " * Current Lintian command line:\n";
    print "     $full_lint_cmd\n\n";
    print " * The environment used will be:\n";
    for my $env_var (@user_env) {
        if (exists($ENV{$env_var})) {
            print "    $env_var=$ENV{$env_var}\n";
        } else {
            print "    $env_var=<preseved, currently unset>\n";
        }
    }
    print "\n";
    if ($opt->lintian_cmd !~ m{/}xsm) {
        print "NB: When starting Lintian the PATH listed above is the one used\n";
        print "to find Lintian\n\n";
    }

    print "Defaults:\n";
    print " * Default Lintian command line:\n";
    print '    ', join(' ', 'lintian', @DEFAULT_LINTIAN_OPTIONS), "\n\n";
    print " * The default environment is:\n";
    for my $env_var (sort(keys(%ENV_SET), @ENV_WHITELIST)) {
        if (!exists($ENV_SET{$env_var})) {
            print "    $env_var=<preserved>\n";
        } else {
            print "    $env_var=$ENV{$env_var}\n";
        }
    }
}

sub untaint_arg {
    my ($arg) = @_;
    if ($arg =~ m{^ ( [^ & ; | \$ \( \) ]*+ ) $}xsm) {
        return $1;
    }
    die "Cowardly refusing to untaint \"$arg\" (has shell meta characters)\n";
}


sub untaint {
    my ($arg) = @_;
    if ($arg =~ m{^ ( .*+ ) $}xsm) {
        return $1;
    }
    die "Not sure how \"$arg\" cannot match the above...\n";
}

sub check_file_args {
    my $ok = 1;
    for my $filename (@LINTIAN_FILE_ARGS) {
        if (! -f $filename) {
            print STDERR "$filename is not a file!\n";
            $ok = 0;
        } elsif ($filename !~ m{ \. (?: u?deb|dsc|changes ) $}xsm) {
            print STDERR "$filename has unknown extension type\n";
            $ok = 0;
        }
    }
    return $ok;
}

Reply to: