package recovery import ( "path/filepath" "strings" "testing" ) // TestBlocksSystemDirectories ist der Kerntest gegen die Rechteausweitung. // // Vor Phase 19 galt eine Wiederherstellung nach /etc als durchfuehrbar — real // gegen die laufende Anlage nachgewiesen. Der Angriff braucht keine Luecke im // Code: Wer Backups zurueckschreiben darf, legt eine Datei in /etc/cron.d oder // eine authorized_keys in ein fremdes Benutzerverzeichnis. Aus einem // Anwendungsrecht wird ein Systemzugang. func TestBlocksSystemDirectories(testInstance *testing.T) { targetGuard := NewTargetGuard(nil) blockedTargets := []string{ "/etc", "/etc/cron.d", "/usr/local/bin", "/bin", "/sbin", "/boot/grub", "/var/lib/postgresql", "/System/Library", "/private/etc/hosts", "/root/.ssh", } for _, target := range blockedTargets { if guardError := targetGuard.Validate(target); guardError == nil { testInstance.Errorf("das Systemverzeichnis %q wurde als Ziel zugelassen", target) } } } // TestBlocksFilesystemRoot prueft das Wurzelverzeichnis. func TestBlocksFilesystemRoot(testInstance *testing.T) { targetGuard := NewTargetGuard(nil) if guardError := targetGuard.Validate("/"); guardError == nil { testInstance.Error("das Wurzelverzeichnis wurde als Ziel zugelassen") } } // TestAllowsOrdinaryTargets prueft die Gegenprobe. // // Ein Schutz, der auch die legitimen Ziele sperrt, macht die Wiederherstellung // unbrauchbar — und wer ihn deswegen abschaltet, hat gar keinen mehr. func TestAllowsOrdinaryTargets(testInstance *testing.T) { targetGuard := NewTargetGuard(nil) allowedTargets := []string{ "/srv/wiederherstellung", "/home/benutzer/daten", "/mnt/ziel", "/data/restore-2026", testInstance.TempDir(), } for _, target := range allowedTargets { if guardError := targetGuard.Validate(target); guardError != nil { testInstance.Errorf("das gewöhnliche Ziel %q wurde gesperrt: %v", target, guardError) } } } // TestPrefixMatchingIsNotFooled prueft den Vergleich der Pfade. // // Ein Vergleich ueber strings.HasPrefix haelt "/etchen" fuer einen Teil von // "/etc" und "/var/library" fuer einen Teil von "/var/lib". Der erste Fall // sperrt zu viel, und beide zeigen, dass ein Pfadvergleich ueber die // Pfadtrennung laufen muss. func TestPrefixMatchingIsNotFooled(testInstance *testing.T) { targetGuard := NewTargetGuard(nil) lookalikeTargets := []string{"/etchen", "/var/library", "/usrdaten", "/bindungen"} for _, target := range lookalikeTargets { if guardError := targetGuard.Validate(target); guardError != nil { testInstance.Errorf("das Ziel %q wurde fälschlich gesperrt: %v", target, guardError) } } } // TestAllowedRootsAreExclusive prueft die konfigurierte Erlaubnisliste. // // Ist sie gesetzt, gilt ausschliesslich sie: Ein Betreiber, der die Ziele auf // ein Verzeichnis begrenzt, will nicht, dass daneben noch der ganze Rest des // Dateisystems offen bleibt. func TestAllowedRootsAreExclusive(testInstance *testing.T) { allowedRoot := testInstance.TempDir() targetGuard := NewTargetGuard([]string{allowedRoot}) if guardError := targetGuard.Validate(filepath.Join(allowedRoot, "ziel")); guardError != nil { testInstance.Errorf("das erlaubte Ziel wurde gesperrt: %v", guardError) } // Ein gewoehnliches Verzeichnis ausserhalb der Liste ist jetzt gesperrt, // obwohl es kein Systemverzeichnis ist. if guardError := targetGuard.Validate("/srv/anderswo"); guardError == nil { testInstance.Error("ein Ziel außerhalb der Erlaubnisliste wurde zugelassen") } } // TestEscapeAttemptsAreResolved prueft Pfade mit Rueckwaertsschritten. // // "/srv/../etc" ist "/etc". Ohne die Bereinigung liefe der Vergleich gegen die // Zeichenkette statt gegen den tatsaechlichen Pfad. func TestEscapeAttemptsAreResolved(testInstance *testing.T) { targetGuard := NewTargetGuard(nil) escapeAttempts := []string{ "/srv/../etc", "/tmp/../etc/cron.d", "/home/benutzer/../../etc", } for _, target := range escapeAttempts { if guardError := targetGuard.Validate(target); guardError == nil { testInstance.Errorf("der Ausbruchsversuch %q wurde zugelassen", target) } } } // TestErrorExplainsTheAlternative prueft die Fehlermeldung. // // Eine Sperre ohne Ausweg ist eine Sackgasse. Wer wirklich ein fremdes /etc // zurueckschreiben will, muss erfahren, wie es geht. func TestErrorExplainsTheAlternative(testInstance *testing.T) { targetGuard := NewTargetGuard(nil) guardError := targetGuard.Validate("/etc/beliebig") if guardError == nil { testInstance.Fatal("das Systemverzeichnis wurde zugelassen") } if !strings.Contains(guardError.Error(), "anderen stelle") { testInstance.Errorf("die Meldung nennt keinen Ausweg: %v", guardError) } }