ブログ

Java ゴッチャ - ビット演算子とブール演算子

アラン・リチャードソン
2021年02月07日掲載

Java ゴッチャ - ビット演算子とブール演算子

> "Java Gotcha" - 誤って実装してしまいがちなミスパターン。

Javaの簡単な失敗例として、ブール比較演算子の代わりにBitwise演算子を使用することがあります。

例えば、単純なミスタイプで、本当は「&&」と書きたかったのに「&」と書いてしまうことがあります。

コードをレビューするときに学ぶ一般的なヒューリスティックな考え方があります。

条件文の中で「&」や「|」が使われている場合は、おそらく意図したものではないでしょう。

このブログ記事では、ヒューリスティックを探り、このコーディング問題を特定して修正する方法を紹介します。


何が問題なのか?ビット演算はブーリアンでも問題なく使える


ビット演算子をブール値で使用することは完全に有効であり、Javaは構文エラーを報告しません。

JUnit Test でビットワイズ OR (|) とビットワイズ AND (&) の真理値表を調べると、ビットワイズ演算子の出力が真理値表と一致することがわかります。このように考えると、Bitwise 演算子の使用は問題ないと思われるかもしれません。

AND真理値表

aで3列、bで1列、最後に(a^b)で1列。


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


テストはパス、これは完全に妥当なJavaです。


OR真理値表


aで3列、bで1列、最後に(a v b)で1列。


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


このテストもパスしていますが、なぜ「&&'」と「||'」を好むのでしょうか?


真理値表イメージは 真理値表ツールweb.standfor.edu.


問題点短絡動作


実際に問題となるのは、ビット演算子(&, |)とブール演算子(&&, ||)の動作の違いです。

ブール演算子は短絡的な演算子で、必要な分だけ評価します。

e.g.

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


上記のコードでは、ビットワイズ演算子が使用されているため、両方のブール条件が評価されます。

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

これでは、argsがnullの場合にNullPointerExceptionが発生する可能性があります。なぜなら、argsがnullの場合でも、args.lengthのチェックを常に行うからです。


ブール演算子 短絡評価


&&が使われている場合は、例えば

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


args != nullがfalseと評価されたことがわかると、すぐに条件式の評価を停止します。

右辺の評価は必要ありません。

右辺の条件の結果がどうであれ、ブール式の最終的な値は「偽」になります。


しかし、これはプロダクションコードではあり得ないことです。


これは非常に犯しやすいミスで、静的解析ツールでは必ずしも検出されません。

このパターンの公開例がないか、以下のGoogle Dorkを使って調べてみました。

filetype:java if "!=null & "
今回の検索では、AndroidのRootWindowContainerのコードが出てきました
isDocument = intent != null & intent.isDocument()


代入文の中でビットワイズ演算子を使って値をマスクすることはよくあるので、このようなコードはコードレビューを通過する可能性があります。しかし、この例では、上記のif文の例と同じ結果になります。intentがnullの場合は、NullPointerExceptionがスローされます。

このような構造になってしまうのは、防御的なコードを書いてしまい、冗長なコードを書いてしまうことが多いからです。!=nullのチェックは、ほとんどのユースケースでは余計なことかもしれません。

これはプログラマーがプロダクションコードで犯したエラーです。

検索結果がどれくらい最新かはわかりませんが、私が検索したときには、Google、Amazon、Apache...そして私のコードが戻ってきました。

私のオープンソースプロジェクトの一つで、最近行われたプルリクエストは、まさにこのエラーに対処するものでした。

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


これを見つけるには


私のサンプルコードをいくつかの静的アナライザーでチェックしたところ、どのアナライザーもこの隠れた自爆コードを拾いませんでした。

Secure Code Warrior のチームとして、これを拾うことができる、かなりシンプルなSensei のレシピを作成し、検討しました。

ビット演算子は完全に有効であり、代入にもよく使用されるため、問題のあるコードを見つけるために、if文の使用例とビット演算子の使用に焦点を当てました。

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


これは、" & " が条件式として使われている場合に、正規表現を使ってマッチさせるものです。

この問題を解決するために、再び正規表現を利用しました。今回はQuickFixのsed関数を使って、式の中の&を&&にグローバルに置き換えました。

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


エンドノート

ここでは、ビットワイズ演算子の最も一般的な誤用、つまり、実際にはブール演算子を意図していた場合について説明します。

