Java Gotchas - Bitweise vs. Boolesche Operatoren

Veröffentlicht Feb 07, 2021
von Alan Richardson
FALLSTUDIE

Java Gotchas - Bitweise vs. Boolesche Operatoren

Veröffentlicht Feb 07, 2021
von Alan Richardson
Ressource anzeigen
Ressource anzeigen

Java Gotchas - Bitweise vs. Boolesche Operatoren

> "Java Gotcha" - ein häufiges Fehlermuster, das sich leicht versehentlich implementieren lässt.

Ein recht einfaches Java-Gotcha, in das man versehentlich hineinfallen kann, ist: die Verwendung eines Bitwise-Operators anstelle eines booleschen Vergleichsoperators.

z. B. kann ein einfacher Tippfehler dazu führen, dass Sie "&" schreiben, obwohl Sie eigentlich "&&" schreiben wollten.

Eine gängige Heuristik, die wir beim Überprüfen von Code lernen, ist:

> "&" oder "|" bei Verwendung in einer bedingten Anweisung ist wahrscheinlich nicht beabsichtigt.

In diesem Blog-Beitrag werden wir die Heuristik untersuchen und Wege aufzeigen, wie wir dieses Kodierungsproblem identifizieren und beheben können.


Wo liegt das Problem? Bitweise Operationen funktionieren gut mit Booleans


Die Verwendung von Bitwise-Operatoren mit Booleschen Operatoren ist durchaus zulässig, so dass Java keinen Syntaxfehler meldet.

Wenn ich einen JUnit-Test konstruiere, um eine Wahrheitstabelle sowohl für Bitweises ODER (|) als auch Bitweises UND (&) zu untersuchen, dann werden wir sehen, dass die Ausgaben des Bitweisen Operators mit der Wahrheitstabelle übereinstimmen. Angesichts dessen könnte man meinen, dass die Verwendung von Bitwise-Operatoren kein Problem darstellt.

AND-Wahrheitstabelle

Drei Spalten, eine mit a, eine mit b und die letzte mit (a^b)


@Test
    void bitwiseOperatorsAndTruthTable(){
          Assertions.assertEquals(true, true & true);
          Assertions.assertEquals(false, true & false);
          Assertions.assertEquals(false, false & true);
          Assertions.assertEquals(false, false & false);
    }


Der Test ist bestanden, dies ist vollkommen gültiges Java.


OR-Wahrheitstabelle


Drei Spalten, eine mit a, eine mit b und die letzte mit (a v b)


   @Test
    void bitwiseOperatorsOrTruthTable(){
        Assertions.assertEquals(true, true | true);
        Assertions.assertEquals(true, true | false);
        Assertions.assertEquals(true, false | true);
        Assertions.assertEquals(false, false | false);
    }


Auch dieser Test besteht, warum bevorzugen wir "&&'und "||'?


Wahrheitstabellenbilder wurden mit dem Wahrheitstabellen-Werkzeug von web.standfor.edu.


Problem: Kurzschlussbetrieb


Das eigentliche Problem ist der Unterschied im Verhalten zwischen bitweisen (&, |) und booleschen (&&, ||) Operatoren.

Ein boolescher Operator ist ein Kurzschlussoperator und wertet nur so viel aus, wie er muss.

z.B.

if (args != null & args.length() > 23) {
    System.out.println(args);
}


Im obigen Code werden beide booleschen Bedingungen ausgewertet, da der Bitwise-Operator verwendet wurde:

  • args != null
  • args.length() > 23

Das lässt meinen Code offen für eine NullPointerException, wenn args null ist, weil wir immer die Prüfung für args.length durchführen, auch wenn args null ist, weil beide booleschen Bedingungen ausgewertet werden müssen.


Boolesche Operatoren Kurzschlussauswertung


Wenn ein && verwendet wird, z. B.

if (args != null && args.length() > 23) {
    System.out.println(args);
}


Sobald wir wissen, dass args != null zu false ausgewertet wird, stoppt die Auswertung des Bedingungsausdrucks.

Die rechte Seite brauchen wir nicht auszuwerten.

Unabhängig vom Ergebnis der Bedingung auf der rechten Seite ist der Endwert des booleschen Ausdrucks falsch.


Aber das würde im Produktionscode nie passieren


Dies ist ein ziemlich leicht zu begehender Fehler, der nicht immer von statischen Analysetools erkannt wird.

Ich habe den folgenden Google Dork verwendet, um zu sehen, ob ich irgendwelche öffentlichen Beispiele für dieses Muster finden kann:

filetype:java if "!=null & "
Diese Suche brachte etwas Code von Android im RootWindowContainer
isDocument = intent != null & intent.isDocument()


