diff --git a/Shorewall/Perl/Shorewall/Chains.pm b/Shorewall/Perl/Shorewall/Chains.pm index 7f86b1e9f..4a6f04b57 100644 --- a/Shorewall/Perl/Shorewall/Chains.pm +++ b/Shorewall/Perl/Shorewall/Chains.pm @@ -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 # } , # => ... # } @@ -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; } diff --git a/Shorewall/Perl/Shorewall/Compiler.pm b/Shorewall/Perl/Shorewall/Compiler.pm index 98a900b43..f23ddae02 100644 --- a/Shorewall/Perl/Shorewall/Compiler.pm +++ b/Shorewall/Perl/Shorewall/Compiler.pm @@ -763,10 +763,6 @@ sub compiler { # setup_mac_lists 1; # - # Prevent Hairpins on non-routeback interfaces - # - prevent_hairpins; - # # Process the rules file. # process_rules; diff --git a/Shorewall/Perl/Shorewall/Config.pm b/Shorewall/Perl/Shorewall/Config.pm index 6bea43b7d..f718c6303 100644 --- a/Shorewall/Perl/Shorewall/Config.pm +++ b/Shorewall/Perl/Shorewall/Config.pm @@ -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} ) { diff --git a/Shorewall/Perl/Shorewall/Misc.pm b/Shorewall/Perl/Shorewall/Misc.pm index 37d2bb35f..cfebcdd53 100644 --- a/Shorewall/Perl/Shorewall/Misc.pm +++ b/Shorewall/Perl/Shorewall/Misc.pm @@ -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 = (); diff --git a/Shorewall/Perl/Shorewall/Rules.pm b/Shorewall/Perl/Shorewall/Rules.pm index cbc2789d8..9b061dfa9 100644 --- a/Shorewall/Perl/Shorewall/Rules.pm +++ b/Shorewall/Perl/Shorewall/Rules.pm @@ -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 # diff --git a/Shorewall/Perl/Shorewall/Zones.pm b/Shorewall/Perl/Shorewall/Zones.pm index fc888f764..7f9606856 100644 --- a/Shorewall/Perl/Shorewall/Zones.pm +++ b/Shorewall/Perl/Shorewall/Zones.pm @@ -153,7 +153,7 @@ my %reservedName = ( all => 1, # zone => # multizone => undef|1 #More than one zone interfaces through this interface # nets => -# bridge => +# bridge => # ports => # ipsec => undef|1 # Has an ipsec host group # broadcasts => 'none', 'detect' or [ , , ... ] @@ -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, @@ -864,7 +866,8 @@ sub chain_base($) { # sub process_interface( $$ ) { my ( $nextinum, $export ) = @_; - my $netsref = ''; + 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 , diff --git a/Shorewall/configfiles/shorewall.conf b/Shorewall/configfiles/shorewall.conf index e2454f573..a7d116e76 100644 --- a/Shorewall/configfiles/shorewall.conf +++ b/Shorewall/configfiles/shorewall.conf @@ -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 diff --git a/Shorewall/releasenotes.txt b/Shorewall/releasenotes.txt index 5eb0520ff..1e2a73841 100644 --- a/Shorewall/releasenotes.txt +++ b/Shorewall/releasenotes.txt @@ -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 - - To control logging and auditing of these DROP operations, two new - options are added to shorewall.conf (shorewall6.conf): + - routeback - - ROUTEBACK_LOG_LEVEL - Default is 'info'. If you don't want these - DROPs logged, set ROUTEBACK_LOG_LEVEL=none + The rule will handle hairpins according to the setting of two new + options in shorewall.conf and shorewall6.conf: - - ROUTEBACK_AUDIT - Defaults to 'No'; 'Yes' causes auditing. + FILTER_LOG_LEVEL specifies the logging level; default is 'info'. + To omit logging, specify FILTER_LOG_LEVEL=none. - This leaves IPv6 bridges still unprotected unless each of its ports - is described as bridge ports in /etc/shorewall/interfaces. + FILTER_DISPOSITION specifies the disposition. Default is DROP and + the possible values are DROP, A_DROP, REJECT and A_REJECT. + + 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 diff --git a/Shorewall6/shorewall6.conf b/Shorewall6/shorewall6.conf index baadbfffc..f8d95fff9 100644 --- a/Shorewall6/shorewall6.conf +++ b/Shorewall6/shorewall6.conf @@ -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 diff --git a/docs/FAQ.xml b/docs/FAQ.xml index f88cab835..25dcb2ab7 100644 --- a/docs/FAQ.xml +++ b/docs/FAQ.xml @@ -1603,6 +1603,21 @@ teastep@ursa:~$ The first number determines the maximum log option. + + + filter + + + On systems running Shorewall 4.4.20 or later, either the + packet matched the interface option + or it is being routed out of the same interface on which it + arrived and the interface does not have the + interface + option. + + diff --git a/manpages/shorewall-interfaces.xml b/manpages/shorewall-interfaces.xml index 3818b5068..53668e132 100644 --- a/manpages/shorewall-interfaces.xml +++ b/manpages/shorewall-interfaces.xml @@ -312,6 +312,18 @@ loc eth2 - + + filter=(net[,...]) + + + Added in Shorewall 4.4.20. This option should be used on + bridges or other interfaces with the + option. On these interfaces, it + should list those local networks that are not routed out of + the bridge or interface. + + + logmartians[={0|1}] @@ -518,6 +530,10 @@ loc eth2 - required when you have used a wildcard in the INTERFACE column if you want to allow traffic between the interfaces that match the wildcard. + + Beginning with Shorewall 4.4.20, if you specify this + option, then you should also specify ; + see above. diff --git a/manpages/shorewall.conf.xml b/manpages/shorewall.conf.xml index 3480a1276..cd2b851fc 100644 --- a/manpages/shorewall.conf.xml +++ b/manpages/shorewall.conf.xml @@ -621,6 +621,41 @@ net all DROP infothen the chain name is 'net2all' + + FILTER_DISPOSITION=[DROP|REJECT|A_DROP|A_REJECT] + + + Added in Shorewall 4.4.20. Determines the disposition of + packets matching the option (see shorewall-interfaces(5)) and + of hairpin packets on interfaces without the + option. + Hairpin packets are packets that are routed out of the + same interface that they arrived on. + interfaces without the routeback option. + + + + + FILTER_LOG_LEVEL=log-level + + + Added on Shorewall 4.4.20. Determines the logging of packets + matching the option (see shorewall-interfaces(5)) and + of hairpin packets on interfaces without the + option. + Hairpin packets are packets that are routed out of the + same interface that they arrived on. + interfaces without the routeback option. The default + is . If you don't wish for these packets to be + logged, use FILTER_LOG_LEVEL=none. + + + FORWARD_CLEAR_MARK={Yes|No} diff --git a/manpages6/shorewall6-interfaces.xml b/manpages6/shorewall6-interfaces.xml index 1c20fb559..616eca5fa 100644 --- a/manpages6/shorewall6-interfaces.xml +++ b/manpages6/shorewall6-interfaces.xml @@ -204,6 +204,18 @@ loc eth2 - + + filter=(net[,...]) + + + Added in Shorewall 4.4.20. This option should be used on + bridges or other interfaces with the + option. On these interfaces, it + should list those local networks that are not routed out of + the bridge or interface. + + + forward[={0|1}] @@ -304,6 +316,10 @@ loc eth2 - required when you have used a wildcard in the INTERFACE column if you want to allow traffic between the interfaces that match the wildcard. + + Beginning with Shorewall 4.4.20, if you specify this + option, then you should also specify ; + see above.