[SCM] Debian package checker branch, wheezy, updated. 2.5.10.4-15-ge0dc594
The following commit has been merged in the wheezy branch:
commit 32d264977a975183b4b22435e56ab13011145717
Author: Niels Thykier <niels@thykier.net>
Date: Fri Apr 5 16:20:14 2013 +0200
checks/*: Check for symlinks before opening files
Signed-off-by: Niels Thykier <niels@thykier.net>
diff --git a/checks/cruft b/checks/cruft
index ece468f..a9fe63d 100644
--- a/checks/cruft
+++ b/checks/cruft
@@ -29,7 +29,7 @@ use warnings;
use Lintian::Data;
use Lintian::Relation ();
use Lintian::Tags qw(tag);
-use Lintian::Util qw(fail);
+use Lintian::Util qw(fail is_ancestor_of);
use Cwd;
use File::Find;
@@ -350,6 +350,10 @@ sub find_cruft {
}
}
-f or return; # we just need normal files for the rest
+ if (-l && !is_ancestor_of($info->unpacked, $_)) {
+ # skip unsafe symlinks too
+ return;
+ }
unless ($warned->{$name}) {
for my $rule (@file_checks) {
diff --git a/checks/debhelper b/checks/debhelper
index 4e2b960..0173588 100644
--- a/checks/debhelper
+++ b/checks/debhelper
@@ -25,7 +25,7 @@ use warnings;
use Lintian::Data;
use Lintian::Relation;
use Lintian::Tags qw(tag);
-use Lintian::Util qw(fail slurp_entire_file);
+use Lintian::Util qw(fail is_ancestor_of slurp_entire_file);
# If compat is less than or equal to this, then a missing version
# for this level is only a pedantic issue.
@@ -88,6 +88,11 @@ my $seen_dh = 0;
my $seen_python_helper = 0;
my $seen_python3_helper = 0;
+if ( ! -f "$droot/rules" or !is_ancestor_of($droot, "$droot/rules")) {
+ # unsafe symlink
+ return;
+}
+
open(RULES, '<', "$droot/rules") or fail("cannot read debian/rules: $!");
while (<RULES>) {
@@ -297,6 +302,7 @@ foreach my $file (sort readdir(DEBIAN)) {
next if $file eq 'rules' or not -f "$droot/$file";
if ($file =~ m/^(?:(.*)\.)?(?:post|pre)(?:inst|rm)$/) {
next unless $needtomodifyscripts;
+ next unless is_ancestor_of($droot, "$droot/$file");
# They need to have #DEBHELPER# in their scripts. Search for scripts
# that look like maintainer scripts and make sure the token is there.
@@ -338,6 +344,7 @@ foreach my $file (sort readdir(DEBIAN)) {
# Check whether this is a debhelper config file that takes a list of
# filenames.
if ($filename_configs->known($base)) {
+ next unless is_ancestor_of($droot, "$droot/$file");
if ($level < 9) {
# debhelper only use executable files in compat 9
_tag_if_executable ($file, "$droot/$file");
diff --git a/checks/infofiles b/checks/infofiles
index 5a1a254..3d8266c 100644
--- a/checks/infofiles
+++ b/checks/infofiles
@@ -79,6 +79,10 @@ foreach my $file ($info->sorted_index) {
# If this is the main info file (no numeric extension). make sure it has
# appropriate dir entry information.
if ($fname !~ /-\d+\.gz/ && $file_info =~ /gzip compressed data/) {
+ if ($index_info->is_symlink && !is_ancestor_of($info->unpacked, $file)) {
+ # unsafe symlink, skip
+ next;
+ }
my $pid = open INFO, '-|';
if (not defined $pid) {
fail("cannot fork: $!");
diff --git a/checks/init.d b/checks/init.d
index abab5df..84d8776 100644
--- a/checks/init.d
+++ b/checks/init.d
@@ -22,6 +22,8 @@ package Lintian::init_d;
use strict;
use warnings;
+use File::Basename qw(dirname);
+
use Lintian::Check qw($PKGNAME_REGEX);
use Lintian::Tags qw(tag);
use Lintian::Util qw(fail);
@@ -243,6 +245,11 @@ sub check_init {
if ($target =~ m,(?:\A|/)lib/init/upstart-job\z,) {
return;
}
+ if (!is_ancestor_of(dirname($initd_file), $initd_file)) {
+ # unsafe symlink, skip. NB: dirname($initd_file) is safe
+ # because coll/init.d does sanity checking for us.
+ return;
+ }
}
open(IN, '<', $initd_file)
or fail("cannot open init.d file $initd_file: $!");
diff --git a/checks/menu-format b/checks/menu-format
index fcee4e9..b947840 100644
--- a/checks/menu-format
+++ b/checks/menu-format
@@ -37,7 +37,7 @@ use strict;
use warnings;
use Lintian::Tags qw(tag);
-use Lintian::Util qw(fail);
+use Lintian::Util qw(fail is_ancestor_of);
use File::Basename;
@@ -373,6 +373,7 @@ foreach my $menufile (@menufiles) {
$fullname = "usr/lib/menu/$basename" if $menufile =~ m,^menu/lib/,o;
next if $basename eq 'README'; # README is a special case
+ next if !is_ancestor_of('menu', $menufile);
my $menufile_line ='';
open (IN, '<', $menufile) or
@@ -655,6 +656,7 @@ sub VerifyIcon {
}
# Try the explicit location, and if that fails, try the standard path.
+ my $pkgroot = $info->unpacked;
my $iconfile = $info->unpacked($icon);
if (! -f $iconfile) {
$iconfile = $info->unpacked("/usr/share/pixmaps/$icon");
@@ -662,6 +664,7 @@ sub VerifyIcon {
my $ginfo = $group->info;
foreach my $depproc (@{ $ginfo->direct_dependencies ($proc) }) {
my $dinfo = $depproc->info;
+ $pkgroot = $dinfo->unpacked;
$iconfile = $dinfo->unpacked($icon);
last if -f $iconfile;
$iconfile = $info->unpacked("/usr/share/pixmaps/$icon");
@@ -670,11 +673,19 @@ sub VerifyIcon {
}
}
- if (! open (IN, '<', $iconfile)) {
+ # Last stat is a -f from above, reuse it
+ if (-e _) {
+ if (!is_ancestor_of($pkgroot, $iconfile)) {
+ # unsafe symlink
+ return;
+ }
+ } else {
tag 'menu-icon-missing', $icon;
return;
}
+ open (IN, '<', $iconfile) or fail "open $iconfile: $!";
+
my $parse = 'XPM header';
my $line;
do { defined ($line = <IN>) or goto parse_error; }
diff --git a/checks/menus b/checks/menus
index 9803a71..6fa0781 100644
--- a/checks/menus
+++ b/checks/menus
@@ -180,7 +180,8 @@ if ($docbase_file) {
my $dbfile;
while (defined ($dbfile = readdir DOCBASEDIR)) {
# don't try to parse executables, plus we already warned about it
- next if -x "doc-base/$dbfile";
+ # - skip symlinks as well, unlikely to be used for real doc-base files.
+ next if -x "doc-base/$dbfile" or -l "doc-base/$dbfile";
check_doc_base_file($dbfile, $pkg, $type, \%all_files, \%all_links);
}
closedir DOCBASEDIR;
diff --git a/checks/patch-systems b/checks/patch-systems
index f492c16..ec1c5fe 100644
--- a/checks/patch-systems
+++ b/checks/patch-systems
@@ -24,7 +24,7 @@ use strict;
use warnings;
use Lintian::Tags qw(tag);
-use Lintian::Util qw(fail);
+use Lintian::Util qw(fail is_ancestor_of);
use Cwd qw(realpath);
sub run {
@@ -43,18 +43,27 @@ sub run {
}
my $quilt_format = ($format =~ /3\.\d+ \(quilt\)/) ? 1 : 0;
- my $droot = realpath($info->debfiles);
+ my $droot = $info->debfiles;
my $dpdir = "$droot/patches";
+ if (!is_ancestor_of($droot, $dpdir)) {
+ # Bad symlink
+ return;
+ }
#----- dpatch
if ($build_deps->implies("dpatch")) {
$uses_patch_system++;
#check for a debian/patches file:
- if (! -r "$dpdir/00list") {
+ if (-l "$dpdir/00list" and not is_ancestor_of($droot, "$dpdir/00list")) {
+ # skip
+ } elsif (! -r "$dpdir/00list") {
tag 'dpatch-build-dep-but-no-patch-list';
} else {
my $list_uses_cpp = 0;
- if (open(OPTS, '<', "$dpdir/00options")) {
+ if (-f "$dpdir/00options"
+ && is_ancestor_of($droot, "$dpdir/00options")) {
+ open(OPTS, '<', "$dpdir/00options")
+ or fail "open 00options: $!";
while(<OPTS>) {
if (/DPATCH_OPTION_CPP=1/) {
$list_uses_cpp = 1;
@@ -65,7 +74,9 @@ sub run {
}
foreach my $listfile (glob("$dpdir/00list*")) {
my @patches;
- if (open(IN, '<', $listfile)) {
+ if (-f $listfile and is_ancestor_of($droot, $listfile)) {
+ open(IN, '<', $listfile)
+ or fail "open $listfile: $!";
while(<IN>) {
chomp;
next if (/^\#/); #ignore comments or CPP directive
@@ -86,9 +97,8 @@ sub run {
$patch_file .= '.dpatch' if -e "$dpdir/$patch_file.dpatch"
and not -e "$dpdir/$patch_file";
next if ( -l "$dpdir/$patch_file" );
- unless (realpath("$dpdir/$patch_file") =~ m,^\Q$droot/,) {
- next;
- }
+ next unless is_ancestor_of($droot, "$dpdir/$patch_file");
+
if (! -r "$dpdir/$patch_file") {
tag 'dpatch-index-references-non-existent-patch', $patch_file;
next;
@@ -120,7 +130,9 @@ sub run {
tag "unneeded-build-dep-on-quilt";
}
#check for a debian/patches file:
- if (! -r "$dpdir/series") {
+ if (-l "$dpdir/series" and not is_ancestor_of($droot, "$dpdir/series")) {
+ # skip
+ } elsif (! -r "$dpdir/series") {
tag 'quilt-build-dep-but-no-series-file' unless $quilt_format;
} else {
if (open(IN, '<', "$dpdir/series")) {
@@ -146,9 +158,8 @@ sub run {
# Check each patch.
foreach my $patch_file (@patches) {
next if ( -l "$dpdir/$patch_file" );
- unless (realpath("$dpdir/$patch_file") =~ m,^\Q$droot\E/,) {
- next;
- }
+ next unless is_ancestor_of($droot, "$dpdir/$patch_file");
+
if (! -r "$dpdir/$patch_file") {
tag 'quilt-series-references-non-existent-patch', $patch_file;
next;
@@ -174,22 +185,43 @@ sub run {
if ($quilt_format) { # 3.0 (quilt) specific checks
# Format 3.0 packages may generate a debian-changes-$version patch
my $version = $info->field('version');
- if (-f "$dpdir/debian-changes-$version" &&
- ! -f "$droot/source/patch-header") {
- tag 'format-3.0-but-debian-changes-patch';
+ my $versioned_patch = "$dpdir/debian-changes-$version";
+ my $patch_header = "$droot/source/patch-header";
+ my $ok = 1;
+ if (-l "$droot/source" or -l $patch_header) {
+ # possible issue
+ if (!is_ancenstor_of($droot, $patch_header)) {
+ $ok = 0;
+ }
+ }
+ # $dpdir is known to be safe at this point, so only check
+ # the patch itself
+ if ($ok and -l $versioned_patch) {
+ if (!is_ancenstor_of($droot, $versioned_patch)) {
+ $ok = 0;
+ }
+ }
+ if ($ok && -f $versioned_patch && ! -f $patch_header) {
+ tag 'format-3.0-but-debian-changes-patch';
}
}
} else {
if (-r "$dpdir/series" and
-f "$dpdir/series") {
# 3.0 (quilt) sources don't need quilt as dpkg-source will do the work
- tag 'quilt-series-but-no-build-dep' unless $quilt_format;
+ if (! -l "$dpdir/series" or is_ancestor_of($droot, "$dpdir/series")) {
+ tag 'quilt-series-but-no-build-dep' unless $quilt_format;
+ }
}
}
#----- look for README.source
if ($uses_patch_system && ! $quilt_format && ! -f "$droot/README.source") {
- tag "patch-system-but-no-source-readme";
+ if (! -l "$droot/README.source") {
+ # tag, unless README.source was a symlink (which could be a
+ # traversal attempt)
+ tag 'patch-system-but-no-source-readme';
+ }
}
#----- general cruft checking:
diff --git a/checks/po-debconf b/checks/po-debconf
index a21deac..89c0633 100644
--- a/checks/po-debconf
+++ b/checks/po-debconf
@@ -23,7 +23,7 @@ use strict;
use warnings;
use Lintian::Tags qw(tag);
-use Lintian::Util qw(fail system_env);
+use Lintian::Util qw(fail is_ancestor_of system_env);
sub run {
@@ -74,6 +74,10 @@ closedir(DEB);
#TODO: check whether all templates are named in TEMPLATES.pot
if ( $has_template ) {
+ if ( -l "$debfiles/po" and !is_ancestor_of($debfiles, "$debfiles/po")) {
+ # debian/po is an unsafe symlink - lets stop here.
+ return;
+ }
if ( ! -d "$debfiles/po" ) {
tag 'not-using-po-debconf';
return 0;
@@ -90,7 +94,7 @@ for (@lang_templates) {
my $missing_files = 0;
-if ( -f "$debfiles/po/POTFILES.in") {
+if ( -f "$debfiles/po/POTFILES.in" and ! -l "$debfiles/po/POTFILES.in") {
open(POTFILES, '<', "$debfiles/po/POTFILES.in")
or fail("Can't open debfiles/po/POTFILES.in.");
while (<POTFILES>) {
@@ -109,7 +113,7 @@ if ( -f "$debfiles/po/POTFILES.in") {
tag 'missing-potfiles-in';
$missing_files = 1;
}
-if (! -f "$debfiles/po/templates.pot") {
+if (! -f "$debfiles/po/templates.pot" and ! -l "$debfiles/po/templates.pot") {
tag 'missing-templates-pot';
$missing_files = 1;
}
@@ -137,6 +141,8 @@ while (defined(my $file=readdir(DEBIAN))) {
unless ($file =~ /^[a-z]{2,3}(_[A-Z]{2})?(?:\@[^\.]+)?\.po$/o);
local ($/) = "\n\n";
$_ = '';
+ # skip suspicious "files"
+ next if -l "$debfiles/po/$file" or ! -f "$debfiles/po/$file";
open(PO, '<', "$debfiles/po/$file")
or fail("Can't open debfiles/po/$file file.");
while (<PO>) {
diff --git a/checks/rules b/checks/rules
index e02d7a6..e5f66eb 100644
--- a/checks/rules
+++ b/checks/rules
@@ -19,7 +19,7 @@ use warnings;
use Lintian::Data;
use Lintian::Tags qw(tag);
-use Lintian::Util qw(fail);
+use Lintian::Util qw(fail is_ancestor_of);
our $PYTHON_DEPEND = 'python | python-dev | python-all | python-all-dev';
our $PYTHON3_DEPEND = 'python3 | python3-dev | python3-all | python3-all-dev';
@@ -118,7 +118,7 @@ my $rules = $info->debfiles('rules');
# all the tests if we then can't read it.
if (-l $rules) {
tag 'debian-rules-is-symlink';
- return 0 unless -f $rules;
+ return 0 unless -f $rules and is_ancestor_of($info->debfiles, $rules);
}
my $architecture = $info->field('architecture') || '';
diff --git a/checks/shared-libs b/checks/shared-libs
index f42b834..6eb63b1 100644
--- a/checks/shared-libs
+++ b/checks/shared-libs
@@ -289,7 +289,9 @@ for (keys %SONAME) {
}
@shlibs = grep { !$unversioned_shlibs{$_} } keys %SONAME;
-if ($#shlibs == -1) {
+if (-l $shlibsf) {
+ # control files are not symlinks, skip this part.
+} elsif ($#shlibs == -1) {
# no shared libraries included in package, thus shlibs control file should
# not be present
if (-f $shlibsf) {
@@ -369,7 +371,9 @@ if ($#shlibs == -1) {
# 5th step: check symbols control file. Add back in the unversioned shared
# libraries, since they can still have symbols files.
-if ($#shlibs == -1 and not %unversioned_shlibs) {
+if (-l $symbolsf) {
+ # control files are not symlinks, skip this part.
+} elsif ($#shlibs == -1 and not %unversioned_shlibs) {
# no shared libraries included in package, thus symbols control file should
# not be present
if (-f $symbolsf) {
diff --git a/checks/watch-file b/checks/watch-file
index 7f1d26b..9618c31 100644
--- a/checks/watch-file
+++ b/checks/watch-file
@@ -27,7 +27,7 @@ use warnings;
use Lintian::Collect;
use Lintian::Tags qw(tag);
-use Lintian::Util qw(fail);
+use Lintian::Util qw(fail is_ancestor_of);
sub run {
@@ -38,6 +38,10 @@ my $info = shift;
my $template = 0;
my $wfile = $info->debfiles('watch');
+if (-l $wfile) {
+ return unless is_ancestor_of($info->debfiles, $wfile);
+}
+
if (! -f $wfile) {
tag 'debian-watch-file-is-missing' unless ($info->native);
return;
diff --git a/debian/changelog b/debian/changelog
index dd6fb51..ee918df 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,5 +1,7 @@
lintian (2.5.10.5) unstable; urgency=low
+ * checks/*:
+ + [NT] Avoid following unsafe symlinks.
* checks/debconf:
+ [NT] Fix several path traversal issues that could leak
information about the host system.
--
Debian package checker
Reply to: