You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by jm...@apache.org on 2007/06/11 22:15:16 UTC
svn commit: r546250 - in /spamassassin/trunk: spamd/ t/
Author: jm
Date: Mon Jun 11 13:15:14 2007
New Revision: 546250
URL: http://svn.apache.org/viewvc?view=rev&rev=546250
Log:
bug 5480: fix CVE-2007-2873, a local user symlink-attack DoS
vulnerability in spamd when '--allow-tell' is used. Full details at
http://spamassassin.apache.org/advisories/cve-2007-2873.txt
Added:
spamassassin/trunk/t/root_spamd.t
spamassassin/trunk/t/root_spamd_tell.t
spamassassin/trunk/t/root_spamd_tell_paranoid.t
spamassassin/trunk/t/root_spamd_tell_x.t
spamassassin/trunk/t/root_spamd_tell_x_paranoid.t
spamassassin/trunk/t/root_spamd_x.t
spamassassin/trunk/t/root_spamd_x_paranoid.t
Modified:
spamassassin/trunk/spamd/spamd.raw
spamassassin/trunk/t/SATest.pm
spamassassin/trunk/t/config.dist
spamassassin/trunk/t/spamd_allow_user_rules.t
Modified: spamassassin/trunk/spamd/spamd.raw
URL: http://svn.apache.org/viewvc/spamassassin/trunk/spamd/spamd.raw?view=diff&rev=546250&r1=546249&r2=546250
==============================================================================
--- spamassassin/trunk/spamd/spamd.raw (original)
+++ spamassassin/trunk/spamd/spamd.raw Mon Jun 11 13:15:14 2007
@@ -489,14 +489,28 @@
# support setuid() to user unless:
# run with -u
# we're not root
-# doing --vpopmail
+# doing --vpopmail or --virtual-config-dir
# we disable user-config
my $setuid_to_user = (
$opt{'username'} ||
$> != 0 ||
$opt{'vpopmail'} ||
- (!$opt{'user-config'} && !($opt{'setuid-with-sql'}||$opt{'setuid-with-ldap'}))
- ) ? 0 : 1;
+ $opt{'virtual-config-dir'}
+ ) ? 0 : 1;
+
+dbg("spamd: will perform setuids? $setuid_to_user");
+
+if ( $opt{'vpopmail'} ) {
+ if ( !$opt{'username'} ) {
+ die "spamd: cannot use --vpopmail without -u\n";
+ }
+}
+
+if ( $opt{'virtual-config-dir'} ) {
+ if ( !$opt{'username'} ) {
+ die "spamd: cannot use --virtual-config-dir without -u\n";
+ }
+}
# always copy the config, later code may disable
my $copy_config_p = 1;
@@ -1463,18 +1477,8 @@
$compress_zlib = $hdrs->{compress_zlib};
}
- handle_setuid_to_user if ($setuid_to_user && $> == 0);
-
- if ( $opt{'sql-config'} && !defined($current_user) ) {
- unless ( handle_user_sql('nobody') ) {
- service_unavailable_error("Error fetching user preferences via SQL");
- return 0;
- }
- }
-
- if ( $opt{'ldap-config'} && !defined($current_user) ) {
- handle_user_ldap('nobody');
- }
+ return 0 unless do_user_handling();
+ if ($> == 0) { die "spamd: still running as root! dying"; }
my $resp = "EX_OK";
@@ -1669,6 +1673,9 @@
my $expected_length = $hdrs->{expected_length};
my $compress_zlib = $hdrs->{compress_zlib};
+ return 0 unless do_user_handling();
+ if ($> == 0) { die "spamd: still running as root! dying"; }
+
if (!$opt{tell}) {
service_unavailable_error("TELL commands have not been enabled.");
return 0;
@@ -1684,8 +1691,6 @@
return 0;
}
- &handle_setuid_to_user if ($setuid_to_user && $> == 0);
-
if ($opt{'sql-config'} && !defined($current_user)) {
unless (handle_user_sql('nobody')) {
service_unavailable_error("Error fetching user preferences via SQL");
@@ -1789,6 +1794,26 @@
###########################################################################
+sub do_user_handling {
+ if ($setuid_to_user && $> == 0) {
+ handle_setuid_to_user();
+ }
+
+ if ( $opt{'sql-config'} && !defined($current_user) ) {
+ unless ( handle_user_sql('nobody') ) {
+ service_unavailable_error("Error fetching user preferences via SQL");
+ return 0;
+ }
+ }
+
+ if ( $opt{'ldap-config'} && !defined($current_user) ) {
+ handle_user_ldap('nobody');
+ }
+
+ dbg ("spamd: running as uid $>");
+ return 1;
+}
+
# generalised header parser.
sub parse_headers {
my ($hdrs, $client) = @_;
@@ -1891,9 +1916,12 @@
handle_user_setuid_with_ldap($current_user);
$setuid_to_user = 1; # as above
}
+ else {
+ handle_user_setuid_basic($current_user);
+ }
}
else {
- handle_user($current_user);
+ handle_user_setuid_basic($current_user);
if ( $opt{'sql-config'} ) {
unless ( handle_user_sql($current_user) ) {
service_unavailable_error("Error fetching user preferences via SQL");
@@ -2016,7 +2044,7 @@
return 1;
}
-sub handle_user {
+sub handle_user_setuid_basic {
my $username = shift;
#
@@ -2055,6 +2083,14 @@
}
}
+ if ($opt{'user-config'}) {
+ handle_user_set_user_prefs($dir, $username);
+ }
+}
+
+sub handle_user_set_user_prefs {
+ my ($dir, $username) = @_;
+
#
# If vpopmail config enabled then set $dir to virtual homedir
#
@@ -2076,33 +2112,14 @@
}
my $cf_file = $dir . "/.spamassassin/user_prefs";
- #
- # If vpopmail config enabled then pass virtual homedir onto create_default_cf_needed
- #
- if ( $opt{'vpopmail'} ) {
- if ( !$opt{'username'} ) {
- warn "spamd: cannot use vpopmail without -u\n";
+ create_default_cf_if_needed( $cf_file, $username, $dir );
+ $spamtest->read_scoreonly_config($cf_file);
+ $spamtest->signal_user_changed(
+ {
+ username => $username,
+ user_dir => $dir
}
- create_default_cf_if_needed( $cf_file, $username, $dir );
- $spamtest->read_scoreonly_config($cf_file);
- $spamtest->signal_user_changed(
- {
- username => $username,
- user_dir => $dir
- }
- );
-
- }
- else {
- create_default_cf_if_needed( $cf_file, $username, $dir );
- $spamtest->read_scoreonly_config($cf_file);
- $spamtest->signal_user_changed(
- {
- username => $username,
- user_dir => $dir
- }
- );
- }
+ );
return 1;
}
@@ -2788,6 +2805,8 @@
The pattern B<must> expand to an absolute directory when spamd is running
daemonized (B<-d>).
+Currently, use of this without B<-u> is not supported. This inhibits setuid.
+
=item B<-r> I<pidfile>, B<--pidfile>=I<pidfile>
Write the process ID of the spamd parent to the file specified by I<pidfile>.
@@ -2801,7 +2820,7 @@
maildir. This option is useful for vpopmail virtual users who do not have an
entry in the system /etc/passwd file.
-Currently, use of this without B<-u> is not supported.
+Currently, use of this without B<-u> is not supported. This inhibits setuid.
=item B<-s> I<facility>, B<--syslog>=I<facility>
Modified: spamassassin/trunk/t/SATest.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/SATest.pm?view=diff&rev=546250&r1=546249&r2=546250
==============================================================================
--- spamassassin/trunk/t/SATest.pm (original)
+++ spamassassin/trunk/t/SATest.pm Mon Jun 11 13:15:14 2007
@@ -146,6 +146,8 @@
$ENV{'TEST_DIR'} = $cwd;
$testname = $tname;
+
+ $current_user = (getpwuid($>))[0];
}
# a port number between 32768 and 65535; used to allow multiple test
Modified: spamassassin/trunk/t/config.dist
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/config.dist?view=diff&rev=546250&r1=546249&r2=546250
==============================================================================
--- spamassassin/trunk/t/config.dist (original)
+++ spamassassin/trunk/t/config.dist Mon Jun 11 13:15:14 2007
@@ -57,3 +57,9 @@
# depending on changes in the third-party modules we import.
run_saw_ampersand_test=n
+# ---------------------------------------------------------------------------
+
+# The "root_*.t" tests require root privileges, and may create files in
+# the filesystem as part of the test. Disabled by default.
+run_root_tests=n
+
Added: spamassassin/trunk/t/root_spamd.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/root_spamd.t?view=auto&rev=546250
==============================================================================
--- spamassassin/trunk/t/root_spamd.t (added)
+++ spamassassin/trunk/t/root_spamd.t Mon Jun 11 13:15:14 2007
@@ -0,0 +1,48 @@
+#!/usr/bin/perl
+
+# run with: sudo prove -v t/root_spamd*
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("root_spamd");
+use Test;
+
+use constant TEST_ENABLED => conf_bool('run_root_tests');
+use constant IS_ROOT => eval { ($> == 0); };
+use constant RUN_TESTS => (TEST_ENABLED && IS_ROOT);
+
+BEGIN { plan tests => (RUN_TESTS ? 14 : 0) };
+exit unless RUN_TESTS;
+
+# ---------------------------------------------------------------------------
+
+%patterns = (
+
+q{ Return-Path: sb55sb55@yahoo.com}, 'firstline',
+q{ Subject: There yours for FREE!}, 'subj',
+q{ X-Spam-Status: Yes, score=}, 'status',
+q{ X-Spam-Flag: YES}, 'flag',
+q{ X-Spam-Level: **********}, 'stars',
+q{ TEST_ENDSNUMS}, 'endsinnums',
+q{ TEST_NOREALNAME}, 'noreal',
+q{ This must be the very last line}, 'lastline',
+
+);
+
+# run spamc as unpriv uid
+$spamc = "sudo -u nobody $spamc";
+
+ok(start_spamd("-L"));
+
+ok(spamcrun("< data/spam/001", \&patterns_run_cb));
+ok_all_patterns();
+
+%patterns = (
+q{ X-Spam-Status: Yes, score=}, 'status',
+q{ X-Spam-Flag: YES}, 'flag',
+ );
+
+
+ok (spamcrun("< data/spam/018", \&patterns_run_cb));
+ok_all_patterns();
+
+ok(stop_spamd());
Added: spamassassin/trunk/t/root_spamd_tell.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/root_spamd_tell.t?view=auto&rev=546250
==============================================================================
--- spamassassin/trunk/t/root_spamd_tell.t (added)
+++ spamassassin/trunk/t/root_spamd_tell.t Mon Jun 11 13:15:14 2007
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("root_spamd_tell");
+use Test;
+
+use constant TEST_ENABLED => conf_bool('run_root_tests');
+use constant IS_ROOT => eval { ($> == 0); };
+use constant RUN_TESTS => (TEST_ENABLED && IS_ROOT);
+
+BEGIN { plan tests => (RUN_TESTS ? 6 : 0) };
+exit unless RUN_TESTS;
+
+# ---------------------------------------------------------------------------
+
+%patterns = (
+q{ Message successfully } => 'learned',
+);
+
+# run spamc as unpriv uid
+$spamc = "sudo -u nobody $spamc";
+
+# remove these first
+unlink('log/user_state/bayes_seen');
+unlink('log/user_state/bayes_toks');
+
+# ensure it is writable by all
+use File::Path; mkpath("log/user_state"); chmod 01777, "log/user_state";
+
+ok(start_spamd("-L --allow-tell"));
+
+ok(spamcrun("-lx -L ham < data/spam/001", \&patterns_run_cb));
+ok_all_patterns();
+
+ok(stop_spamd());
+
+# ensure these are not owned by root
+ok check_owner('log/user_state/bayes_seen');
+ok check_owner('log/user_state/bayes_toks');
+
+sub check_owner {
+ my $f = shift;
+ my @stat = stat $f;
+
+ print "stat($f) = ".join(', ', @stat)."\n";
+
+ if (!defined $stat[1]) {
+ warn "no stat for $f";
+ return 0;
+ }
+ elsif ($stat[4] == 0) {
+ warn "stat for $f: owner is root";
+ return 0;
+ }
+ else {
+ return 1;
+ }
+}
Added: spamassassin/trunk/t/root_spamd_tell_paranoid.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/root_spamd_tell_paranoid.t?view=auto&rev=546250
==============================================================================
--- spamassassin/trunk/t/root_spamd_tell_paranoid.t (added)
+++ spamassassin/trunk/t/root_spamd_tell_paranoid.t Mon Jun 11 13:15:14 2007
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("root_spamd_tell_paranoid");
+use Test;
+
+use constant TEST_ENABLED => conf_bool('run_root_tests');
+use constant IS_ROOT => eval { ($> == 0); };
+use constant RUN_TESTS => (TEST_ENABLED && IS_ROOT);
+
+BEGIN { plan tests => (RUN_TESTS ? 6 : 0) };
+exit unless RUN_TESTS;
+
+# ---------------------------------------------------------------------------
+
+%patterns = (
+q{ Message successfully } => 'learned',
+);
+
+# run spamc as unpriv uid
+$spamc = "sudo -u nobody $spamc";
+
+# remove these first
+unlink('log/user_state/bayes_seen');
+unlink('log/user_state/bayes_toks');
+
+# ensure it is writable by all
+use File::Path; mkpath("log/user_state"); chmod 01777, "log/user_state";
+
+ok(start_spamd("-L --allow-tell --paranoid"));
+
+ok(spamcrun("-lx -L ham < data/spam/001", \&patterns_run_cb));
+ok_all_patterns();
+
+ok(stop_spamd());
+
+# ensure these are not owned by root
+ok check_owner('log/user_state/bayes_seen');
+ok check_owner('log/user_state/bayes_toks');
+
+sub check_owner {
+ my $f = shift;
+ my @stat = stat $f;
+
+ print "stat($f) = ".join(', ', @stat)."\n";
+
+ if (!defined $stat[1]) {
+ warn "no stat for $f";
+ return 0;
+ }
+ elsif ($stat[4] == 0) {
+ warn "stat for $f: owner is root";
+ return 0;
+ }
+ else {
+ return 1;
+ }
+}
Added: spamassassin/trunk/t/root_spamd_tell_x.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/root_spamd_tell_x.t?view=auto&rev=546250
==============================================================================
--- spamassassin/trunk/t/root_spamd_tell_x.t (added)
+++ spamassassin/trunk/t/root_spamd_tell_x.t Mon Jun 11 13:15:14 2007
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("root_spamd_tell_x");
+use Test;
+
+use constant TEST_ENABLED => conf_bool('run_root_tests');
+use constant IS_ROOT => eval { ($> == 0); };
+use constant RUN_TESTS => (TEST_ENABLED && IS_ROOT);
+
+BEGIN { plan tests => (RUN_TESTS ? 6 : 0) };
+exit unless RUN_TESTS;
+
+# ---------------------------------------------------------------------------
+
+%patterns = (
+q{ Message successfully } => 'learned',
+);
+
+# run spamc as unpriv uid
+$spamc = "sudo -u nobody $spamc";
+
+# remove these first
+unlink('log/user_state/bayes_seen');
+unlink('log/user_state/bayes_toks');
+
+# ensure it is writable by all
+use File::Path; mkpath("log/user_state"); chmod 01777, "log/user_state";
+
+ok(start_spamd("-L --allow-tell --create-prefs -x"));
+
+ok(spamcrun("-lx -L ham < data/spam/001", \&patterns_run_cb));
+ok_all_patterns();
+
+ok(stop_spamd());
+
+# ensure these are not owned by root
+ok check_owner('log/user_state/bayes_seen');
+ok check_owner('log/user_state/bayes_toks');
+
+sub check_owner {
+ my $f = shift;
+ my @stat = stat $f;
+
+ print "stat($f) = ".join(', ', @stat)."\n";
+
+ if (!defined $stat[1]) {
+ warn "no stat for $f";
+ return 0;
+ }
+ elsif ($stat[4] == 0) {
+ warn "stat for $f: owner is root";
+ return 0;
+ }
+ else {
+ return 1;
+ }
+}
Added: spamassassin/trunk/t/root_spamd_tell_x_paranoid.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/root_spamd_tell_x_paranoid.t?view=auto&rev=546250
==============================================================================
--- spamassassin/trunk/t/root_spamd_tell_x_paranoid.t (added)
+++ spamassassin/trunk/t/root_spamd_tell_x_paranoid.t Mon Jun 11 13:15:14 2007
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("root_spamd_tell_x_paranoid");
+use Test;
+
+use constant TEST_ENABLED => conf_bool('run_root_tests');
+use constant IS_ROOT => eval { ($> == 0); };
+use constant RUN_TESTS => (TEST_ENABLED && IS_ROOT);
+
+BEGIN { plan tests => (RUN_TESTS ? 6 : 0) };
+exit unless RUN_TESTS;
+
+# ---------------------------------------------------------------------------
+
+%patterns = (
+q{ Message successfully } => 'learned',
+);
+
+# run spamc as unpriv uid
+$spamc = "sudo -u nobody $spamc";
+
+# remove these first
+unlink('log/user_state/bayes_seen');
+unlink('log/user_state/bayes_toks');
+
+# ensure it is writable by all
+use File::Path; mkpath("log/user_state"); chmod 01777, "log/user_state";
+
+ok(start_spamd("-L --allow-tell --create-prefs -x --paranoid"));
+
+ok(spamcrun("-lx -L ham < data/spam/001", \&patterns_run_cb));
+ok_all_patterns();
+
+ok(stop_spamd());
+
+# ensure these are not owned by root
+ok check_owner('log/user_state/bayes_seen');
+ok check_owner('log/user_state/bayes_toks');
+
+sub check_owner {
+ my $f = shift;
+ my @stat = stat $f;
+
+ print "stat($f) = ".join(', ', @stat)."\n";
+
+ if (!defined $stat[1]) {
+ warn "no stat for $f";
+ return 0;
+ }
+ elsif ($stat[4] == 0) {
+ warn "stat for $f: owner is root";
+ return 0;
+ }
+ else {
+ return 1;
+ }
+}
Added: spamassassin/trunk/t/root_spamd_x.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/root_spamd_x.t?view=auto&rev=546250
==============================================================================
--- spamassassin/trunk/t/root_spamd_x.t (added)
+++ spamassassin/trunk/t/root_spamd_x.t Mon Jun 11 13:15:14 2007
@@ -0,0 +1,46 @@
+#!/usr/bin/perl
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("root_spamd_x");
+use Test;
+
+use constant TEST_ENABLED => conf_bool('run_root_tests');
+use constant IS_ROOT => eval { ($> == 0); };
+use constant RUN_TESTS => (TEST_ENABLED && IS_ROOT);
+
+BEGIN { plan tests => (RUN_TESTS ? 14 : 0) };
+exit unless RUN_TESTS;
+
+# ---------------------------------------------------------------------------
+
+%patterns = (
+
+q{ Return-Path: sb55sb55@yahoo.com}, 'firstline',
+q{ Subject: There yours for FREE!}, 'subj',
+q{ X-Spam-Status: Yes, score=}, 'status',
+q{ X-Spam-Flag: YES}, 'flag',
+q{ X-Spam-Level: **********}, 'stars',
+q{ TEST_ENDSNUMS}, 'endsinnums',
+q{ TEST_NOREALNAME}, 'noreal',
+q{ This must be the very last line}, 'lastline',
+
+);
+
+# run spamc as unpriv uid
+$spamc = "sudo -u nobody $spamc";
+
+ok(start_spamd("-L --create-prefs -x"));
+
+ok(spamcrun("< data/spam/001", \&patterns_run_cb));
+ok_all_patterns();
+
+%patterns = (
+q{ X-Spam-Status: Yes, score=}, 'status',
+q{ X-Spam-Flag: YES}, 'flag',
+ );
+
+
+ok (spamcrun("< data/spam/018", \&patterns_run_cb));
+ok_all_patterns();
+
+ok(stop_spamd());
Added: spamassassin/trunk/t/root_spamd_x_paranoid.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/root_spamd_x_paranoid.t?view=auto&rev=546250
==============================================================================
--- spamassassin/trunk/t/root_spamd_x_paranoid.t (added)
+++ spamassassin/trunk/t/root_spamd_x_paranoid.t Mon Jun 11 13:15:14 2007
@@ -0,0 +1,46 @@
+#!/usr/bin/perl
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("root_spamd_x_paranoid");
+use Test;
+
+use constant TEST_ENABLED => conf_bool('run_root_tests');
+use constant IS_ROOT => eval { ($> == 0); };
+use constant RUN_TESTS => (TEST_ENABLED && IS_ROOT);
+
+BEGIN { plan tests => (RUN_TESTS ? 14 : 0) };
+exit unless RUN_TESTS;
+
+# ---------------------------------------------------------------------------
+
+%patterns = (
+
+q{ Return-Path: sb55sb55@yahoo.com}, 'firstline',
+q{ Subject: There yours for FREE!}, 'subj',
+q{ X-Spam-Status: Yes, score=}, 'status',
+q{ X-Spam-Flag: YES}, 'flag',
+q{ X-Spam-Level: **********}, 'stars',
+q{ TEST_ENDSNUMS}, 'endsinnums',
+q{ TEST_NOREALNAME}, 'noreal',
+q{ This must be the very last line}, 'lastline',
+
+);
+
+# run spamc as unpriv uid
+$spamc = "sudo -u nobody $spamc";
+
+ok(start_spamd("-L --create-prefs -x --paranoid"));
+
+ok(spamcrun("< data/spam/001", \&patterns_run_cb));
+ok_all_patterns();
+
+%patterns = (
+q{ X-Spam-Status: Yes, score=}, 'status',
+q{ X-Spam-Flag: YES}, 'flag',
+ );
+
+
+ok (spamcrun("< data/spam/018", \&patterns_run_cb));
+ok_all_patterns();
+
+ok(stop_spamd());
Modified: spamassassin/trunk/t/spamd_allow_user_rules.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/spamd_allow_user_rules.t?view=diff&rev=546250&r1=546249&r2=546250
==============================================================================
--- spamassassin/trunk/t/spamd_allow_user_rules.t (original)
+++ spamassassin/trunk/t/spamd_allow_user_rules.t Mon Jun 11 13:15:14 2007
@@ -36,7 +36,7 @@
";
close OUT;
-ok (start_spamd ("--virtual-config-dir=log/virtualconfig/%u -L"));
+ok (start_spamd ("--virtual-config-dir=log/virtualconfig/%u -L -u $current_user"));
ok (spamcrun ("-u testuser < data/spam/009", \&patterns_run_cb));
ok (stop_spamd ());