浏览代码

fix(db): harden unrestricted freedom outbounds (#6184)

n0ctal 18 小时之前
父节点
当前提交
3bb87e80aa
共有 2 个文件被更改,包括 64 次插入1 次删除
  1. 12 1
      internal/database/db.go
  2. 52 0
      internal/database/freedom_finalrules_migration_test.go

+ 12 - 1
internal/database/db.go

@@ -1628,6 +1628,14 @@ func isLegacyPrivateOnlyFinalRules(v any) bool {
 	return true
 }
 
+func isUnrestrictedFreedomFinalRules(v any, present bool) bool {
+	if !present || v == nil {
+		return true
+	}
+	rules, ok := v.([]any)
+	return ok && len(rules) == 0
+}
+
 func hardenFreedomFinalRules() error {
 	var setting model.Setting
 	err := db.Model(model.Setting{}).Where("key = ?", "xrayTemplateConfig").First(&setting).Error
@@ -1680,7 +1688,10 @@ func rewriteFreedomFinalRulesPrivateEgress(raw string) (string, bool, error) {
 		if !ok {
 			continue
 		}
-		if !isAllowOnlyFinalRules(settings["finalRules"]) && !isLegacyPrivateOnlyFinalRules(settings["finalRules"]) {
+		finalRules, present := settings["finalRules"]
+		if !isUnrestrictedFreedomFinalRules(finalRules, present) &&
+			!isAllowOnlyFinalRules(finalRules) &&
+			!isLegacyPrivateOnlyFinalRules(finalRules) {
 			continue
 		}
 		settings["finalRules"] = []any{

+ 52 - 0
internal/database/freedom_finalrules_migration_test.go

@@ -23,6 +23,24 @@ func TestRewriteFreedomFinalRulesPrivateEgress(t *testing.T) {
 			wantChanged: true,
 			wantRules:   hardened,
 		},
+		{
+			name:        "missing finalRules is hardened",
+			raw:         `{"outbounds":[{"protocol":"freedom","settings":{"domainStrategy":"AsIs"},"tag":"direct"}]}`,
+			wantChanged: true,
+			wantRules:   hardened,
+		},
+		{
+			name:        "null finalRules is hardened",
+			raw:         `{"outbounds":[{"protocol":"freedom","settings":{"domainStrategy":"AsIs","finalRules":null},"tag":"direct"}]}`,
+			wantChanged: true,
+			wantRules:   hardened,
+		},
+		{
+			name:        "empty finalRules is hardened",
+			raw:         `{"outbounds":[{"protocol":"freedom","settings":{"domainStrategy":"AsIs","finalRules":[]},"tag":"direct"}]}`,
+			wantChanged: true,
+			wantRules:   hardened,
+		},
 		{
 			name:        "legacy private-only allow is hardened",
 			raw:         `{"outbounds":[{"protocol":"freedom","settings":{"finalRules":[{"action":"allow","ip":["geoip:private"]}]},"tag":"direct"}]}`,
@@ -76,6 +94,40 @@ func TestRewriteFreedomFinalRulesPrivateEgress(t *testing.T) {
 	}
 }
 
+func TestRewriteFreedomFinalRulesPreservesSplitRouting(t *testing.T) {
+	const raw = `{
+		"outbounds":[{"protocol":"freedom","settings":{"domainStrategy":"AsIs"},"tag":"direct"}],
+		"routing":{"domainStrategy":"AsIs","rules":[
+			{"type":"field","domain":["regexp:.*\\.ru$"],"outboundTag":"direct"},
+			{"type":"field","network":"tcp,udp","outboundTag":"proxy"}
+		]}
+	}`
+	updated, changed, err := rewriteFreedomFinalRulesPrivateEgress(raw)
+	if err != nil {
+		t.Fatalf("rewrite: %v", err)
+	}
+	if !changed {
+		t.Fatal("missing finalRules must be hardened")
+	}
+	var before, after map[string]any
+	if err := json.Unmarshal([]byte(raw), &before); err != nil {
+		t.Fatalf("decode before: %v", err)
+	}
+	if err := json.Unmarshal([]byte(updated), &after); err != nil {
+		t.Fatalf("decode after: %v", err)
+	}
+	beforeRouting, _ := json.Marshal(before["routing"])
+	afterRouting, _ := json.Marshal(after["routing"])
+	if string(afterRouting) != string(beforeRouting) {
+		t.Fatalf("split routing changed:\n got %s\nwant %s", afterRouting, beforeRouting)
+	}
+	outbound := after["outbounds"].([]any)[0].(map[string]any)
+	settings := outbound["settings"].(map[string]any)
+	if settings["domainStrategy"] != "AsIs" {
+		t.Fatalf("freedom domainStrategy=%v want AsIs", settings["domainStrategy"])
+	}
+}
+
 func TestRewriteFreedomFinalRulesPrivateEgressInvalidJSON(t *testing.T) {
 	_, changed, err := rewriteFreedomFinalRulesPrivateEgress("{not json")
 	if err == nil {