課題の例のように、このような状況は他にもありますが、レシピを書く際には、誤認識を避けるようにしなければなりません。私たちは、最も一般的な現象に合わせてレシピを作成します。Sensei が進化するにつれ、より多くの一致する条件をカバーするために、検索機能にさらなる特異性を追加する可能性があります。

現在の形では、このレシピは多くのライブユースケースを特定し、最も重要なのは、私のプロジェクトで報告されたものです。

注:この例題とレシピのレビューには、Charlie Eriksen、Matthieu Calie、Robin Claerhaut、Brysen Ackx、Nathan Desmet、Downey Robersscheutenなど、かなりの数のコードウォリアーが協力してくれました。ご協力ありがとうございました。


---


IntelliJの「Preferences ‾ Plugins」(Mac)または「Settings ‾ Plugins」(Windows)から、「sensei secure code」を検索して、「Sensei 」をインストールできます。

Secure Code Warrior GitHub アカウントの `sensei-blog-examples` リポジトリには、これらのブログ記事(今回の記事を含む)のソースコードやレシピが多数用意されています。

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

についてはこちらをご覧ください。Sensei


リソースを見る
リソースを見る

このブログ記事では、一般的なJavaのコーディングミス(条件演算子の代わりにビット演算子を使用)、コードが脆弱になるエラー、Sensei を使用して問題を修正・検出する方法をご紹介します。

ご興味がおありですか?

アラン・リチャードソンは、20年以上にわたり、開発者として、またテスターからテスト責任者まで、あらゆるレベルのテストに携わってきたプロフェッショナルなIT経験を持っています。アラン・リチャードソンは、Secure Code Warrior のデベロッパーリレーションズの責任者として、チームと直接連携し、高品質で安全なコードの開発を促進しています。また、「Dear Evil Tester」や「Java For Testers」など4冊の著書があります。また、テクニカルWebテストやSelenium WebDriver with Javaを学ぶためのオンライントレーニングcourses を作成しています。アランは、SeleniumSimplified.com、EvilTester.com、JavaForTesters.com、CompendiumDev.co.ukに執筆やトレーニングビデオを掲載している。

Secure Code Warrior は、ソフトウェア開発ライフサイクル全体にわたってコードを保護し、サイバーセキュリティを最優先とする企業文化を創造するために、お客様の組織を支援します。AppSec マネージャー、開発者、CISO、またはセキュリティに関わるすべての人が、安全でないコードに関連するリスクを減らすことができるよう、支援します。

デモを予約する
シェアする
著者
アラン・リチャードソン
2021年02月07日掲載

アラン・リチャードソンは、20年以上にわたり、開発者として、またテスターからテスト責任者まで、あらゆるレベルのテストに携わってきたプロフェッショナルなIT経験を持っています。アラン・リチャードソンは、Secure Code Warrior のデベロッパーリレーションズの責任者として、チームと直接連携し、高品質で安全なコードの開発を促進しています。また、「Dear Evil Tester」や「Java For Testers」など4冊の著書があります。また、テクニカルWebテストやSelenium WebDriver with Javaを学ぶためのオンライントレーニングcourses を作成しています。アランは、SeleniumSimplified.com、EvilTester.com、JavaForTesters.com、CompendiumDev.co.ukに執筆やトレーニングビデオを掲載している。

シェアする

Java ゴッチャ - ビット演算子とブール演算子

> "Java Gotcha" - 誤って実装してしまいがちなミスパターン。

Javaの簡単な失敗例として、ブール比較演算子の代わりにBitwise演算子を使用することがあります。

例えば、単純なミスタイプで、本当は「&&」と書きたかったのに「&」と書いてしまうことがあります。

コードをレビューするときに学ぶ一般的なヒューリスティックな考え方があります。

条件文の中で「&」や「|」が使われている場合は、おそらく意図したものではないでしょう。

このブログ記事では、ヒューリスティックを探り、このコーディング問題を特定して修正する方法を紹介します。


何が問題なのか?ビット演算はブーリアンでも問題なく使える


ビット演算子をブール値で使用することは完全に有効であり、Javaは構文エラーを報告しません。

JUnit Test でビットワイズ OR (|) とビットワイズ AND (&) の真理値表を調べると、ビットワイズ演算子の出力が真理値表と一致することがわかります。このように考えると、Bitwise 演算子の使用は問題ないと思われるかもしれません。

AND真理値表

aで3列、bで1列、最後に(a^b)で1列。


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


テストはパス、これは完全に妥当なJavaです。


OR真理値表


aで3列、bで1列、最後に(a v b)で1列。


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


