代码分析平台CodeQL学习手记(六) - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

代码分析平台CodeQL学习手记(六)

fanyeee 资讯 2020-01-07 11:20:00
927966
收藏

导语:在前面的文章中,我们通过一个抓小偷的例子,为大家展示了逻辑谓词、连接词和聚合操作的威力。在本文中,我们将通过一个抓纵火犯的例子来复习谓词的知识,并进一步学习类的定义和用法,以及如何覆盖父类的成员谓词。

代码分析平台CodeQL入门(一)

代码分析平台CodeQL学习手记(二)

代码分析平台CodeQL学习手记(三)

代码分析平台CodeQL学习手记(四)

代码分析平台CodeQL学习手记(五)

在前面的文章中,我们通过一个抓小偷的例子,为大家展示了逻辑谓词、连接词和聚合操作的威力。在本文中,我们将通过一个抓纵火犯的例子来复习谓词的知识,并进一步学习类的定义和用法,以及如何覆盖父类的成员谓词。

简介

上一次,我们用QL破获了王冠失窃案,之后我们便获得了QL大侦探的美誉。可是,不久之后,村里又发生了一起案件:村北的庄稼地被人恶意纵火,导致地里的农作物颗粒无收,村民损失惨重。所以,我们奉命找出纵火犯。

现在,我们除了手头上的村民信息之外,还掌握了一条重要线索:村南和村北的村民之间交恶已久,所以,纵火犯很可能就是住在村南的村民。

在破案过程中,我们将进一步介绍如何在QL代码中定义和使用谓词和类。有了它们,不仅可以让我们编写的查询逻辑变得更加易于理解,同时,还有助于简化我们的侦查工作。

缩小包围圈

现在,怀疑对象已经被锁定为一个特定的村民群体,即那些生活在村庄南部的村民。因此,我们可以定义一个新的谓词southern,用于遴选住在村南的村民,这样的话,就不必在所有查询这些村民的代码中加入GetLocation()=“south”这一条件了。谓词southern的定义如下所示:

predicate southern(Person p) {
    p.getLocation() = "south"
}

使用谓词southern(p)时,我们需要提供一个参数p,以便让谓词检查p是否满足条件p.getLocation() = "south",也就是住在村南。

好了,根据上面的例子,我们再次复习一下谓词的定义和分类。我们知道,谓词分为两类:一类是带有返回值的谓词,另一类是没有返回值的谓词。在定义谓词时,首先要注意的是,谓词的名称必须以小写字母开头。另外,上面的这个谓词属于没有返回结果的谓词,定义这种类型的谓词时,需要使用关键字predicate;不过,当需要定义带有返回结果的谓词的时候,需要把这里的关键字predicate替换为返回结果的数据类型。这时,还需要引入了一个新的参数来保存返回结果——特殊变量result。例如,int getAge() {result = ...}便是定义了一个返回整型数据的谓词。

现在,我们就可以利用上面定义的谓词来找出所有住在村南的居民了,具体代码如下所示:

/* 谓词southern的定义如上所示 */

from Person p
where southern(p)
select p

1.png

运行结果如下所示:

2.png

如您所见,利用谓词的好处是,不仅使得我们的代码的逻辑更加清晰,同时,也能提高编写代码的效率。对于上面的查询代码,from子句表示要考察所有村民(Person p),然后,在where子句中加入了一个限制条件:住在村南的居民(southern(p))。

实际上,除了利用上面的查询来找出考察对象之外,我们还可以自定义一个Southerner类,用它来找出我们的考察对象,也就是住在村南的居民,具体代码如下所示:

class Southerner extends Person {
    Southerner() { southern(this) }
}

对于QL语言来说,可以用类来表示一个逻辑属性:当一个值满足该属性时,它就是类的成员。这意味着一个值可以属于多个类,这其实不难理解,举例来说,3既属于“整数”类,也属于“奇数”类,同时属于“质数”类,等等。

在上面的类的定义中,表达式southern(this)定义了这个类所表示的逻辑属性,我们称这个谓词为这个类的特征谓词。需要注意的是,这个表达式中使用了一个特殊变量this,就这里来耍,该变量表示一个Person类型的值,也就是一个村民;如果this满足southern(this)这一限制条件,那么,this代表的村民就属于Southerner类,也就是居住在村南的村民。

对于熟悉面向对象编程语言的读者来说,会发现特征谓词跟构造函数非常类似。不过,特征谓词并非构造函数,实际上它是一个逻辑属性,并且不创建任何对象。

在QL语言中,我们通常需要根据现有的类(超集)来定义新的类(子集)。 在我们的例子中,Southerner是村民中的一个特殊群体,所以,我们说Southerner(住在村南的村民)类继承自Person(村民)类,换句话说,Southerner是Person的一个子集。

借助于这个类,在列出所有住在村南的居民的时候,相应的代码会变得更加简洁:

from Southerner s
select s

第一句是声明变量,就是住在村南的村民,然后,没有附加任何条件就直接列出这些变量了。完整的代码如下所示:

3.png

运行结果如下所示:

4.png

通过上面的例子,您可能已经注意到,有些谓词是跟在某些变量后面的,例如p.getAge();而有些则是以参数的形式传递变量,例如southern(p)。这是因为,getAge()是一个定义在类Person中的一个成员谓词(类似于成员函数),也就是说,它是一个只能用于该类中的成员变量的谓词。在定义类时,我们也可定义自己的成员谓词,这一点将在下面看到。相反,谓词southern是单独定义的,不属于任何类。实际上,我们还可以将多个成员谓词串起来完成一系列的操作,例如,p. getage ().sqrt(),这里首先获取村民p的年龄,然后计算年龄的平方根——用起来是不是特别方便啊!

