Java 陷阱 - 位操作符与布尔操作符

2021年2月7日出版
作者:Alan Richardson
案例研究

Java 陷阱 - 位操作符与布尔操作符

2021年2月7日出版
作者:Alan Richardson
查看资源
查看资源

Java 陷阱 - 位操作符与布尔操作符

> "Java Gotcha" - 一个很容易意外实现的常见错误模式。

一个不小心陷入的相当简单的Java Gotcha是:使用Bitwise操作符而不是布尔比较操作符。

例如,一个简单的错误输入会导致写成"&",而你真正想写的是"&&"。

我们在审查代码时学到的一个常见的启发式方法是。

> "&"或"|"在条件语句中使用时,可能不是故意的。

在这篇博文中,我们将探讨启发式方法,并确定我们可以识别和修复这个编码问题的方法。


问题是什么?位操作在布尔运算中工作良好


在布尔运算中使用Bitwise运算符是完全有效的,所以Java不会报告语法错误。

如果我构建一个JUnit测试来探索Bitwise OR (|)和Bitwise AND (&)的真值表,那么我们将看到Bitwise运算符的输出与真值表相匹配。鉴于此,我们可能会认为使用Bitwise运算符不是一个问题。

与真值表

有三栏是a,一栏是b,最后一栏是(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);
    }


测试通过,这就是完全有效的Java。


OR真值表


三列是a,一列是b,最后一列是(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);
    }


这个测试也通过了,为什么我们更喜欢"&&"和"||"?


真值表图像是用 真值表工具web.standfor.edu.


问题。短路运行


真正的问题是Bitwise (&, |)和Boolean (&&, ||)运算符之间的行为差异。

布尔运算符是一个短路运算符,只在需要的时候进行评估。

比如说

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


在上面的代码中,两个布尔条件都会被评估,因为使用了Bitwise操作符。

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

这使得我的代码在args为空时容易出现NullPointerException,因为即使args为空,我们也会对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()


这种类型的代码可能会通过代码审查,因为我们经常在赋值语句中使用Bitwise运算符来屏蔽数值。但是在这个例子中,结果和上面的if语句的例子是一样的。如果intent曾经是空的,那么就会抛出一个NullPointerException。

很多时候,我们之所以能够摆脱这种结构,是因为我们经常进行防御性编码,并编写多余的代码。在大多数情况下,对!=null的检查很可能是多余的。

这是程序员在生产代码中犯的一个错误。

我不知道搜索结果的时效性如何,但当我进行搜索时,有结果回来了,有来自:谷歌、亚马逊、阿帕奇......和我的代码。

最近我的一个开源项目的拉动请求正是为了解决这个错误。

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


如何找到这个


当我在几个静态分析器中检查我的样本代码时,没有任何一个分析器发现这个隐藏的自毁代码。

作为一个团队,Secure Code Warrior ,我们创建并审查了一个相当简单的Sensei 的配方,可以接这个。

因为Bitwise运算符是完全有效的,而且经常用于赋值,我们把重点放在if语句的使用上,以及Bitwise &的使用上,以找到有问题的代码。

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


当"&"被用作条件表达式时,它使用正则表达式来匹配,例如在if语句中。

为了解决这个问题,我们再次依靠正则表达式。这一次,我们使用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。感谢你们的帮助。


---


你可以使用 "Preferences\ Plugins"(Mac)或 "Settings\ Plugins"(Windows)从IntelliJ内部安装Sensei ,然后只需搜索 "sensei secure code"

我们在Secure Code Warrior GitHub账户中的`sensei-blog-examples`仓库里有很多这些博文的源代码和配方(包括这一篇)。

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

了解更多关于Sensei


查看资源
查看资源

作者

艾伦-理查德森

想要更多吗?

在博客上深入了解我们最新的安全编码见解。

我们广泛的资源库旨在增强人类对安全编码技术提升的方法。

查看博客
想要更多吗?

获取关于开发者驱动的安全的最新研究