このテストもパスしていますが、なぜ「&&'」と「||'」を好むのでしょうか?


真理値表イメージは 真理値表ツールweb.standfor.edu.


問題点短絡動作


実際に問題となるのは、ビット演算子(&, |)とブール演算子(&&, ||)の動作の違いです。

ブール演算子は短絡的な演算子で、必要な分だけ評価します。

e.g.

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


上記のコードでは、ビットワイズ演算子が使用されているため、両方のブール条件が評価されます。

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

これでは、argsがnullの場合にNullPointerExceptionが発生する可能性があります。なぜなら、argsがnullの場合でも、args.lengthのチェックを常に行うからです。


ブール演算子 短絡評価


&&が使われている場合は、例えば

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


args != nullがfalseと評価されたことがわかると、すぐに条件式の評価を停止します。

右辺の評価は必要ありません。

右辺の条件の結果がどうであれ、ブール式の最終的な値は「偽」になります。


しかし、これはプロダクションコードではあり得ないことです。


これは非常に犯しやすいミスで、静的解析ツールでは必ずしも検出されません。

このパターンの公開例がないか、以下のGoogle Dorkを使って調べてみました。

filetype:java if "!=null & "
今回の検索では、AndroidのRootWindowContainerのコードが出てきました
isDocument = intent != null & intent.isDocument()


代入文の中でビットワイズ演算子を使って値をマスクすることはよくあるので、このようなコードはコードレビューを通過する可能性があります。しかし、この例では、上記のif文の例と同じ結果になります。intentがnullの場合は、NullPointerExceptionがスローされます。

このような構造になってしまうのは、防御的なコードを書いてしまい、冗長なコードを書いてしまうことが多いからです。!=nullのチェックは、ほとんどのユースケースでは余計なことかもしれません。

これはプログラマーがプロダクションコードで犯したエラーです。

検索結果がどれくらい最新かはわかりませんが、私が検索したときには、Google、Amazon、Apache...そして私のコードが戻ってきました。

私のオープンソースプロジェクトの一つで、最近行われたプルリクエストは、まさにこのエラーに対処するものでした。

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


これを見つけるには


私のサンプルコードをいくつかの静的アナライザーでチェックしたところ、どのアナライザーもこの隠れた自爆コードを拾いませんでした。

Secure Code Warrior のチームとして、これを拾うことができる、かなりシンプルなSensei のレシピを作成し、検討しました。

ビット演算子は完全に有効であり、代入にもよく使用されるため、問題のあるコードを見つけるために、if文の使用例とビット演算子の使用に焦点を当てました。

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


これは、" & " が条件式として使われている場合に、正規表現を使ってマッチさせるものです。

この問題を解決するために、再び正規表現を利用しました。今回はQuickFixのsed関数を使って、式の中の&を&&にグローバルに置き換えました。

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


エンドノート

ここでは、ビットワイズ演算子の最も一般的な誤用、つまり、実際にはブール演算子を意図していた場合について説明します。

課題の例のように、このような状況は他にもありますが、レシピを書く際には、誤認識を避けるようにしなければなりません。私たちは、最も一般的な現象に合わせてレシピを作成します。Sensei が進化するにつれ、より多くの一致する条件をカバーするために、検索機能にさらなる特異性を追加する可能性があります。

現在の形では、このレシピは多くのライブユースケースを特定し、最も重要なのは、私のプロジェクトで報告されたものです。

注:この例題とレシピのレビューには、Charlie Eriksen、Matthieu Calie、Robin Claerhaut、Brysen Ackx、Nathan Desmet、Downey Robersscheutenなど、かなりの数のコードウォリアーが協力してくれました。ご協力ありがとうございました。


---


IntelliJの「Preferences ‾ Plugins」(Mac)または「Settings ‾ Plugins」(Windows)から、「sensei secure code」を検索して、「Sensei 」をインストールできます。

Secure Code Warrior GitHub アカウントの `sensei-blog-examples` リポジトリには、これらのブログ記事(今回の記事を含む)のソースコードやレシピが多数用意されています。

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

についてはこちらをご覧ください。Sensei


リソースを見る
リソースを見る

以下のフォームに記入し、レポートをダウンロードしてください。

弊社製品や関連するセキュアコーディングのトピックに関する情報をお送りする許可をお願いします。当社は、お客様の個人情報を細心の注意を払って取り扱い、マーケティング目的で他社に販売することは決してありません。

送信
フォームを送信するには、「Analytics」のCookieを有効にしてください。完了したら、再度無効にしてください。

