Falsche Blockabgrenzung
Beschreibung
Falsche Blockabgrenzung tritt auf, wenn Kontrollflussanweisungen (if, for, while, else) ihren beabsichtigten Geltungsbereich nicht ordnungsgemäß mit geschweiften Klammern oder ähnlichen Konstrukten abgrenzen. In C-Familie-Sprachen kontrollieren diese Anweisungen nur die unmittelbar folgende Anweisung, wenn Klammern weggelassen werden. Programmierer können beabsichtigen, dass mehrere Anweisungen kontrolliert werden, aber nur die erste ist tatsächlich betroffen. Dies führt zu Code, der visuell korrekt erscheint, sich aber anders verhält — oft als "dangling else" oder "einrückungsbasiertes Bug"-Problem bezeichnet.
Risiko
Falsche Blockabgrenzung erzeugt schwerwiegende Sicherheitslücken und Logikfehler. Sicherheitsprüfungen können scheinbar Code schützen, der tatsächlich bedingungslos ausgeführt wird. Die Apple "goto fail"-Schwachstelle ist ein berühmtes Beispiel. Authentifizierungsumgehungen, Autorisierungsfehler und Datenkorruption können die Folge sein. Diese Bugs sind besonders gefährlich, weil Code-Review sie oft nicht erkennt — der Code sieht aufgrund der Einrückung visuell korrekt aus, aber der Compiler ignoriert die Einrückung. Automatisierte Tools können diese Probleme ebenfalls übersehen.
Lösung
Verwenden Sie immer geschweifte Klammern für Kontrollstrukturen, auch für einzelne Anweisungen. Übernehmen und erzwingen Sie Codierungsstandards, die Klammern vorschreiben. Verwenden Sie statische Analysetools, die irreführende Einrückung erkennen. Konfigurieren Sie IDE-Formatierung, um Blockabgrenzungsprobleme aufzudecken. Aktivieren Sie Compiler-Warnungen wie -Wmisleading-indentation (GCC 6+). Überprüfen Sie bei Code-Review, dass Einrückung mit dem tatsächlichen Kontrollfluss übereinstimmt. Erwägen Sie Sprachen, die signifikante Einrückung verwenden (Python) oder Klammern erzwingen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Sicherheit | Umfang: Authentifizierungs-/Autorisierungsumgehung Sicherheitsprüfungen schützen möglicherweise nicht den beabsichtigten Code. |
| Integrität | Umfang: Logikfehler Code wird außerhalb des beabsichtigten Kontrollflusses ausgeführt. |
| Verfügbarkeit | Umfang: Unvorhersehbares Verhalten Programmfluss weicht von der Programmierabsicht ab. |
Beispielcode
Anfälliger Code
// ANFÄLLIG: Das berühmte "goto fail"-Muster
int verify_signature_vulnerable(Signature* sig) {
int err = 0;
// Hash prüfen
if ((err = check_hash(sig)) != 0)
goto fail;
// Zertifikat prüfen
if ((err = check_certificate(sig)) != 0)
goto fail;
goto fail; // BUG! Dies wird immer ausgeführt!
// Signatur prüfen - WIRD NIE ERREICHT aufgrund des Bugs oben!
if ((err = check_signature(sig)) != 0)
goto fail;
err = 0; // Erfolg - WIRD NIE ERREICHT!
fail:
return err;
}
// ANFÄLLIG: If ohne Klammern
int authenticate_vulnerable(User* user, const char* password) {
if (check_password(user, password) == 0)
log_attempt(user, "success");
user->authenticated = 1; // BUG! Wird immer ausgeführt!
grant_access(user); // BUG! Wird immer ausgeführt!
return user->authenticated;
}
// ANFÄLLIG: Dangling else
void process_vulnerable(int value, int flag) {
if (flag)
if (value > 0)
handle_positive();
else // BUG! Dieses else bindet an das innere if, nicht das äußere!
handle_no_flag(); // Behandelt tatsächlich value <= 0 wenn flag wahr ist!
}
// ANFÄLLIG: For-Schleife ohne Klammern
void clear_array_vulnerable(int* arr, int size) {
for (int i = 0; i < size; i++)
arr[i] = 0;
validate_cleared(arr, i); // BUG! Außerhalb der Schleife, 'i' außer Geltungsbereich!
}
// ANFÄLLIG: While-Schleifen-Problem
void process_stream_vulnerable(Stream* stream) {
while (!stream->eof())
read_data(stream);
process_data(stream); // BUG! Wird nur einmal nach der Schleife ausgeführt!
}
// ANFÄLLIG: Irreführende Einrückung in Sicherheitsprüfung
int check_access_vulnerable(User* user, Resource* resource) {
if (user->role != ADMIN)
if (resource->owner != user->id)
return ACCESS_DENIED;
log_denial(user, resource); // BUG! Protokolliert immer Ablehnung!
// Zugriff gewähren
return ACCESS_GRANTED; // BUG! Erreicht auch für Nicht-Eigentümer!
}
// ANFÄLLIG: Verschachtelte Bedingungen
void complex_check_vulnerable(int a, int b, int c) {
if (a > 0)
if (b > 0)
if (c > 0)
do_all_positive();
else // BUG! Bindet an innerstes if (c > 0)
do_a_negative(); // Wird tatsächlich ausgeführt wenn c <= 0!
}
// ANFÄLLIG: Mehrere Anweisungen für Schleife beabsichtigt
void initialize_vulnerable(int* values, int* flags, int count) {
for (int i = 0; i < count; i++)
values[i] = 0;
flags[i] = 0; // BUG! Außerhalb der Schleife, i ist außer Geltungsbereich!
}
// ANFÄLLIG: C++ mit denselben Problemen
class VulnerableAuth {
public:
bool login(const std::string& password) {
if (checkPassword(password))
logSuccess();
authenticated = true; // BUG! Setzt immer auf true!
return authenticated; // Gibt nach Aufruf immer true zurück!
}
void process(bool condition) {
if (condition)
doFirst();
doSecond(); // BUG! Wird immer ausgeführt!
doThird(); // BUG! Wird immer ausgeführt!
}
private:
bool authenticated = false;
};
// ANFÄLLIG: Destruktor-Aufräum-Problem
void cleanup_vulnerable(Resource* resources, int count) {
for (int i = 0; i < count; i++)
resources[i].release();
delete &resources[i]; // BUG! Löscht nur letztes Element!
}
// ANFÄLLIG: Ausnahmebehandlung
void handle_vulnerable(Request* req) {
if (req == nullptr)
log_error("Null-Anfrage");
throw std::invalid_argument("req ist null"); // BUG! Wirft immer!
}
// JavaScript hat dieselben Probleme
// ANFÄLLIG: Fehlende Klammern
function loginVulnerable(password) {
if (checkPassword(password))
console.log('Anmeldung erfolgreich');
authenticated = true; // BUG! Wird immer ausgeführt!
return authenticated;
}
// ANFÄLLIG: For-Schleife
function processVulnerable(items) {
for (let i = 0; i < items.length; i++)
validate(items[i]);
process(items[i]); // BUG! Verarbeitet nur letztes Element!
}
// ANFÄLLIG: Verschachteltes if
function checkAccessVulnerable(user, resource) {
if (user.isAdmin)
if (resource.isPrivate)
return 'admin-private';
else // Bindet an inneres if!
return 'not-admin'; // Falsch! Gibt zurück wenn private falsch ist!
return 'regular';
}
Korrigierter Code
// SICHER: Immer Klammern verwenden
int verify_signature_safe(Signature* sig) {
int err = 0;
// Hash prüfen
if ((err = check_hash(sig)) != 0) {
goto fail;
}
// Zertifikat prüfen
if ((err = check_certificate(sig)) != 0) {
goto fail;
}
// Signatur prüfen
if ((err = check_signature(sig)) != 0) {
goto fail;
}
err = 0; // Erfolg
fail:
return err;
}
// SICHER: Klammern machen Absicht klar
int authenticate_safe(User* user, const char* password) {
if (check_password(user, password) == 0) {
log_attempt(user, "success");
user->authenticated = 1;
grant_access(user);
} else {
log_attempt(user, "failure");
user->authenticated = 0;
}
return user->authenticated;
}
// SICHER: Explizite Klammern für dangling else
void process_safe(int value, int flag) {
if (flag) {
if (value > 0) {
handle_positive();
}
} else {
handle_no_flag();
}
}
// Alternative mit else-if-Kette
void process_safe_v2(int value, int flag) {
if (flag && value > 0) {
handle_positive();
} else if (flag && value <= 0) {
handle_non_positive();
} else {
handle_no_flag();
}
}
// SICHER: For-Schleife mit Klammern
void clear_array_safe(int* arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] = 0;
validate_index(arr, i);
}
// Separate Validierung des gesamten Arrays
validate_cleared(arr, size);
}
// SICHER: While-Schleife mit Klammern
void process_stream_safe(Stream* stream) {
while (!stream->eof()) {
read_data(stream);
process_data(stream);
}
}
// SICHER: Sicherheitsprüfung mit ordnungsgemäßen Blöcken
int check_access_safe(User* user, Resource* resource) {
if (user->role != ADMIN) {
if (resource->owner != user->id) {
log_denial(user, resource);
return ACCESS_DENIED;
}
}
// Zugriff nur gewähren wenn Bedingungen es erlauben
return ACCESS_GRANTED;
}
// SICHER: Verschachtelte Bedingungen mit Klammern
void complex_check_safe(int a, int b, int c) {
if (a > 0) {
if (b > 0) {
if (c > 0) {
do_all_positive();
}
}
} else {
do_a_negative();
}
}
// Oder Logik vereinfachen
void complex_check_safe_v2(int a, int b, int c) {
if (a > 0 && b > 0 && c > 0) {
do_all_positive();
} else if (a <= 0) {
do_a_negative();
}
}
// SICHER: Mehrere Anweisungen in Schleife
void initialize_safe(int* values, int* flags, int count) {
for (int i = 0; i < count; i++) {
values[i] = 0;
flags[i] = 0;
}
}
// SICHER: Einzeiliges if bekommt trotzdem Klammern
void set_flag_safe(int* flag, int condition) {
if (condition) {
*flag = 1;
}
}
// Oder für wirklich einfache Fälle auf eine Zeile setzen
void set_flag_oneline(int* flag, int condition) {
if (condition) { *flag = 1; }
}
// SICHER: C++ mit ordnungsgemäßen Klammern
class SafeAuth {
public:
bool login(const std::string& password) {
if (checkPassword(password)) {
logSuccess();
authenticated = true;
} else {
logFailure();
authenticated = false;
}
return authenticated;
}
void process(bool condition) {
if (condition) {
doFirst();
doSecond();
doThird();
}
}
private:
bool authenticated = false;
};
// SICHER: Aufräumen mit ordnungsgemäßer Schleife
void cleanup_safe(std::vector<std::unique_ptr<Resource>>& resources) {
for (auto& resource : resources) {
resource->release();
// unique_ptr behandelt Löschung automatisch
}
resources.clear();
}
// SICHER: Ausnahmebehandlung
void handle_safe(Request* req) {
if (req == nullptr) {
log_error("Null-Anfrage");
throw std::invalid_argument("req ist null");
}
// Gültige Anfrage verarbeiten
processRequest(req);
}
// SICHER: Moderner C++-Stil mit Initialisierung
bool validateAndProcess(const Data& data) {
if (auto result = validate(data); result.success) {
return process(data);
}
return false;
}
// SICHER: JavaScript mit ordnungsgemäßen Klammern
function loginSafe(password) {
if (checkPassword(password)) {
console.log('Anmeldung erfolgreich');
authenticated = true;
}
return authenticated;
}
// SICHER: For-Schleife mit Klammern
function processSafe(items) {
for (let i = 0; i < items.length; i++) {
validate(items[i]);
process(items[i]);
}
}
// Oder moderner Ansatz
function processSafeModern(items) {
items.forEach(item => {
validate(item);
process(item);
});
}
// SICHER: Verschachteltes if mit expliziten Klammern
function checkAccessSafe(user, resource) {
if (user.isAdmin) {
if (resource.isPrivate) {
return 'admin-private';
}
return 'admin-public';
} else {
return 'not-admin';
}
}
// Oder vereinfachte Logik
function checkAccessFlat(user, resource) {
if (user.isAdmin && resource.isPrivate) {
return 'admin-private';
}
if (user.isAdmin) {
return 'admin-public';
}
return 'not-admin';
}
// ESLint: curly-Regel erzwingt Klammern
// "curly": ["error", "all"]
Ausgenutzt in der Praxis
Apple "goto fail"-Bug (CVE-2014-1266)
Das berühmteste Beispiel — eine doppelte goto-Anweisung, die die SSL/TLS-Zertifikatsvalidierung umging und Man-in-the-Middle-Angriffe ermöglichte.
Authentifizierungsumgehungen
Mehrere Authentifizierungssysteme wurden aufgrund falscher Blockabgrenzung umgangen, bei der Sicherheitsprüfungen nicht den Code schützten, den sie zu schützen schienen.
Zugriffskontrollfehler
Autorisierungscode gewährte unbeabsichtigten Zugriff, wenn Bedingungen die geschützten Operationen nicht ordnungsgemäß abgrenzten.
CVE-Beispiele
-
CVE-2014-1266 — Apple SSL "goto fail"-Bug.
-
CVE-2018-16712 — ImageMagick Blockabgrenzungsproblem.
-
Mehrere Authentifizierungsumgehungs-CVEs durch ähnliche Muster.
Referenzen
-
MITRE Corporation. "CWE-483: Incorrect Block Delimitation." https://cwe.mitre.org/data/definitions/483.html
-
CERT C Secure Coding Standard. "EXP19-C: Use braces for the body of an if, for, or while statement." https://wiki.sei.cmu.edu/confluence/display/c/