util/linuxfw: fix nil pointer panic in connmark rules without IPv6 (#18946)
When IPv6 is unavailable on a system, AddConnmarkSaveRule() and
DelConnmarkSaveRule() would panic with a nil pointer dereference.
Both methods directly iterated over []iptablesInterface{i.ipt4, i.ipt6}
without checking if ipt6 was nil.
Use `getTables()` instead to properly retrieve the available tables
on a given system
Fixes #3310
Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
This commit is contained in:
+10
-8
@@ -26,13 +26,15 @@ type fakeRule struct {
|
|||||||
func newFakeIPTables() *fakeIPTables {
|
func newFakeIPTables() *fakeIPTables {
|
||||||
return &fakeIPTables{
|
return &fakeIPTables{
|
||||||
n: map[string][]string{
|
n: map[string][]string{
|
||||||
"filter/INPUT": nil,
|
"filter/INPUT": nil,
|
||||||
"filter/OUTPUT": nil,
|
"filter/OUTPUT": nil,
|
||||||
"filter/FORWARD": nil,
|
"filter/FORWARD": nil,
|
||||||
"nat/PREROUTING": nil,
|
"nat/PREROUTING": nil,
|
||||||
"nat/OUTPUT": nil,
|
"nat/OUTPUT": nil,
|
||||||
"nat/POSTROUTING": nil,
|
"nat/POSTROUTING": nil,
|
||||||
"mangle/FORWARD": nil,
|
"mangle/FORWARD": nil,
|
||||||
|
"mangle/PREROUTING": nil,
|
||||||
|
"mangle/OUTPUT": nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,7 +82,7 @@ func (n *fakeIPTables) Delete(table, chain string, args ...string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmt.Errorf("delete of unknown rule %q from %s", strings.Join(args, " "), k)
|
return errors.New("exitcode:1")
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("unknown table/chain %s", k)
|
return fmt.Errorf("unknown table/chain %s", k)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -533,7 +533,7 @@ func (i *iptablesRunner) DelStatefulRule(tunname string) error {
|
|||||||
// proper routing table lookups for exit nodes and subnet routers.
|
// proper routing table lookups for exit nodes and subnet routers.
|
||||||
func (i *iptablesRunner) AddConnmarkSaveRule() error {
|
func (i *iptablesRunner) AddConnmarkSaveRule() error {
|
||||||
// Check if rules already exist (idempotency)
|
// Check if rules already exist (idempotency)
|
||||||
for _, ipt := range []iptablesInterface{i.ipt4, i.ipt6} {
|
for _, ipt := range i.getTables() {
|
||||||
rules, err := ipt.List("mangle", "PREROUTING")
|
rules, err := ipt.List("mangle", "PREROUTING")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
@@ -551,7 +551,7 @@ func (i *iptablesRunner) AddConnmarkSaveRule() error {
|
|||||||
|
|
||||||
// mangle/PREROUTING: Restore mark from conntrack for ESTABLISHED/RELATED connections
|
// mangle/PREROUTING: Restore mark from conntrack for ESTABLISHED/RELATED connections
|
||||||
// This runs BEFORE routing decision and rp_filter check
|
// This runs BEFORE routing decision and rp_filter check
|
||||||
for _, ipt := range []iptablesInterface{i.ipt4, i.ipt6} {
|
for _, ipt := range i.getTables() {
|
||||||
args := []string{
|
args := []string{
|
||||||
"-m", "conntrack",
|
"-m", "conntrack",
|
||||||
"--ctstate", "ESTABLISHED,RELATED",
|
"--ctstate", "ESTABLISHED,RELATED",
|
||||||
@@ -566,7 +566,7 @@ func (i *iptablesRunner) AddConnmarkSaveRule() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mangle/OUTPUT: Save mark to conntrack for NEW connections with non-zero marks
|
// mangle/OUTPUT: Save mark to conntrack for NEW connections with non-zero marks
|
||||||
for _, ipt := range []iptablesInterface{i.ipt4, i.ipt6} {
|
for _, ipt := range i.getTables() {
|
||||||
args := []string{
|
args := []string{
|
||||||
"-m", "conntrack",
|
"-m", "conntrack",
|
||||||
"--ctstate", "NEW",
|
"--ctstate", "NEW",
|
||||||
@@ -587,7 +587,7 @@ func (i *iptablesRunner) AddConnmarkSaveRule() error {
|
|||||||
|
|
||||||
// DelConnmarkSaveRule removes conntrack marking rules added by AddConnmarkSaveRule.
|
// DelConnmarkSaveRule removes conntrack marking rules added by AddConnmarkSaveRule.
|
||||||
func (i *iptablesRunner) DelConnmarkSaveRule() error {
|
func (i *iptablesRunner) DelConnmarkSaveRule() error {
|
||||||
for _, ipt := range []iptablesInterface{i.ipt4, i.ipt6} {
|
for _, ipt := range i.getTables() {
|
||||||
// Delete PREROUTING rule
|
// Delete PREROUTING rule
|
||||||
args := []string{
|
args := []string{
|
||||||
"-m", "conntrack",
|
"-m", "conntrack",
|
||||||
|
|||||||
@@ -364,3 +364,143 @@ func checkSNATRuleCount(t *testing.T, iptr *iptablesRunner, ip netip.Addr, wants
|
|||||||
t.Fatalf("wants %d rules, got %d", wantsRules, len(rules))
|
t.Fatalf("wants %d rules, got %d", wantsRules, len(rules))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddAndDelConnmarkSaveRule(t *testing.T) {
|
||||||
|
preroutingArgs := []string{
|
||||||
|
"-m", "conntrack",
|
||||||
|
"--ctstate", "ESTABLISHED,RELATED",
|
||||||
|
"-j", "CONNMARK",
|
||||||
|
"--restore-mark",
|
||||||
|
"--nfmask", "0xff0000",
|
||||||
|
"--ctmask", "0xff0000",
|
||||||
|
}
|
||||||
|
|
||||||
|
outputArgs := []string{
|
||||||
|
"-m", "conntrack",
|
||||||
|
"--ctstate", "NEW",
|
||||||
|
"-m", "mark",
|
||||||
|
"!", "--mark", "0x0/0xff0000",
|
||||||
|
"-j", "CONNMARK",
|
||||||
|
"--save-mark",
|
||||||
|
"--nfmask", "0xff0000",
|
||||||
|
"--ctmask", "0xff0000",
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("with_ipv6", func(t *testing.T) {
|
||||||
|
iptr := newFakeIPTablesRunner()
|
||||||
|
|
||||||
|
// Add connmark rules
|
||||||
|
if err := iptr.AddConnmarkSaveRule(); err != nil {
|
||||||
|
t.Fatalf("AddConnmarkSaveRule failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify rules exist in both IPv4 and IPv6
|
||||||
|
for _, proto := range []iptablesInterface{iptr.ipt4, iptr.ipt6} {
|
||||||
|
if exists, err := proto.Exists("mangle", "PREROUTING", preroutingArgs...); err != nil {
|
||||||
|
t.Fatalf("error checking PREROUTING rule: %v", err)
|
||||||
|
} else if !exists {
|
||||||
|
t.Errorf("PREROUTING connmark rule doesn't exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists, err := proto.Exists("mangle", "OUTPUT", outputArgs...); err != nil {
|
||||||
|
t.Fatalf("error checking OUTPUT rule: %v", err)
|
||||||
|
} else if !exists {
|
||||||
|
t.Errorf("OUTPUT connmark rule doesn't exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test idempotency - calling AddConnmarkSaveRule again should not fail or duplicate
|
||||||
|
if err := iptr.AddConnmarkSaveRule(); err != nil {
|
||||||
|
t.Fatalf("AddConnmarkSaveRule (second call) failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify rules still exist and weren't duplicated
|
||||||
|
for _, proto := range []iptablesInterface{iptr.ipt4, iptr.ipt6} {
|
||||||
|
preroutingRules, err := proto.List("mangle", "PREROUTING")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error listing PREROUTING rules: %v", err)
|
||||||
|
}
|
||||||
|
connmarkCount := 0
|
||||||
|
for _, rule := range preroutingRules {
|
||||||
|
if strings.Contains(rule, "CONNMARK") && strings.Contains(rule, "restore-mark") {
|
||||||
|
connmarkCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if connmarkCount != 1 {
|
||||||
|
t.Errorf("expected 1 PREROUTING connmark rule, got %d", connmarkCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete connmark rules
|
||||||
|
if err := iptr.DelConnmarkSaveRule(); err != nil {
|
||||||
|
t.Fatalf("DelConnmarkSaveRule failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify rules are deleted
|
||||||
|
for _, proto := range []iptablesInterface{iptr.ipt4, iptr.ipt6} {
|
||||||
|
if exists, err := proto.Exists("mangle", "PREROUTING", preroutingArgs...); err != nil {
|
||||||
|
t.Fatalf("error checking PREROUTING rule: %v", err)
|
||||||
|
} else if exists {
|
||||||
|
t.Errorf("PREROUTING connmark rule still exists after deletion")
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists, err := proto.Exists("mangle", "OUTPUT", outputArgs...); err != nil {
|
||||||
|
t.Fatalf("error checking OUTPUT rule: %v", err)
|
||||||
|
} else if exists {
|
||||||
|
t.Errorf("OUTPUT connmark rule still exists after deletion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test idempotency of deletion
|
||||||
|
if err := iptr.DelConnmarkSaveRule(); err != nil {
|
||||||
|
t.Fatalf("DelConnmarkSaveRule (second call) failed: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without_ipv6", func(t *testing.T) {
|
||||||
|
// Create an iptables runner with only IPv4 (simulating system without IPv6)
|
||||||
|
iptr := &iptablesRunner{
|
||||||
|
ipt4: newFakeIPTables(),
|
||||||
|
ipt6: nil, // IPv6 not available
|
||||||
|
v6Available: false,
|
||||||
|
v6NATAvailable: false,
|
||||||
|
v6FilterAvailable: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add connmark rules should NOT panic with nil ipt6
|
||||||
|
if err := iptr.AddConnmarkSaveRule(); err != nil {
|
||||||
|
t.Fatalf("AddConnmarkSaveRule failed with IPv6 disabled: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify rules exist ONLY in IPv4
|
||||||
|
if exists, err := iptr.ipt4.Exists("mangle", "PREROUTING", preroutingArgs...); err != nil {
|
||||||
|
t.Fatalf("error checking IPv4 PREROUTING rule: %v", err)
|
||||||
|
} else if !exists {
|
||||||
|
t.Errorf("IPv4 PREROUTING connmark rule doesn't exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists, err := iptr.ipt4.Exists("mangle", "OUTPUT", outputArgs...); err != nil {
|
||||||
|
t.Fatalf("error checking IPv4 OUTPUT rule: %v", err)
|
||||||
|
} else if !exists {
|
||||||
|
t.Errorf("IPv4 OUTPUT connmark rule doesn't exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete connmark rules should NOT panic with nil ipt6
|
||||||
|
if err := iptr.DelConnmarkSaveRule(); err != nil {
|
||||||
|
t.Fatalf("DelConnmarkSaveRule failed with IPv6 disabled: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify rules are deleted from IPv4
|
||||||
|
if exists, err := iptr.ipt4.Exists("mangle", "PREROUTING", preroutingArgs...); err != nil {
|
||||||
|
t.Fatalf("error checking IPv4 PREROUTING rule: %v", err)
|
||||||
|
} else if exists {
|
||||||
|
t.Errorf("IPv4 PREROUTING connmark rule still exists after deletion")
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists, err := iptr.ipt4.Exists("mangle", "OUTPUT", outputArgs...); err != nil {
|
||||||
|
t.Fatalf("error checking IPv4 OUTPUT rule: %v", err)
|
||||||
|
} else if exists {
|
||||||
|
t.Errorf("IPv4 OUTPUT connmark rule still exists after deletion")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user