Java ゴッチャ - ビット演算子とブール演算子

> "Java Gotcha" - 誤って実装してしまいがちなミスパターン。

Javaの簡単な失敗例として、ブール比較演算子の代わりにBitwise演算子を使用することがあります。

例えば、単純なミスタイプで、本当は「&&」と書きたかったのに「&」と書いてしまうことがあります。

コードをレビューするときに学ぶ一般的なヒューリスティックな考え方があります。

条件文の中で「&」や「|」が使われている場合は、おそらく意図したものではないでしょう。

このブログ記事では、ヒューリスティックを探り、このコーディング問題を特定して修正する方法を紹介します。


何が問題なのか?ビット演算はブーリアンでも問題なく使える


ビット演算子をブール値で使用することは完全に有効であり、Javaは構文エラーを報告しません。

JUnit Test でビットワイズ OR (|) とビットワイズ AND (&) の真理値表を調べると、ビットワイズ演算子の出力が真理値表と一致することがわかります。このように考えると、Bitwise 演算子の使用は問題ないと思われるかもしれません。

AND真理値表

aで3列、bで1列、最後に(a^b)で1列。


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


テストはパス、これは完全に妥当なJavaです。


OR真理値表


aで3列、bで1列、最後に(a v b)で1列。


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


このテストもパスしていますが、なぜ「&&'」と「||'」を好むのでしょうか?


真理値表イメージは 真理値表ツールweb.standfor.edu.


問題点短絡動作


実際に問題となるのは、ビット演算子(&, |)とブール演算子(&&, ||)の動作の違いです。

ブール演算子は短絡的な演算子で、必要な分だけ評価します。

e.g.

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


上記のコードでは、ビットワイズ演算子が使用されているため、両方のブール条件が評価されます。

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

これでは、argsがnullの場合にNullPointerExceptionが発生する可能性があります。なぜなら、argsがnullの場合でも、args.lengthのチェックを常に行うからです。


ブール演算子 短絡評価


&&が使われている場合は、例えば

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


args != nullがfalseと評価されたことがわかると、すぐに条件式の評価を停止します。

右辺の評価は必要ありません。

右辺の条件の結果がどうであれ、ブール式の最終的な値は「偽」になります。


しかし、これはプロダクションコードではあり得ないことです。


これは非常に犯しやすいミスで、静的解析ツールでは必ずしも検出されません。

このパターンの公開例がないか、以下のGoogle Dorkを使って調べてみました。

filetype:java if "!=null & "
今回の検索では、AndroidのRootWindowContainerのコードが出てきました
isDocument = intent != null & intent.isDocument()


代入文の中でビットワイズ演算子を使って値をマスクすることはよくあるので、このようなコードはコードレビューを通過する可能性があります。しかし、この例では、上記のif文の例と同じ結果になります。intentがnullの場合は、NullPointerExceptionがスローされます。

このような構造になってしまうのは、防御的なコードを書いてしまい、冗長なコードを書いてしまうことが多いからです。!=nullのチェックは、ほとんどのユースケースでは余計なことかもしれません。

これはプログラマーがプロダクションコードで犯したエラーです。

検索結果がどれくらい最新かはわかりませんが、私が検索したときには、Google、Amazon、Apache...そして私のコードが戻ってきました。

私のオープンソースプロジェクトの一つで、最近行われたプルリクエストは、まさにこのエラーに対処するものでした。

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


これを見つけるには


私のサンプルコードをいくつかの静的アナライザーでチェックしたところ、どのアナライザーもこの隠れた自爆コードを拾いませんでした。

Secure Code Warrior のチームとして、これを拾うことができる、かなりシンプルなSensei のレシピを作成し、検討しました。

ビット演算子は完全に有効であり、代入にもよく使用されるため、問題のあるコードを見つけるために、if文の使用例とビット演算子の使用に焦点を当てました。

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


これは、" & " が条件式として使われている場合に、正規表現を使ってマッチさせるものです。

この問題を解決するために、再び正規表現を利用しました。今回はQuickFixのsed関数を使って、式の中の&を&&にグローバルに置き換えました。

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


エンドノート

ここでは、ビットワイズ演算子の最も一般的な誤用、つまり、実際にはブール演算子を意図していた場合について説明します。

課題の例のように、このような状況は他にもありますが、レシピを書く際には、誤認識を避けるようにしなければなりません。私たちは、最も一般的な現象に合わせてレシピを作成します。Sensei が進化するにつれ、より多くの一致する条件をカバーするために、検索機能にさらなる特異性を追加する可能性があります。

