Reject ORIGDEST on forwarded rules and mixed-family lists
This commit is contained in:
@@ -423,6 +423,10 @@ func (c *Compiler) compileZonePair(state *FirewallState, tag, srcZone, srcAddr,
|
||||
srcIfaces := c.resolveZoneInterfaces(srcZone)
|
||||
dstIfaces := c.resolveZoneInterfaces(dstZone)
|
||||
chain := c.selectChain(srcZone, dstZone, fwZone)
|
||||
// ponytail: forward daddr is post-DNAT; lift with `ct original daddr` (expr.Ct Direction, google/nftables v0.3.0).
|
||||
if origDest != "" && chain == "forward" {
|
||||
return fmt.Errorf("origdest: ORIGDEST on forwarded rules is not supported yet")
|
||||
}
|
||||
|
||||
for _, srcIface := range srcIfaces {
|
||||
for _, dstIface := range dstIfaces {
|
||||
@@ -430,7 +434,6 @@ func (c *Compiler) compileZonePair(state *FirewallState, tag, srcZone, srcAddr,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// ponytail: matches daddr, so a forward rule never sees a pre-DNAT address; needs `ct original daddr` (google/nftables Ct.Direction, >v0.2.0).
|
||||
if origDest != "" {
|
||||
od, err := matchOrigDest(origDest)
|
||||
if err != nil {
|
||||
@@ -1410,11 +1413,17 @@ func matchDestCIDR(cidr string) ([]expr.Any, error) {
|
||||
|
||||
// matchOrigDest guards the daddr match with the address's nfproto so it is family-correct in the inet table.
|
||||
func matchOrigDest(addr string) ([]expr.Any, error) {
|
||||
first, _, _ := strings.Cut(strings.TrimPrefix(addr, "!"), ",")
|
||||
first, _, _ = strings.Cut(first, "/")
|
||||
proto := byte(unix.NFPROTO_IPV6)
|
||||
if ip := net.ParseIP(first); ip != nil && ip.To4() != nil {
|
||||
proto = unix.NFPROTO_IPV4
|
||||
var proto byte
|
||||
for i, a := range strings.Split(strings.TrimPrefix(addr, "!"), ",") {
|
||||
a, _, _ = strings.Cut(a, "/")
|
||||
p := byte(unix.NFPROTO_IPV6)
|
||||
if ip := net.ParseIP(a); ip != nil && ip.To4() != nil {
|
||||
p = unix.NFPROTO_IPV4
|
||||
}
|
||||
if i > 0 && p != proto {
|
||||
return nil, fmt.Errorf("%q mixes IPv4 and IPv6 addresses", addr)
|
||||
}
|
||||
proto = p
|
||||
}
|
||||
dst, err := matchDestCIDR(addr)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,6 +3,7 @@ package nftables
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -1781,12 +1782,12 @@ func describeRule(r ManagedRule) string {
|
||||
parts = append(parts, "oif="+strings.TrimRight(string(cmp.Data), "\x00"))
|
||||
}
|
||||
case *expr.Payload:
|
||||
if m.Base == expr.PayloadBaseNetworkHeader && m.Len == 4 {
|
||||
name := map[uint32]string{12: "saddr", 16: "daddr"}[m.Offset]
|
||||
if m.Base == expr.PayloadBaseNetworkHeader && (m.Len == 4 || m.Len == 16) {
|
||||
name := map[uint32]string{12: "saddr", 16: "daddr", 8: "saddr", 24: "daddr"}[m.Offset]
|
||||
if cmp.Op == expr.CmpOpNeq {
|
||||
name = "!" + name
|
||||
}
|
||||
parts = append(parts, fmt.Sprintf("%s=%d.%d.%d.%d", name, cmp.Data[0], cmp.Data[1], cmp.Data[2], cmp.Data[3]))
|
||||
parts = append(parts, name+"="+net.IP(cmp.Data).String())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1870,6 +1871,11 @@ func TestCompile_CommaZoneLists(t *testing.T) {
|
||||
rule: config.Rule{Action: config.RuleAccept, Source: "net", Dest: "fw", Proto: "tcp", DPort: config.PortSpec{"22"}, OrigDest: "203.0.113.5"},
|
||||
want: map[string][]string{"input": {"iif=eth0 daddr=203.0.113.5"}},
|
||||
},
|
||||
{
|
||||
name: "accept ipv6 origdest",
|
||||
rule: config.Rule{Action: config.RuleAccept, Source: "net", Dest: "fw", Proto: "tcp", DPort: config.PortSpec{"22"}, OrigDest: "2001:db8::5"},
|
||||
want: map[string][]string{"input": {"iif=eth0 daddr=2001:db8::5"}},
|
||||
},
|
||||
{
|
||||
name: "blrule zone list",
|
||||
blrule: &config.BlruleRule{Action: config.BlruleDrop, Source: "net,anycast", Dest: "fw"},
|
||||
@@ -2290,4 +2296,21 @@ func TestMatchOrigDest_FamilyGuard(t *testing.T) {
|
||||
t.Errorf("%s: missing nfproto %d guard: %v", addr, want, e[:2])
|
||||
}
|
||||
}
|
||||
if _, err := matchOrigDest("!203.0.113.5,2001:db8::5"); err == nil {
|
||||
t.Error("mixed IPv4/IPv6 origdest: want error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompile_OrigDestForwardRejected(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
Settings: config.Settings{TableName: "test", AddressFamily: config.FamilyINET},
|
||||
Zones: map[string]config.Zone{"fw": {Type: config.ZoneFirewall}, "net": {Type: config.ZoneIP}, "svr": {Type: config.ZoneIP}},
|
||||
Interfaces: []config.Interface{{Zone: "net", Interface: "eth0"}, {Zone: "svr", Interface: "eth2"}},
|
||||
Rules: []config.Rule{{Action: config.RuleAccept, Source: "net", Dest: "svr", Proto: "tcp", DPort: config.PortSpec{"80"}, OrigDest: "203.0.113.5"}},
|
||||
PortGroups: make(map[string]config.PortGroup),
|
||||
}
|
||||
_, err := NewCompiler(cfg).Compile()
|
||||
if err == nil || !strings.Contains(err.Error(), "not supported yet") {
|
||||
t.Fatalf("Compile() error = %v, want forwarded ORIGDEST rejection", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user