我们广泛的资源库充满了有用的资源,从白皮书到网络研讨会,让你开始使用开发者驱动的安全编码。现在就去探索它。

资源中心

Java 陷阱 - 位操作符与布尔操作符

2021年2月7日出版
作者:Alan Richardson

Java 陷阱 - 位操作符与布尔操作符

> "Java Gotcha" - 一个很容易意外实现的常见错误模式。

一个不小心陷入的相当简单的Java Gotcha是:使用Bitwise操作符而不是布尔比较操作符。

例如,一个简单的错误输入会导致写成"&",而你真正想写的是"&&"。

我们在审查代码时学到的一个常见的启发式方法是。

> "&"或"|"在条件语句中使用时,可能不是故意的。

在这篇博文中,我们将探讨启发式方法,并确定我们可以识别和修复这个编码问题的方法。


问题是什么?位操作在布尔运算中工作良好


在布尔运算中使用Bitwise运算符是完全有效的,所以Java不会报告语法错误。

如果我构建一个JUnit测试来探索Bitwise OR (|)和Bitwise AND (&)的真值表,那么我们将看到Bitwise运算符的输出与真值表相匹配。鉴于此,我们可能会认为使用Bitwise运算符不是一个问题。

与真值表

有三栏是a,一栏是b,最后一栏是(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);
    }


测试通过,这就是完全有效的Java。


OR真值表


三列是a,一列是b,最后一列是(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);
    }


这个测试也通过了,为什么我们更喜欢"&&"和"||"?


真值表图像是用 真值表工具web.standfor.edu.


问题。短路运行


真正的问题是Bitwise (&, |)和Boolean (&&, ||)运算符之间的行为差异。

布尔运算符是一个短路运算符,只在需要的时候进行评估。

比如说

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


在上面的代码中,两个布尔条件都会被评估,因为使用了Bitwise操作符。

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

这使得我的代码在args为空时容易出现NullPointerException,因为即使args为空,我们也会对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()


这种类型的代码可能会通过代码审查,因为我们经常在赋值语句中使用Bitwise运算符来屏蔽数值。但是在这个例子中,结果和上面的if语句的例子是一样的。如果intent曾经是空的,那么就会抛出一个NullPointerException。

很多时候,我们之所以能够摆脱这种结构,是因为我们经常进行防御性编码,并编写多余的代码。在大多数情况下,对!=null的检查很可能是多余的。

这是程序员在生产代码中犯的一个错误。

我不知道搜索结果的时效性如何,但当我进行搜索时,有结果回来了,有来自:谷歌、亚马逊、阿帕奇......和我的代码。

最近我的一个开源项目的拉动请求正是为了解决这个错误。

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


如何找到这个


当我在几个静态分析器中检查我的样本代码时,没有任何一个分析器发现这个隐藏的自毁代码。

作为一个团队,Secure Code Warrior ,我们创建并审查了一个相当简单的Sensei 的配方,可以接这个。

因为Bitwise运算符是完全有效的,而且经常用于赋值,我们把重点放在if语句的使用上,以及Bitwise &的使用上,以找到有问题的代码。

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


当"&"被用作条件表达式时,它使用正则表达式来匹配,例如在if语句中。

为了解决这个问题,我们再次依靠正则表达式。这一次,我们使用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。感谢你们的帮助。


---


你可以使用 "Preferences\ Plugins"(Mac)或 "Settings\ Plugins"(Windows)从IntelliJ内部安装Sensei ,然后只需搜索 "sensei secure code"

我们在Secure Code Warrior GitHub账户中的`sensei-blog-examples`仓库里有很多这些博文的源代码和配方(包括这一篇)。

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

了解更多关于Sensei


我们希望得到您的许可,向您发送有关我们产品和/或相关安全编码主题的信息。我们将始终以最谨慎的态度对待您的个人资料,绝不会将其出售给其他公司用于营销目的。

要提交表格,请启用 "分析 "cookies。完成后,请随时再次禁用它们。