現在の形では、このレシピは多くのライブユースケースを特定し、最も重要なのは、私のプロジェクトで報告されたものです。

注:この例題とレシピのレビューには、Charlie Eriksen、Matthieu Calie、Robin Claerhaut、Brysen Ackx、Nathan Desmet、Downey Robersscheutenなど、かなりの数のコードウォリアーが協力してくれました。ご協力ありがとうございました。


---


IntelliJの「Preferences ‾ Plugins」(Mac)または「Settings ‾ Plugins」(Windows)から、「sensei secure code」を検索して、「Sensei 」をインストールできます。

Secure Code Warrior GitHub アカウントの `sensei-blog-examples` リポジトリには、これらのブログ記事(今回の記事を含む)のソースコードやレシピが多数用意されています。

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

についてはこちらをご覧ください。Sensei


リソースにアクセス

以下のリンクをクリックし、この資料のPDFをダウンロードしてください。

Secure Code Warrior は、ソフトウェア開発ライフサイクル全体にわたってコードを保護し、サイバーセキュリティを最優先とする企業文化を創造するために、お客様の組織を支援します。AppSec マネージャー、開発者、CISO、またはセキュリティに関わるすべての人が、安全でないコードに関連するリスクを減らすことができるよう、支援します。

レポートを見るデモを予約する
PDFをダウンロード
リソースを見る
シェアする
ご興味がおありですか?

シェアする
著者
アラン・リチャードソン
2021年02月07日掲載

アラン・リチャードソンは、20年以上にわたり、開発者として、またテスターからテスト責任者まで、あらゆるレベルのテストに携わってきたプロフェッショナルなIT経験を持っています。アラン・リチャードソンは、Secure Code Warrior のデベロッパーリレーションズの責任者として、チームと直接連携し、高品質で安全なコードの開発を促進しています。また、「Dear Evil Tester」や「Java For Testers」など4冊の著書があります。また、テクニカルWebテストやSelenium WebDriver with Javaを学ぶためのオンライントレーニングcourses を作成しています。アランは、SeleniumSimplified.com、EvilTester.com、JavaForTesters.com、CompendiumDev.co.ukに執筆やトレーニングビデオを掲載している。

シェアする

Java ゴッチャ - ビット演算子とブール演算子

> "Java Gotcha" - 誤って実装してしまいがちなミスパターン。

Javaの簡単な失敗例として、ブール比較演算子の代わりにBitwise演算子を使用することがあります。

例えば、単純なミスタイプで、本当は「&&」と書きたかったのに「&」と書いてしまうことがあります。

コードをレビューするときに学ぶ一般的なヒューリスティックな考え方があります。

条件文の中で「&」や「|」が使われている場合は、おそらく意図したものではないでしょう。

このブログ記事では、ヒューリスティックを探り、このコーディング問題を特定して修正する方法を紹介します。


何が問題なのか?ビット演算はブーリアンでも問題なく使える


ビット演算子をブール値で使用することは完全に有効であり、Javaは構文エラーを報告しません。

JUnit Test でビットワイズ OR (|) とビットワイズ AND (&) の真理値表を調べると、ビットワイズ演算子の出力が真理値表と一致することがわかります。このように考えると、Bitwise 演算子の使用は問題ないと思われるかもしれません。

AND真理値表

aで3列、bで1列、最後に(a^b)で1列。


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


テストはパス、これは完全に妥当なJavaです。


OR真理値表


aで3列、bで1列、最後に(a v b)で1列。


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


このテストもパスしていますが、なぜ「&&'」と「||'」を好むのでしょうか?


真理値表イメージは 真理値表ツールweb.standfor.edu.


問題点短絡動作


実際に問題となるのは、ビット演算子(&, |)とブール演算子(&&, ||)の動作の違いです。

ブール演算子は短絡的な演算子で、必要な分だけ評価します。

e.g.

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


上記のコードでは、ビットワイズ演算子が使用されているため、両方のブール条件が評価されます。

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

これでは、argsがnullの場合にNullPointerExceptionが発生する可能性があります。なぜなら、argsがnullの場合でも、args.lengthのチェックを常に行うからです。


ブール演算子 短絡評価


&&が使われている場合は、例えば

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


args != nullがfalseと評価されたことがわかると、すぐに条件式の評価を停止します。

右辺の評価は必要ありません。

右辺の条件の結果がどうであれ、ブール式の最終的な値は「偽」になります。


