Zum Inhalt springen

Speicherrisiken bei delegatecall

Delegatecall führt den Code eines anderen Vertrags im Speicher des aufrufenden Vertrags aus. Erfahren Sie, wie Speicherkollisionen, Upgrade-Befugnisse und nicht vertrauenswürdige Ziele einen Proxy oder ein Smart Wallet gefährden können.

Aktualisiert

Nur zu Bildungszwecken; stellt keine Anlageberatung dar. Anlagen können zu Verlusten führen.

Direkte Antwort

delegatecall führt Code aus einem Zielvertrag im Kontext des aufrufenden Vertrags aus. Der Aufrufer behält seinen eigenen Speicher, Saldo und address(this), während msg.sender und msg.value die Werte des ursprünglichen Aufrufs beibehalten.

Dieses Verhalten ermöglicht Proxys, Bibliotheken und Smart-Wallet-Module, verleiht dem delegierten Code aber zugleich faktisch die Befugnisse des Aufrufers. Schreibzugriffe auf den Speicher, Vermögensübertragungen, Genehmigungen oder externe Aufrufe erfolgen als Aufrufer und nicht als Zielvertrag, der den Code bereitgestellt hat.

Behandeln Sie jedes erreichbare delegatecall-Ziel als privilegierten Code. Die Sicherheit hängt von den Regeln für die Zielauswahl, kompatiblen Speicherlayouts, dem Initialisierungszustand, den Upgrade-Kontrollen und der genauen Implementierung ab, die für die Transaktion aktiv ist.

Funktionsweise

Bei einem gewöhnlichen externen Aufruf liest und beschreibt der aufgerufene Vertrag seinen eigenen Speicher. Bei delegatecall wird der Bytecode des Ziels auf dem Speicher des Aufrufers ausgeführt: Eine SSTORE-Anweisung verändert einen Slot des Aufrufers. Die Variablennamen des Ziels spielen zur Laufzeit keine Rolle; entscheidend sind allein die berechneten Slot-Positionen.

Daraus ergeben sich vier Grenzen, die geprüft werden müssen:

  • Kontrolle über das Ziel: Ermitteln Sie, ob das Ziel fest vorgegeben ist, von einem Benutzer ausgewählt, über eine Registry aufgelöst oder von einem Administrator geändert werden kann.
  • Speicherkompatibilität: Vergleichen Sie Variablenreihenfolge, Typen, Vererbung, Speicherlücken sowie namensraumgebundene oder standardisierte Slots über alle Implementierungsversionen hinweg.
  • Initialisierung und Autorisierung: Stellen Sie sicher, dass Initialisierer nicht erneut ausgeführt werden können und dass Upgrade- oder Modulverwaltungsfunktionen den vorgesehenen Aufrufer und die festgelegte Governance-Verzögerung durchsetzen.
  • Verarbeitung von Rückgabewerten: Prüfen Sie, ob Fehler weitergegeben und Rückgabedaten als der erwartete Typ dekodiert werden; Low-Level-Aufrufe bieten nicht die üblichen Vertragstypprüfungen von Solidity.

ERC-1967 verringert Kollisionen bei Proxys, indem Implementierungs-, Beacon- und Administratoradressen in standardisierten Slots außerhalb der normalen Compilerbelegung gespeichert werden. Der Standard beweist weder, dass eine Implementierung sicher ist, noch dass ein autorisiertes Upgrade unbedenklich ist.

Beispiel

Angenommen, ein Wallet speichert owner in slot 0. Ein Plug-in, das mit counter in slot 0 kompiliert wurde, erhöht den Zähler, wenn das Wallet es über delegatecall aufruft.

Der Schreibzugriff verändert den owner-Wert des Wallets, weil der Speicher dem Wallet gehört. Wenn das resultierende Wort eine vom Angreifer kontrollierte Adresse kodiert, können spätere Autorisierungsprüfungen den Angreifer als Eigentümer anerkennen, obwohl das Plug-in die Vermögenswerte des Wallets nie selbst verwahrt hat.

Eine erfolgreiche Transaktionsbestätigung unterscheidet nicht zwischen beabsichtigten und schädlichen Zustandsänderungen. Eine Transaktionssimulation sollte daher Speicherdifferenzen, Änderungen an Vermögenswerten und Genehmigungen, ausgelöste Ereignisse sowie nachgelagerte Aufrufe anhand der exakten Proxy- und Implementierungsadressen prüfen.

Risiken

  • Ausführung mit beliebigem Ziel: Von Benutzern kontrollierte oder unzureichend geprüfte Ziele können bösartigen Code mit den Berechtigungen des Aufrufers ausführen.
  • Speicherkollision: Eine Implementierung kann Eigentumsdaten, Guthaben, den Pausenzustand oder sogar den Slot überschreiben, der die nächste Implementierung festlegt.
  • Unsicheres Upgrade: Ein kompromittierter Administrator oder Governance-Prozess kann zuvor geprüften Code ersetzen, nachdem Benutzer Vermögenswerte eingezahlt oder Genehmigungen erteilt haben.
  • Fehlerhafte Initialisierung: Bei einem nicht initialisierten Proxy oder einer nicht initialisierten Implementierung kann ein anderes Konto privilegierte Rollen übernehmen oder gefährliche Abhängigkeiten konfigurieren.
  • Irreführende Prüfung: Wer nur den Quellcode des Proxys, die aktuelle Implementierung oder die Schnittstelle prüft, kann einen Beacon, ein ausstehendes Upgrade, eine Modul-Registry oder einen alternativen Ausführungspfad übersehen.

Ermitteln Sie vor dem Signieren die Implementierung an einem aktuellen Block, prüfen Sie, wer sie mit welcher Verzögerung ändern kann, untersuchen Sie den verifizierten Bytecode und das Speicherlayout des Ziels, simulieren Sie die vollständigen Calldata und vergleichen Sie sensible Speicherwerte sowie Token-Genehmigungen vor und nach der Ausführung. Prüfen Sie bei einem Smart Wallet zusätzlich, wie Module aktiviert und deaktiviert werden und welche Ziele sie auswählen dürfen.

Häufige Irrtümer

  • „Das Ziel kann die Vermögenswerte des Aufrufers nicht berühren.“ Delegierter Code wird als Aufrufer ausgeführt und kann externe Verträge aufrufen, Vermögenswerte übertragen oder Genehmigungen erteilen, sofern der Aufrufer über diese Möglichkeiten verfügt.
  • „Übereinstimmende Variablennamen verhindern Kollisionen.“ Die EVM verwendet Speicher-Slots und keine Namen aus dem Quellcode. Layoutreihenfolge, Vererbung und Typen müssen kompatibel bleiben.
  • „Verifizierter Proxy-Code bedeutet, dass das gesamte System verifiziert ist.“ Die aktive Implementierung, der Beacon, der Upgrade-Administrator, der Initialisierungszustand und die Modulberechtigungen sind eigenständige Teile der Vertrauensgrenze.

Verwandte Themen

Quellen

Navigation

Wiki durchsuchen...