Finish filtering implementation

Signed-off-by: Tom Eastep <teastep@shorewall.net>
This commit is contained in:
Tom Eastep 2011-05-26 13:38:44 -07:00
parent 6c3163cc27
commit 0287d96aa2
13 changed files with 236 additions and 90 deletions

View File

@ -226,6 +226,7 @@ our $VERSION = '4.4_20';
# restricted => Logical OR of restrictions of rules in this chain.
# restriction => Restrictions on further rules in this chain.
# audit => Audit the result.
# filtered => Number of filter rules at the front of an interface forward chain
# } ,
# <chain2> => ...
# }
@ -834,6 +835,12 @@ sub move_rules( $$ ) {
my $count = @{$chain1->{rules}};
my $tableref = $chain_table{$chain1->{table}};
my $blacklist = $chain2->{blacklist};
my $filtered;
my $filtered1 = $chain1->{filtered};
my $filtered2 = $chain2->{filtered};
my @filtered1;
my @filtered2;
my $rule;
assert( ! $chain1->{blacklist} );
#
@ -844,9 +851,20 @@ sub move_rules( $$ ) {
for ( @{$chain1->{rules}} ) {
adjust_reference_counts( $tableref->{$1}, $name1, $name2 ) if / -[jg] ([^\s]+)/;
}
#
# We set aside the filtered rules for the time being
#
$filtered = $filtered1;
push @filtered1 , shift @{$chain1->{rules}} while $filtered--;
$chain1->{filtered} = 0;
$filtered = $filtered2;
push @filtered2 , shift @{$chain2->{rules}} while $filtered--;
if ( $debug ) {
my $rule = $blacklist;
my $rule = $blacklist + $filtered2;
trace( $chain2, 'A', ++$rule, $_ ) for @{$chain1->{rules}};
}
@ -866,6 +884,25 @@ sub move_rules( $$ ) {
shift @{$rules} while @{$rules} > 1 && $rules->[0] eq $rules->[1];
}
#
# Now insert the filter rules at the head of the chain (before blacklist rules)
#
if ( $filtered1 ) {
if ( $debug ) {
$rule = $filtered2;
$filtered = 0;
trace( $chain2, 'I', ++$rule, $filtered1[$filtered++] ) while $filtered < $filtered1;
}
splice @{$rules}, 0, 0, @filtered1;
}
splice @{$rules}, 0, 0, @filtered2;
$chain2->{filtered} = $filtered1 + $filtered2;
delete_chain $chain1;
$count;
@ -1206,7 +1243,9 @@ sub new_chain($$)
log => 1,
cmdlevel => 0,
references => {},
blacklist => 0 };
blacklist => 0,
filtered => 0
};
trace( $chainref, 'N', undef, '' ) if $debug;
@ -4275,11 +4314,11 @@ sub promote_blacklist_rules() {
# rule to the head of one of those chains
$copied++;
#
# Copy the blacklist rule to the head of the parent chain unless it
# already has a blacklist rule.
# Copy the blacklist rule to the head of the parent chain (after any
# filter rules) unless it already has a blacklist rule.
#
unless ( $chain2ref->{blacklist} ) {
unshift @{$chain2ref->{rules}}, $rule;
splice @{$chain2ref->{rules}}, $chain2ref->{filtered}, 0, $rule;
add_reference $chain2ref, $chainbref;
$chain2ref->{blacklist} = 1;
}

View File

@ -763,10 +763,6 @@ sub compiler {
#
setup_mac_lists 1;
#
# Prevent Hairpins on non-routeback interfaces
#
prevent_hairpins;
#
# Process the rules file.
#
process_rules;

View File

@ -447,7 +447,7 @@ sub initialize( $ ) {
LOG_MARTIANS => undef,
LOG_VERBOSITY => undef,
STARTUP_LOG => undef,
ROUTEBACK_LOG_LEVEL => undef,
FILTER_LOG_LEVEL => undef,
#
# Location of Files
#
@ -551,7 +551,7 @@ sub initialize( $ ) {
TCP_FLAGS_DISPOSITION => undef,
BLACKLIST_DISPOSITION => undef,
SMURF_DISPOSITION => undef,
ROUTEBACK_DISPOSITION => undef,
FILTER_DISPOSITION => undef,
#
# Mark Geometry
#
@ -3379,12 +3379,13 @@ sub get_configuration( $ ) {
default_log_level 'SMURF_LOG_LEVEL', '';
default_log_level 'LOGALLNEW', '';
default_log_level 'ROUTEBACK_LOG_LEVEL', 'info';
default_log_level 'FILTER_LOG_LEVEL', 'info';
if ( $val = $config{ROUTEBACK_DISPOSITION} ) {
fatal_error "Invalid ROUTEBACK_DISPOSITION setting ($val)" unless $val =~ /^(?:A_)?DROP$/;
if ( $val = $config{FILTER_DISPOSITION} ) {
fatal_error "Invalid FILTER_DISPOSITION setting ($val)" unless $val =~ /^(A_)?(DROP|REJECT)$/;
require_capability 'AUDIT_TARGET' , "FILTER_DISPOSITION=$val", 's' if $1;
} else {
$config{ROUTEBACK_DISPOSITION} = 'DROP';
$config{FILTER_DISPOSITION} = 'DROP';
}
if ( $val = $config{MACLIST_DISPOSITION} ) {

View File

@ -469,27 +469,65 @@ sub add_common_rules() {
my $rule;
my $list;
my $chain;
my $dynamicref;
my $state = $config{BLACKLISTNEWONLY} ? $globals{UNTRACKED} ? "-m state --state NEW,INVALID,UNTRACKED " : "$globals{STATEMATCH} NEW,INVALID " : '';
my $state = $config{BLACKLISTNEWONLY} ? $globals{UNTRACKED} ? "$globals{STATEMATCH} NEW,INVALID,UNTRACKED " : "$globals{STATEMATCH} NEW,INVALID " : '';
my $level = $config{BLACKLIST_LOGLEVEL};
my $rejectref = $filter_table->{reject};
if ( $config{DYNAMIC_BLACKLIST} ) {
add_rule_pair dont_delete( new_standard_chain( 'logdrop' ) ), ' ' , 'DROP' , $level ;
add_rule_pair dont_delete( new_standard_chain( 'logreject' ) ), ' ' , 'reject' , $level ;
$chainref = dont_optimize( new_standard_chain( 'dynamic' ) );
add_jump $filter_table->{$_}, $chainref, 0, $state for qw( INPUT FORWARD );
add_commands( $chainref, '[ -f ${VARDIR}/.dynamic ] && cat ${VARDIR}/.dynamic >&3' );
$dynamicref = dont_optimize( new_standard_chain( 'dynamic' ) );
add_jump $filter_table->{INPUT}, $dynamicref, 0, $state;
add_commands( $dynamicref, '[ -f ${VARDIR}/.dynamic ] && cat ${VARDIR}/.dynamic >&3' );
}
setup_mss;
if ( $config{FASTACCEPT} ) {
add_rule( $filter_table->{$_} , "$globals{STATEMATCH} ESTABLISHED,RELATED -j ACCEPT" ) for qw( INPUT FORWARD OUTPUT );
add_rule( $filter_table->{$_} , "$globals{STATEMATCH} ESTABLISHED,RELATED -j ACCEPT" ) for qw( INPUT OUTPUT );
}
my $policy = $config{FILTER_DISPOSITION};
$level = $config{FILTER_LOG_LEVEL};
my $audit = $policy =~ s/^A_//;
if ( $level || $audit ) {
$chainref = new_standard_chain 'filter';
log_rule $level , $chainref , $policy , '' if $level ne '';
add_rule( $chainref, '-j AUDIT --type ' . lc $policy ) if $audit;
add_jump $chainref, $policy eq 'REJECT' ? 'reject' : $policy , 1;
$target = 'filter';
} elsif ( ( $target = $policy ) eq 'REJECT' ) {
$target = 'reject';
}
for $interface ( grep $_ ne '%vserver%', all_interfaces ) {
ensure_chain( 'filter', $_ ) for first_chains( $interface ), output_chain( $interface );
my $interfaceref = find_interface $interface;
unless ( $interfaceref->{options}{ignore} ) {
my @filters = @{$interfaceref->{filter}};
$chainref = $filter_table->{forward_chain $interface};
if ( @filters ) {
add_jump( $chainref , $target, 1, match_source_net( $_ ) ), $chainref->{filtered}++ for @filters;
} elsif ( $interfaceref->{bridge} eq $interface ) {
add_jump( $chainref , $target, 1, match_dest_dev( $interface ) ), $chainref->{filtered}++ unless $interfaceref->{options}{routeback} || $interfaceref->{options}{routefilter};
}
add_rule( $chainref, "$globals{STATEMATCH} ESTABLISHED,RELATED -j ACCEPT" ), $chainref->{filtered}++ if $config{FASTACCEPT};
add_jump( $chainref, $dynamicref, 0, $state ), $chainref->{filtered}++ if $dynamicref;
}
}
run_user_exit1 'initdone';
@ -1175,7 +1213,7 @@ sub generate_matrix() {
my @vservers = vserver_zones;
my $notrackref = $raw_table->{notrack_chain $fw};
my $state = $config{BLACKLISTNEWONLY} ? $globals{UNTRACKED} ? "-m state --state NEW,INVALID,UNTRACKED " : "$globals{STATEMATCH} NEW,INVALID " : '';
my $state = $config{BLACKLISTNEWONLY} ? $globals{UNTRACKED} ? "$globals{STATEMATCH} NEW,INVALID,UNTRACKED " : "$globals{STATEMATCH} NEW,INVALID " : '';
my $interface_jumps_added = 0;
our %input_jump_added = ();

View File

@ -49,7 +49,6 @@ our @EXPORT = qw(
process_actions
process_rules
verify_audit
prevent_hairpins
);
our @EXPORT_OK = qw( initialize );
@ -1184,50 +1183,6 @@ sub require_audit($$) {
return ensure_audit_chain $target, $action;
}
#
# Generate rules to prevent hairpins
#
sub prevent_hairpins() {
my $loglevel = $config{ROUTEBACK_LOG_LEVEL};
my $target = $config{ROUTEBACK_DISPOSITION};
my $audit = $target eq 'A_DROP';
require_capability 'AUDIT_TARGET' , 'ROUTEBACK_DISPOSITION=A_DROP', 's' if $audit;
if ( $loglevel ) {
my $chainref = new_standard_chain 'routeback';
log_rule $loglevel , $chainref , $target, '';
if ( $audit ) {
if ( $config{FAKE_AUDIT} ) {
add_jump( $chainref, 'AUDIT', 0, '-m comment --comment "--type drop"' , 0 );
} else {
add_rule $chainref, 'AUDIT --type drop';
}
}
add_jump $chainref, $target, 1;
$target = $chainref;
} else {
$target = require_audit( 'DROP', $audit ? 'audit' : '' );
}
for my $interface (all_interfaces) {
my $interfaceref = find_interface( $interface );
if ( $interfaceref->{bridge} eq $interface ) {
#
# It is not possible to block these attempts on a bridge :-(
#
add_jump( $filter_table->{forward_chain $interface},
$target,
1,
match_dest_dev( $interface ) )
unless $interfaceref->{optiones}{routefilter} || $interfaceref->{options}{routeback} || $interfaceref->{options}{ignore};
}
}
}
#
# The following small functions generate rules for the builtin actions of the same name
#

View File

@ -153,7 +153,7 @@ my %reservedName = ( all => 1,
# zone => <zone name>
# multizone => undef|1 #More than one zone interfaces through this interface
# nets => <number of nets in interface/hosts records referring to this interface>
# bridge => <bridge>
# bridge => <bridge name>
# ports => <number of port on this bridge>
# ipsec => undef|1 # Has an ipsec host group
# broadcasts => 'none', 'detect' or [ <addr1>, <addr2>, ... ]
@ -245,6 +245,7 @@ sub initialize( $ ) {
bridge => SIMPLE_IF_OPTION,
detectnets => OBSOLETE_IF_OPTION,
dhcp => SIMPLE_IF_OPTION,
filter => IPLIST_IF_OPTION,
maclist => SIMPLE_IF_OPTION + IF_OPTION_HOST,
logmartians => BINARY_IF_OPTION,
nets => IPLIST_IF_OPTION + IF_OPTION_ZONEONLY + IF_OPTION_VSERVER,
@ -277,6 +278,7 @@ sub initialize( $ ) {
%validinterfaceoptions = ( blacklist => SIMPLE_IF_OPTION + IF_OPTION_HOST,
bridge => SIMPLE_IF_OPTION,
dhcp => SIMPLE_IF_OPTION,
filter => IPLIST_IF_OPTION,
maclist => SIMPLE_IF_OPTION + IF_OPTION_HOST,
nets => IPLIST_IF_OPTION + IF_OPTION_ZONEONLY + IF_OPTION_VSERVER,
nosmurfs => SIMPLE_IF_OPTION + IF_OPTION_HOST,
@ -865,6 +867,7 @@ sub chain_base($) {
sub process_interface( $$ ) {
my ( $nextinum, $export ) = @_;
my $netsref = '';
my $filterref = [];
my ($zone, $originalinterface, $bcasts, $options ) = split_line 2, 4, 'interfaces file';
my $zoneref;
my $bridge = '';
@ -1055,6 +1058,12 @@ sub process_interface( $$ ) {
# Assume 'broadcast'
#
$hostoptions{broadcast} = 1;
} elsif ( $option eq 'filter' ) {
warning_message "filter is ineffective with FASTACCEPT=Yes" if $config{FASTACCEPT};
$filterref = [ split_list $value, 'address' ];
validate_net( $_, 1) for @{$filterref}
} else {
assert(0);
}
@ -1102,6 +1111,7 @@ sub process_interface( $$ ) {
$physical{$physical} = $interfaces{$interface} = { name => $interface ,
bridge => $bridge ,
filter => $filterref ,
nets => 0 ,
number => $nextinum ,
root => $root ,

View File

@ -45,7 +45,7 @@ SMURF_LOG_LEVEL=info
LOG_MARTIANS=Yes
ROUTEBACK_LOG_LEVEL=info
FILTER_LOG_LEVEL=info
###############################################################################
# L O C A T I O N O F F I L E S A N D D I R E C T O R I E S
@ -214,6 +214,6 @@ TCP_FLAGS_DISPOSITION=DROP
SMURF_DISPOSITION=DROP
ROUTEBACK_DISPOSITION=DROP
FILTER_DISPOSITION=DROP
#LAST LINE -- DO NOT REMOVE

View File

@ -19,32 +19,57 @@ VI. PROBLEMS CORRECTED AND NEW FEATURES IN PRIOR RELEASES
did not specify a number. Now, the compiler selects the lowest
unallocated number when no device number is explicitly allocated.
2) Eric Leblond, creator of NuFW, has discovered an exploit that
allows locally-connected hosts to poke holes in the firewall. The
known ways to protect against the exploit are:
2) Network developers have discovered an exploit that allows hosts to
poke holes in the firewall. The known ways to protect against the
exploit are:
a) rt_filter (Shorewall's routefilter). Only applicable to IPv4
and can't be used with some multi-ISP configurations.
a) rt_filter (Shorewall's routefilter). Only applicable to IPv4.
b) Insert a DROP rule that prevents hairpinning (routeback). The rule
must be inserted before any RELATED firewall rules. This
approach is not appropriate for bridges.
must be inserted before any ESTABLISHED,RELATED firewall rules.
This approach is not appropriate for bridges and other cases,
where the 'routeback' option is specified or implied.
To deal with this issue, Shorewall will insert a hairpin-prevention
rule for each interface that has none of these options:
For non-bridges, Shorewall will insert a hairpin rule, provided
that the following options are not specified:
- ignore
- routeback
- routefilter
- routeback
To control logging and auditing of these DROP operations, two new
options are added to shorewall.conf (shorewall6.conf):
The rule will handle hairpins according to the setting of two new
options in shorewall.conf and shorewall6.conf:
- ROUTEBACK_LOG_LEVEL - Default is 'info'. If you don't want these
DROPs logged, set ROUTEBACK_LOG_LEVEL=none
FILTER_LOG_LEVEL specifies the logging level; default is 'info'.
To omit logging, specify FILTER_LOG_LEVEL=none.
- ROUTEBACK_AUDIT - Defaults to 'No'; 'Yes' causes auditing.
FILTER_DISPOSITION specifies the disposition. Default is DROP and
the possible values are DROP, A_DROP, REJECT and A_REJECT.
This leaves IPv6 bridges still unprotected unless each of its ports
is described as bridge ports in /etc/shorewall/interfaces.
To deal with bridges and other routeback interfaces , there is now
a 'filter' option in /shorewall/interfaces and
/etc/shorewall6/interfaces.
The value of the 'filter' option is a list of addresses enclosed in
in parentheses. Where only a single address is listed, the
parentheses may be deleted. When a packet from a filtered address
is received on the interface, it is handled based on the new
options described above.
For each bridge, you should list all of your other local networks
(those networks not attached to the bridge) in the bridge's filter
list.
Example:
My DMZ is 2001:470:b:227::40/124
My local interface (br1) is a bridge.
In /etc/shorewall6/interfaces, I have:
#ZONE INTERFACE BROADCAST OPTIONS
loc br1 - filter=2001:470:b:227::40/124
----------------------------------------------------------------------------
I I. K N O W N P R O B L E M S R E M A I N I N G

View File

@ -42,7 +42,7 @@ TCP_FLAGS_LOG_LEVEL=info
SMURF_LOG_LEVEL=info
ROUTEBACK_LOG_LEVEL=info
FILTER_LOG_LEVEL=info
###############################################################################
# L O C A T I O N O F F I L E S A N D D I R E C T O R I E S
@ -175,6 +175,6 @@ TCP_FLAGS_DISPOSITION=DROP
SMURF_DISPOSITION=DROP
ROUTEBACK_DISPOSITION=DROP
FILTER_DISPOSITION=DROP
#LAST LINE -- DO NOT REMOVE

View File

@ -1603,6 +1603,21 @@ teastep@ursa:~$ </programlisting>The first number determines the maximum log
option</ulink>.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>filter</term>
<listitem>
<para>On systems running Shorewall 4.4.20 or later, either the
packet matched the <option>filter</option> <ulink
url="manpages/shorewall-interfaces.html">interface option</ulink>
or it is being routed out of the same interface on which it
arrived and the interface does not have the
<option>routeback</option> <ulink
url="manpages/shorewall-interfaces.html">interface
option</ulink>.</para>
</listitem>
</varlistentry>
</variablelist>
<example id="Example3">

View File

@ -312,6 +312,18 @@ loc eth2 -</programlisting>
</listitem>
</varlistentry>
<varlistentry>
<term>filter=(<emphasis>net</emphasis>[,...])</term>
<listitem>
<para>Added in Shorewall 4.4.20. This option should be used on
bridges or other interfaces with the
<option>routeback</option> option. On these interfaces, it
should list those local networks that are not routed out of
the bridge or interface.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><emphasis
role="bold">logmartians[={0|1}]</emphasis></term>
@ -518,6 +530,10 @@ loc eth2 -</programlisting>
required when you have used a wildcard in the INTERFACE column
if you want to allow traffic between the interfaces that match
the wildcard.</para>
<para>Beginning with Shorewall 4.4.20, if you specify this
option, then you should also specify <option>filter</option>;
see above.</para>
</listitem>
</varlistentry>

View File

@ -621,6 +621,41 @@ net all DROP info</programlisting>then the chain name is 'net2all'
</listitem>
</varlistentry>
<varlistentry>
<term><emphasis role="bold">FILTER_DISPOSITION=</emphasis>[<emphasis
role="bold">DROP</emphasis>|<emphasis
role="bold">REJECT</emphasis>|A_DROP|A_REJECT]</term>
<listitem>
<para>Added in Shorewall 4.4.20. Determines the disposition of
packets matching the <option>filter</option> option (see <ulink
url="shorewall-interfaces.html">shorewall-interfaces</ulink>(5)) and
of <firstterm>hairpin</firstterm> packets on interfaces without the
<option>routeback</option> option.<footnote>
<para>Hairpin packets are packets that are routed out of the
same interface that they arrived on.</para>
</footnote> interfaces without the routeback option.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><emphasis
role="bold">FILTER_LOG_LEVEL=</emphasis><emphasis>log-level</emphasis></term>
<listitem>
<para>Added on Shorewall 4.4.20. Determines the logging of packets
matching the <option>filter</option> option (see <ulink
url="shorewall-interfaces.html">shorewall-interfaces</ulink>(5)) and
of <firstterm>hairpin</firstterm> packets on interfaces without the
<option>routeback</option> option.<footnote>
<para>Hairpin packets are packets that are routed out of the
same interface that they arrived on.</para>
</footnote> interfaces without the routeback option. The default
is <option>info</option>. If you don't wish for these packets to be
logged, use FILTER_LOG_LEVEL=none.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><emphasis role="bold">FORWARD_CLEAR_MARK=</emphasis>{<emphasis
role="bold">Yes</emphasis>|<emphasis role="bold">No</emphasis>}</term>

View File

@ -204,6 +204,18 @@ loc eth2 -</programlisting>
</listitem>
</varlistentry>
<varlistentry>
<term>filter=(<emphasis>net</emphasis>[,...])</term>
<listitem>
<para>Added in Shorewall 4.4.20. This option should be used on
bridges or other interfaces with the
<option>routeback</option> option. On these interfaces, it
should list those local networks that are not routed out of
the bridge or interface.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><emphasis role="bold">forward</emphasis>[={0|1}]</term>
@ -304,6 +316,10 @@ loc eth2 -</programlisting>
required when you have used a wildcard in the INTERFACE column
if you want to allow traffic between the interfaces that match
the wildcard.</para>
<para>Beginning with Shorewall 4.4.20, if you specify this
option, then you should also specify <option>filter</option>;
see above.</para>
</listitem>
</varlistentry>