しかし、これはプロダクションコードではあり得ないことです。


これは非常に犯しやすいミスで、静的解析ツールでは必ずしも検出されません。

このパターンの公開例がないか、以下のGoogle Dorkを使って調べてみました。

filetype:java if "!=null & "
今回の検索では、AndroidのRootWindowContainerのコードが出てきました
isDocument = intent != null & intent.isDocument()


代入文の中でビットワイズ演算子を使って値をマスクすることはよくあるので、このようなコードはコードレビューを通過する可能性があります。しかし、この例では、上記のif文の例と同じ結果になります。intentがnullの場合は、NullPointerExceptionがスローされます。

このような構造になってしまうのは、防御的なコードを書いてしまい、冗長なコードを書いてしまうことが多いからです。!=nullのチェックは、ほとんどのユースケースでは余計なことかもしれません。

これはプログラマーがプロダクションコードで犯したエラーです。

検索結果がどれくらい最新かはわかりませんが、私が検索したときには、Google、Amazon、Apache...そして私のコードが戻ってきました。

私のオープンソースプロジェクトの一つで、最近行われたプルリクエストは、まさにこのエラーに対処するものでした。

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


これを見つけるには


私のサンプルコードをいくつかの静的アナライザーでチェックしたところ、どのアナライザーもこの隠れた自爆コードを拾いませんでした。

Secure Code Warrior のチームとして、これを拾うことができる、かなりシンプルなSensei のレシピを作成し、検討しました。

ビット演算子は完全に有効であり、代入にもよく使用されるため、問題のあるコードを見つけるために、if文の使用例とビット演算子の使用に焦点を当てました。

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


これは、" & " が条件式として使われている場合に、正規表現を使ってマッチさせるものです。

この問題を解決するために、再び正規表現を利用しました。今回はQuickFixのsed関数を使って、式の中の&を&&にグローバルに置き換えました。

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


エンドノート

ここでは、ビットワイズ演算子の最も一般的な誤用、つまり、実際にはブール演算子を意図していた場合について説明します。

課題の例のように、このような状況は他にもありますが、レシピを書く際には、誤認識を避けるようにしなければなりません。私たちは、最も一般的な現象に合わせてレシピを作成します。Sensei が進化するにつれ、より多くの一致する条件をカバーするために、検索機能にさらなる特異性を追加する可能性があります。

現在の形では、このレシピは多くのライブユースケースを特定し、最も重要なのは、私のプロジェクトで報告されたものです。

注:この例題とレシピのレビューには、Charlie Eriksen、Matthieu Calie、Robin Claerhaut、Brysen Ackx、Nathan Desmet、Downey Robersscheutenなど、かなりの数のコードウォリアーが協力してくれました。ご協力ありがとうございました。


---


IntelliJの「Preferences ‾ Plugins」(Mac)または「Settings ‾ Plugins」(Windows)から、「sensei secure code」を検索して、「Sensei 」をインストールできます。

Secure Code Warrior GitHub アカウントの `sensei-blog-examples` リポジトリには、これらのブログ記事(今回の記事を含む)のソースコードやレシピが多数用意されています。

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

についてはこちらをご覧ください。Sensei


目次

PDFをダウンロード
リソースを見る
ご興味がおありですか?

アラン・リチャードソンは、20年以上にわたり、開発者として、またテスターからテスト責任者まで、あらゆるレベルのテストに携わってきたプロフェッショナルなIT経験を持っています。アラン・リチャードソンは、Secure Code Warrior のデベロッパーリレーションズの責任者として、チームと直接連携し、高品質で安全なコードの開発を促進しています。また、「Dear Evil Tester」や「Java For Testers」など4冊の著書があります。また、テクニカルWebテストやSelenium WebDriver with Javaを学ぶためのオンライントレーニングcourses を作成しています。アランは、SeleniumSimplified.com、EvilTester.com、JavaForTesters.com、CompendiumDev.co.ukに執筆やトレーニングビデオを掲載している。

Secure Code Warrior は、ソフトウェア開発ライフサイクル全体にわたってコードを保護し、サイバーセキュリティを最優先とする企業文化を創造するために、お客様の組織を支援します。AppSec マネージャー、開発者、CISO、またはセキュリティに関わるすべての人が、安全でないコードに関連するリスクを減らすことができるよう、支援します。

デモを予約するダウンロード
シェアする
リソース・ハブ

始めるためのリソース

その他の記事
リソース・ハブ

始めるためのリソース

その他の記事