Dies ist die Art von Code, die eine Codeprüfung bestehen könnte, weil wir oft Bitwise-Operatoren in Zuweisungsanweisungen verwenden, um Werte zu maskieren. Aber in diesem Fall ist das Ergebnis das gleiche wie im obigen Beispiel der if-Anweisung. Wenn intent jemals null ist, dann wird eine NullPointerException geworfen.

Sehr oft kommen wir mit diesem Konstrukt davon, weil wir oft defensiv codieren und redundanten Code schreiben. Die Prüfung auf != null mag in den meisten Anwendungsfällen durchaus redundant sein.

Dies ist ein Fehler, der von Programmierern im Produktionscode gemacht wird.

Ich weiß nicht, wie aktuell die Ergebnisse für die Suche sind, aber als ich die Suche ausgeführt habe, gab es Ergebnisse zurück mit Code von: Google, Amazon, Apache... und mir.

Eine kürzliche Pull-Anfrage in einem meiner Open-Source-Projekte sollte genau diesen Fehler beheben.

if(type!=null & type.trim().length()>0){
    acceptMediaTypeDefinitionsList.add(type.trim());
}


Wie Sie das finden


Als ich meinen Beispielcode in ein paar statischen Analysatoren überprüft habe, hat keiner von ihnen diesen versteckten Selbstzerstörungscode gefunden.

Als Team auf Secure Code Warrior haben wir ein recht einfaches Sensei Rezept erstellt und überprüft, das dies auffangen kann.

Da Bitwise-Operatoren durchaus zulässig sind und häufig in Zuweisungen verwendet werden, haben wir uns auf den Anwendungsfall von if-Anweisungen und die Verwendung von Bitwise & konzentriert, um den problematischen Code zu finden.

search:
  expression:
    anyOf:
    - in:
        condition: {}
    value:
      caseSensitive: false
      matches: ".* & .*"


Hier wird ein regulärer Ausdruck verwendet, um mit " & " übereinzustimmen, wenn er als Bedingungsausdruck verwendet wird. z. B. in einer if-Anweisung.

Um das zu beheben, haben wir wieder auf reguläre Ausdrücke zurückgegriffen. Diesmal mit der sed-Funktion im QuickFix, um das & im Ausdruck global durch && zu ersetzen.

availableFixes:
  - name: "Replace bitwise AND operator to logical AND operator"
    actions:
      - rewrite:
          to: "{{#sed}}s/&/&&/g,{{{ . }}}{{/sed}}"


Ende Anmerkungen

Dies deckt den häufigsten Missbrauch eines Bitwise-Operators ab, d. h. wenn eigentlich ein boolescher Operator beabsichtigt war.

Es gibt andere Situationen, in denen dies auftreten könnte, z. B. das Zuordnungsbeispiel, aber beim Schreiben von Rezepten müssen wir versuchen, eine falsch-positive Identifizierung zu vermeiden, sonst werden Rezepte ignoriert oder ausgeschaltet. Wir erstellen Rezepte, die den häufigsten Vorkommnissen entsprechen. Wenn sich Sensei weiterentwickelt, werden wir möglicherweise zusätzliche Spezifität in die Suchfunktion einbauen, um mehr passende Bedingungen abzudecken.

In seiner aktuellen Form würde dieses Rezept viele der Live-Anwendungsfälle identifizieren, und vor allem den, der in meinem Projekt berichtet wurde.

HINWEIS: Eine ganze Reihe von Code-Kriegern hat zu diesem Beispiel und der Rezeptüberprüfung beigetragen - Charlie Eriksen, Matthieu Calie, Robin Claerhaut, Brysen Ackx, Nathan Desmet, Downey Robersscheuten. Vielen Dank für Ihre Hilfe.


---


Sie können Sensei aus IntelliJ heraus über "Preferences \ Plugins" (Mac) oder "Settings \ Plugins" (Windows) installieren und dann einfach nach "sensei secure code" suchen.

Wir haben eine Menge Quellcode und Rezepte für diese Blog-Beiträge (einschließlich dieses Beitrags) im `sensei-blog-examples`-Repository im Secure Code Warrior GitHub-Konto.

https://github.com/securecodewarrior/sensei-blog-examples

Erfahren Sie mehr über Sensei


Ressource anzeigen
Ressource anzeigen

Autor

Alan Richardson

Sie wollen mehr?

Tauchen Sie ein in unsere neuesten Erkenntnisse über sichere Kodierung im Blog.

Unsere umfangreiche Ressourcenbibliothek zielt darauf ab, die menschliche Herangehensweise an eine sichere Weiterbildung im Bereich der Programmierung zu stärken.

Blog ansehen
Sie wollen mehr?

Holen Sie sich die neuesten Forschungsergebnisse zur entwicklergesteuerten Sicherheit