出行管制

在这里,我们还要考虑另一个因素:发生王冠失窃案后,村子里实施了出行管制。案发之前,村民是可以在村子里自由走动的,因此,谓词isAllowedIn(string region) 是适用于任何村民和任何区域的。举例来说,下面的查询代码将会列出所有村民,因为他们都可以去村北:

from Person p
where p.isAllowedIn("north")
select p

完整的代码如下所示:

5.png

运行结果如下所示:

6.png

然而,在发生盗窃案之后,村民们变得非常警惕,所以,不再允许10岁以下的儿童离开居住地,例如,村北的孩子不能到村南、村东、村西去玩了。这就意味着,谓词isAllowedIn(string region) 已经不再适用于所有村民和所有区域,所以,当p表示一个孩子时,我们应该临时覆盖原来的谓词isAllowedIn(string region)。

为此,需要首先定义一个类Child,用以表示10岁以下所有村民。然后,在类Child类中,我们重新定义成员谓词isAllowedIn(string region),以使孩子们只能在自己的地盘上走动。这一限制可以通过region = this.getLocation()来进行表示。好了,现在大家终于明白了吧?上面所说的覆盖,实际上就是重新定义父类中的成员谓词。

class Child extends Person {
 
    /* the characteristic predicate */
    Child() { this.getAge() < 10 }
 
    /* a member predicate */
    override predicate isAllowedIn(string region) {
        region = this.getLocation()
    }
}

现在,当我们将谓词isAllowedIn(string region)应用于表示村民的变量p上的时候,如果变量p的类型不是Child,也就是这个村民不是一个孩子的时候,则使用该谓词原来的定义;但是如果变量p的类型为Child,也就是说这个变量表示的村民是一个孩子,那么,该谓词将会使用Child类中的新定义,从而覆盖原来的代码。

根据现有的线索,我们知道纵火犯住在村南,所以,他们必定是被允许到村北走动的。为此,我们可以编写一个查询来找出可能的嫌疑犯。同时,我们还可以扩展select子句来列出嫌疑人的年龄。具体代码如下所示:

7.png

运行结果如下所示:

8.png

通过年龄一栏我们就可以清楚地看到:所有的孩子都被排除在了嫌疑人名单之外。

好了,接下来我们还需要进一步收集更多线索,以便找出真正的纵火犯!

揪出真凶

为了找出真凶,我们继续在村北寻找线索,幸运的是,我们这次找到了目击证人!就在火灾发生后,住在田地旁边的农民看见两个人仓皇逃跑了。虽然他们只看到了嫌疑人的背影,却注意到他们都是秃头。

太好了,这是一个非常有用的线索——这样以来,我们就可以通过QL查询来查找所有秃头的人了,具体代码如下所示

from Person p
where not exists (string c | p.getHairColor() = c)
select p

如上所示,这里使用了排除法来找出秃头的村民,也就是发色无法与现有村民发色相匹配的人,即not exists (string c | p.getHairColor() = c)——因为秃子根本没有头发,哪里来的发色呀!当然,也许有读者觉得每次找秃子都需要输入一长串代码的做法,不仅费时费力,而且容易出错,那有没有更好的办法呢?别急,我们可以把这些代码封装到一个谓词中,之后只需要调用这个谓词就行了,具体代码如下所示:

predicate bald(Person p) {
    not exists (string c | p.getHairColor() = c)
}

当村民p是一个秃子时,属性bald(p)便成立,因此,之前用来判断村民是否为秃子时的查询代码可以简化为:

from Person p
where bald(p)
select p

根据前面的定义,我们知道谓词bald的参数的类型为Person,所以,我们也可以使用Southerner类型的变量作为其参数,因为Southerner类型是Person类型的一个子类型。但是,我们却不能用整型变量作为其参数,这会导致语法错误。

好了,利用上面介绍的知识,现在可以编写一个查询,来查找住在村南的且可以进入村北的秃头村民,具体代码如下所示:

import tutorial
 
predicate southern(Person p) {
    p.getLocation() = "south"
}
 
class Southerner extends Person {
    /* the characteristic predicate */
    Southerner() { southern(this) }
}
 
class Child extends Person {
    /* the characteristic predicate */
    Child() { this.getAge() < 10 }
 
    /* a member predicate */
    override predicate isAllowedIn(string region) {
        region = this.getLocation()
    }
}
 
predicate bald(Person p) {
    not exists (string c | p.getHairColor() = c)
}
 
from Southerner s
where s.isAllowedIn("north") and bald(s)
select s

上面的代码的运行结果如下所示:

9.png

纵火犯终于落网了!

小结

在本文中,我们以破获纵火案为例,回顾了谓词和类的定义和使用,同时,还介绍了如何覆盖成员谓词。至此,QL语言的威力给村民们留下来深刻的印象,在下一篇文章中,我们将受村民之托,帮他们寻找合法的王位继承人。

备注:本系列文章乃本人在学习CodeQL平台过程中所做的笔记,希望能够对大家有点滴帮助——若果真如此的话,本人将备感荣幸。

参考资料:

https://help.semmle.com/

如若转载,请注明原文地址
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务