Unsere umfangreiche Ressourcenbibliothek ist voll von hilfreichen Ressourcen, von Whitepapers bis hin zu Webinaren, die Ihnen den Einstieg in die entwicklungsorientierte sichere Programmierung erleichtern. Erforschen Sie sie jetzt.

Ressourcendrehscheibe

Java Gotchas - Bitweise vs. Boolesche Operatoren

Veröffentlicht Feb 07, 2021
Von Alan Richardson

Java Gotchas - Bitweise vs. Boolesche Operatoren

> "Java Gotcha" - ein häufiges Fehlermuster, das sich leicht versehentlich implementieren lässt.

Ein recht einfaches Java-Gotcha, in das man versehentlich hineinfallen kann, ist: die Verwendung eines Bitwise-Operators anstelle eines booleschen Vergleichsoperators.

z. B. kann ein einfacher Tippfehler dazu führen, dass Sie "&" schreiben, obwohl Sie eigentlich "&&" schreiben wollten.

Eine gängige Heuristik, die wir beim Überprüfen von Code lernen, ist:

> "&" oder "|" bei Verwendung in einer bedingten Anweisung ist wahrscheinlich nicht beabsichtigt.

In diesem Blog-Beitrag werden wir die Heuristik untersuchen und Wege aufzeigen, wie wir dieses Kodierungsproblem identifizieren und beheben können.


Wo liegt das Problem? Bitweise Operationen funktionieren gut mit Booleans


Die Verwendung von Bitwise-Operatoren mit Booleschen Operatoren ist durchaus zulässig, so dass Java keinen Syntaxfehler meldet.

Wenn ich einen JUnit-Test konstruiere, um eine Wahrheitstabelle sowohl für Bitweises ODER (|) als auch Bitweises UND (&) zu untersuchen, dann werden wir sehen, dass die Ausgaben des Bitweisen Operators mit der Wahrheitstabelle übereinstimmen. Angesichts dessen könnte man meinen, dass die Verwendung von Bitwise-Operatoren kein Problem darstellt.

AND-Wahrheitstabelle

Drei Spalten, eine mit a, eine mit b und die letzte mit (a^b)


@Test
    void bitwiseOperatorsAndTruthTable(){
          Assertions.assertEquals(true, true & true);
          Assertions.assertEquals(false, true & false);
          Assertions.assertEquals(false, false & true);
          Assertions.assertEquals(false, false & false);
    }


Der Test ist bestanden, dies ist vollkommen gültiges Java.


OR-Wahrheitstabelle


Drei Spalten, eine mit a, eine mit b und die letzte mit (a v b)


   @Test
    void bitwiseOperatorsOrTruthTable(){
        Assertions.assertEquals(true, true | true);
        Assertions.assertEquals(true, true | false);
        Assertions.assertEquals(true, false | true);
        Assertions.assertEquals(false, false | false);
    }


Auch dieser Test besteht, warum bevorzugen wir "&&'und "||'?


Wahrheitstabellenbilder wurden mit dem Wahrheitstabellen-Werkzeug von web.standfor.edu.


Problem: Kurzschlussbetrieb


Das eigentliche Problem ist der Unterschied im Verhalten zwischen bitweisen (&, |) und booleschen (&&, ||) Operatoren.

Ein boolescher Operator ist ein Kurzschlussoperator und wertet nur so viel aus, wie er muss.

z.B.

if (args != null & args.length() > 23) {
    System.out.println(args);
}


Im obigen Code werden beide booleschen Bedingungen ausgewertet, da der Bitwise-Operator verwendet wurde:

  • args != null
  • args.length() > 23

Das lässt meinen Code offen für eine NullPointerException, wenn args null ist, weil wir immer die Prüfung für args.length durchführen, auch wenn args null ist, weil beide booleschen Bedingungen ausgewertet werden müssen.


Boolesche Operatoren Kurzschlussauswertung


Wenn ein && verwendet wird, z. B.

if (args != null && args.length() > 23) {
    System.out.println(args);
}


Sobald wir wissen, dass args != null zu false ausgewertet wird, stoppt die Auswertung des Bedingungsausdrucks.

Die rechte Seite brauchen wir nicht auszuwerten.

Unabhängig vom Ergebnis der Bedingung auf der rechten Seite ist der Endwert des booleschen Ausdrucks falsch.


Aber das würde im Produktionscode nie passieren


Dies ist ein ziemlich leicht zu begehender Fehler, der nicht immer von statischen Analysetools erkannt wird.

Ich habe den folgenden Google Dork verwendet, um zu sehen, ob ich irgendwelche öffentlichen Beispiele für dieses Muster finden kann:

filetype:java if "!=null & "
Diese Suche brachte etwas Code von Android im RootWindowContainer
isDocument = intent != null & intent.isDocument()


Dies ist die Art von Code, die eine Codeprüfung bestehen könnte, weil wir oft Bitwise-Operatoren in Zuweisungsanweisungen verwenden, um Werte zu maskieren. Aber in diesem Fall ist das Ergebnis das gleiche wie im obigen Beispiel der if-Anweisung. Wenn intent jemals null ist, dann wird eine NullPointerException geworfen.

Sehr oft kommen wir mit diesem Konstrukt davon, weil wir oft defensiv codieren und redundanten Code schreiben. Die Prüfung auf != null mag in den meisten Anwendungsfällen durchaus redundant sein.

Dies ist ein Fehler, der von Programmierern im Produktionscode gemacht wird.

Ich weiß nicht, wie aktuell die Ergebnisse für die Suche sind, aber als ich die Suche ausgeführt habe, gab es Ergebnisse zurück mit Code von: Google, Amazon, Apache... und mir.

Eine kürzliche Pull-Anfrage in einem meiner Open-Source-Projekte sollte genau diesen Fehler beheben.

if(type!=null & type.trim().length()>0){
    acceptMediaTypeDefinitionsList.add(type.trim());
}


Wie Sie das finden


Als ich meinen Beispielcode in ein paar statischen Analysatoren überprüft habe, hat keiner von ihnen diesen versteckten Selbstzerstörungscode gefunden.

Als Team auf Secure Code Warrior haben wir ein recht einfaches Sensei Rezept erstellt und überprüft, das dies auffangen kann.

Da Bitwise-Operatoren durchaus zulässig sind und häufig in Zuweisungen verwendet werden, haben wir uns auf den Anwendungsfall von if-Anweisungen und die Verwendung von Bitwise & konzentriert, um den problematischen Code zu finden.

search:
  expression:
    anyOf:
    - in:
        condition: {}
    value:
      caseSensitive: false
      matches: ".* & .*"


Hier wird ein regulärer Ausdruck verwendet, um mit " & " übereinzustimmen, wenn er als Bedingungsausdruck verwendet wird. z. B. in einer if-Anweisung.

Um das zu beheben, haben wir wieder auf reguläre Ausdrücke zurückgegriffen. Diesmal mit der sed-Funktion im QuickFix, um das & im Ausdruck global durch && zu ersetzen.

availableFixes:
  - name: "Replace bitwise AND operator to logical AND operator"
    actions:
      - rewrite:
          to: "{{#sed}}s/&/&&/g,{{{ . }}}{{/sed}}"


Ende Anmerkungen

Dies deckt den häufigsten Missbrauch eines Bitwise-Operators ab, d. h. wenn eigentlich ein boolescher Operator beabsichtigt war.

Es gibt andere Situationen, in denen dies auftreten könnte, z. B. das Zuordnungsbeispiel, aber beim Schreiben von Rezepten müssen wir versuchen, eine falsch-positive Identifizierung zu vermeiden, sonst werden Rezepte ignoriert oder ausgeschaltet. Wir erstellen Rezepte, die den häufigsten Vorkommnissen entsprechen. Wenn sich Sensei weiterentwickelt, werden wir möglicherweise zusätzliche Spezifität in die Suchfunktion einbauen, um mehr passende Bedingungen abzudecken.

In seiner aktuellen Form würde dieses Rezept viele der Live-Anwendungsfälle identifizieren, und vor allem den, der in meinem Projekt berichtet wurde.

HINWEIS: Eine ganze Reihe von Code-Kriegern hat zu diesem Beispiel und der Rezeptüberprüfung beigetragen - Charlie Eriksen, Matthieu Calie, Robin Claerhaut, Brysen Ackx, Nathan Desmet, Downey Robersscheuten. Vielen Dank für Ihre Hilfe.


---


Sie können Sensei aus IntelliJ heraus über "Preferences \ Plugins" (Mac) oder "Settings \ Plugins" (Windows) installieren und dann einfach nach "sensei secure code" suchen.

Wir haben eine Menge Quellcode und Rezepte für diese Blog-Beiträge (einschließlich dieses Beitrags) im `sensei-blog-examples`-Repository im Secure Code Warrior GitHub-Konto.

https://github.com/securecodewarrior/sensei-blog-examples

Erfahren Sie mehr über Sensei


Wir bitten Sie um Ihre Erlaubnis, Ihnen Informationen über unsere Produkte und/oder verwandte Themen der sicheren Codierung zuzusenden. Wir werden Ihre persönlichen Daten immer mit äußerster Sorgfalt behandeln und sie niemals zu Marketingzwecken an andere Unternehmen verkaufen.

Senden
Um das Formular abzuschicken, aktivieren Sie bitte "Analytics"-Cookies. Sie können die Cookies wieder deaktivieren, sobald Sie fertig sind.