一、背景介绍
参[3],Max Kellermann介绍”Dirty Pipe”漏洞时提到”git bisect”。我不是git用户,属于水货程序员,但我有审阅源码的需求,比如想知道哪次commit引入BUG。为此查看”git bisect”帮助手册,参[2],顺道学习了几种git源码查错技巧。
二、git源码查错技巧
1) 准备git测试环境
mkdir -p /tmp/gittest
cd /tmp/gittest
git init -q
echo “line 0” > gittest.txt
git add -A && git commit -q -m “Adding line 0”
echo “line 1” >> gittest.txt
git add -A && git commit -q -m “Adding line 1”
echo “line 2” >> gittest.txt
git add -A && git commit -q -m “Adding line 2”
echo “line 3” >> gittest.txt
git add -A && git commit -q -m “Adding line 3”
echo “line 4” >> gittest.txt
git add -A && git commit -q -m “Adding line 4”
sed -i -e ‘s/line 4/line unknown/g’ gittest.txt
git add -A && git commit -q -m “Changing something”
echo “line 5” >> gittest.txt
git add -A && git commit -q -m “Adding line 5”
echo “line 6” >> gittest.txt
git add -A && git commit -q -m “Adding line 6″
$ cat gittest.txt
line 0
line 1
line 2
line 3
line unknown
line 5
line 6
撤销测试环境,只需删除”.git”目录即可。
2) git log
假设txt中出现”unknown”表示错误,用”git log”看一下哪次commit涉及”unknown”的出现。
$ git log –pretty=oneline –abbrev-commit -S “line unknown”
f2fe2fb Changing something
$ git log -S “line unknown”
commit f2fe2fb3263a56a04095b2d275c88fef65047ae0
Author: scz <scz@debian>
Date: Thu Mar 10 13:35:28 2022 +0800
Changing something
查看指定commit
$ git show f2fe2fb
commit f2fe2fb3263a56a04095b2d275c88fef65047ae0
Author: scz <scz@debian>
Date: Thu Mar 10 13:35:28 2022 +0800
Changing something
diff –git a/gittest.txt b/gittest.txt
index 2805227..7d86da4 100644
— a/gittest.txt
+++ b/gittest.txt
@@ -2,4 +2,4 @@ line 0
line 1
line 2
line 3
-line 4
+line unknown
上述commit将”line 4″改成”line unknown”
本项目只有一个文件,实际项目会有很多文件,可以对指定文件使用”git log”。
查看修改指定文件的历次commit
$ git log — gittest.txt
$ git log –pretty=oneline –abbrev-commit — gittest.txt
3) git bisect
第2小节比较简单,用”git log”直接定位引入错误的commit。更多时候无法通过静态源码审阅判断哪些变动引入错误,可能在运行时与预期不符,尚不知道哪些代码逻辑的变动引入了错误,想先定位引入错误的那次commit,此时可以尝试”git bisect”。
$ git log –pretty=oneline –abbrev-commit
$ git log –oneline
0c65554 (HEAD -> master) Adding line 6
1c307d8 Adding line 5
f2fe2fb Changing something
adf61e1 Adding line 4
c40a709 Adding line 3
a1fbd78 Adding line 2
9203e57 Adding line 1
33ca65a Adding line 0
假设HEAD时通过实际运行判定已经引入错误,假设初始33ca65a时没有错误。
$ git bisect start HEAD 33ca65a
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[c40a709629605c163d14639a327803982a7312c9] Adding line 3
当前commit已切换
$ git log –oneline
c40a709 (HEAD) Adding line 3
a1fbd78 Adding line 2
9203e57 Adding line 1
33ca65a (refs/bisect/good-33ca65a950c155cf53cf275e0ebf7f90ecff32d8) Adding line 0
查看源码,对应当前commit
$ cat gittest.txt
line 0
line 1
line 2
line 3
本小节假设”grep unknown gittest.txt”对应源码审阅、编译、运行、测试过程,若有命中表示有错,若无命中表示无误。
$ grep unknown gittest.txt
(无输出)
在当前commit测试,无误,对”git bisect”进行good标记
$ git bisect good
Bisecting: 1 revision left to test after this (roughly 1 step)
[f2fe2fb3263a56a04095b2d275c88fef65047ae0] Changing something
当前commit已切换
$ git log –oneline
f2fe2fb (HEAD) Changing something
adf61e1 Adding line 4
c40a709 (refs/bisect/good-c40a709629605c163d14639a327803982a7312c9) Adding line 3
a1fbd78 Adding line 2
9203e57 Adding line 1
33ca65a (refs/bisect/good-33ca65a950c155cf53cf275e0ebf7f90ecff32d8) Adding line 0
查看源码,对应当前commit
$ cat gittest.txt
line 0
line 1
line 2
line 3
line unknown
进行源码审阅、编译、运行、测试
$ grep unknown gittest.txt
line unknown
在当前commit测试,有错,对”git bisect”进行bad标记
$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[adf61e1ef557b1c5a286dcbfb4cdc3d4fbede87c] Adding line 4
当前commit已切换
$ git log –oneline
adf61e1 (HEAD) Adding line 4
c40a709 (refs/bisect/good-c40a709629605c163d14639a327803982a7312c9) Adding line 3
a1fbd78 Adding line 2
9203e57 Adding line 1
33ca65a (refs/bisect/good-33ca65a950c155cf53cf275e0ebf7f90ecff32d8) Adding line 0
查看源码,对应当前commit
$ cat gittest.txt
line 0
line 1
line 2
line 3
line 4
进行源码审阅、编译、运行、测试
$ grep unknown gittest.txt
(无输出)
在当前commit测试,无误,对”git bisect”进行good标记
$ git bisect good
f2fe2fb3263a56a04095b2d275c88fef65047ae0 is the first bad commit
commit f2fe2fb3263a56a04095b2d275c88fef65047ae0
Author: scz <scz@debian>
Date: Thu Mar 10 13:35:28 2022 +0800
Changing something
gittest.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
输出首行有”the first bad commit”,表示这次commit首次引入目标错误,查看之
$ git show f2fe2fb3263a56a04095b2d275c88fef65047ae0
(略)
退出”git bisect”
$ git bisect reset
Previous HEAD position was f2fe2fb Changing something
Switched to branch ‘master’
当前commit已切换
$ git log –oneline -1
0c65554 (HEAD -> master) Adding line 6
“git bisect”对[good,bad]区间的历次commit进行二分查找,”git bisect”只是不断切换当前commit,并不负责源码审阅、编译、运行、测试。对”git bisect”进行good、bad标记需要依赖其他技术手段,本例简化为”grep unknown gittest.txt”有无命中。
小结整个过程
git bisect start HEAD <some>
<check and test your code>
git bisect good
<check and test your code>
git bisect bad
<repeat>
git bisect reset
4) git blame
假设已知gittest.txt是存在错误的源码,可用”git blame”对之进一步剖析。
显示指定文件中各行最后一次修改的commit记录
$ git blame gittest.txt
^33ca65a (scz 2022-03-10 13:35:28 +0800 1) line 0
9203e57b (scz 2022-03-10 13:35:28 +0800 2) line 1
a1fbd78d (scz 2022-03-10 13:35:28 +0800 3) line 2
c40a7096 (scz 2022-03-10 13:35:28 +0800 4) line 3
f2fe2fb3 (scz 2022-03-10 13:35:28 +0800 5) line unknown
1c307d86 (scz 2022-03-10 13:35:28 +0800 6) line 5
0c65554f (scz 2022-03-10 13:35:29 +0800 7) line 6
输出分为几列,依次是
commit hash | author name | date | line number | line content
以^号打头的行表示自第一次commit后从未修改。
可以为”git blame”指定行范围
$ git blame -L 3,6 gittest.txt
a1fbd78d (scz 2022-03-10 13:35:28 +0800 3) line 2
c40a7096 (scz 2022-03-10 13:35:28 +0800 4) line 3
f2fe2fb3 (scz 2022-03-10 13:35:28 +0800 5) line unknown
1c307d86 (scz 2022-03-10 13:35:28 +0800 6) line 5
显示长格式”commit hash”
$ git blame -l -L 5,5 gittest.txt
f2fe2fb3263a56a04095b2d275c88fef65047ae0 (scz 2022-03-10 13:35:28 +0800 5) line unknown
假设静态审阅源码发现某行代码引入BUG,想知道哪次commit改动该行,”git blame”就派上用场了。github界面上有blame操作。
5) fix bug
5.1) 切换到指定commit
$ git checkout f2fe2fb3263a56a04095b2d275c88fef65047ae0
$ git branch
* (HEAD detached at f2fe2fb)
master
查看当前commit的源码
$ cat gittest.txt
line 0
line 1
line 2
line 3
line unknown
5.2) 新建本地分支
若是实际项目,可基于指定commit新建名为new的本地分支
$ git checkout -b new f2fe2fb3263a56a04095b2d275c88fef65047ae0
Switched to a new branch ‘new’
查看分支,带星号的是当前分支
$ git branch
master
* new
查看有BUG的文件
$ cat gittest.txt
line 0
line 1
line 2
line 3
line unknown
修改有BUG的文件
$ sed -i -e ‘s/line unknown/line fix bug/g’ gittest.txt
$ cat gittest.txt
line 0
line 1
line 2
line 3
line fix bug
提交修改
$ git add -A && git commit -q -m “Fix bug”
$ git log –oneline
71db18f (HEAD -> new) Fix bug
f2fe2fb Changing something
adf61e1 Adding line 4
c40a709 Adding line 3
a1fbd78 Adding line 2
9203e57 Adding line 1
33ca65a Adding line 0
放弃修改,回滚至指定commit
$ git reset –hard f2fe2fb
HEAD is now at f2fe2fb Changing something
$ git log –oneline
f2fe2fb (HEAD -> new) Changing something
adf61e1 Adding line 4
c40a709 Adding line 3
a1fbd78 Adding line 2
9203e57 Adding line 1
33ca65a Adding line 0
$ cat gittest.txt
line 0
line 1
line 2
line 3
line unknown
回滚操作很危险,谨慎使用
将本地new分支推到远程
$ git push –set-upstream origin new
本例没有origin远程分支,上述命令不会成功,只是YY示意。
5.3) 切换分支
$ git checkout master
$ git switch master
Switched to branch ‘master’
新版git推荐用”git switch”、”git switch -c”切换分支
$ cat gittest.txt
line 0
line 1
line 2
line 3
line unknown
line 5
line 6
三、Dirty Pipe
下面只是基于[3]实操一下git,与漏洞挖掘、漏洞分析无关。
1) git下载Linux内核源码
cd /mnt/z/work
git clone https://github.com/torvalds/linux.git linux
cd /mnt/z/work/linux
git pull
2) 寻找引入”PIPE_BUF_FLAG_CAN_MERGE”的commit
$ git log -S “PIPE_BUF_FLAG_CAN_MERGE”
commit f6dd975583bd8ce088400648fd9819e4691c8958
Author: Christoph Hellwig <hch@lst.de>
Date: Wed May 20 17:58:12 2020 +0200
pipe: merge anon_pipe_buf*_ops
All the op vectors are exactly the same, they are just used to encode
packet or nomerge behavior. There already is a flag for the packet
behavior, so just add a new one to allow for merging. Inverting it vs
the previous nomerge special casing actually allows for much nicer code.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
warning: inexact rename detection was skipped due to too many files.
warning: you may want to set your diff.renameLimit variable to at least 779 and retry the command.
下列命令相比前述命令,不会额外输出有价值信息,只是消掉了两条警告
$ git -c “diff.renamelimit=779” log -S “PIPE_BUF_FLAG_CAN_MERGE”
显示简版输出
$ git -c “diff.renamelimit=779” log –pretty=oneline –abbrev-commit -S “PIPE_BUF_FLAG_CAN_MERGE”
f6dd975583bd pipe: merge anon_pipe_buf*_ops
3) 查看指定commit
$ git show f6dd975583bd8ce088400648fd9819e4691c8958
$ git show f6dd975583bd8
在线查看指定commit
https://github.com/torvalds/linux/commit/f6dd975583bd8ce088400648fd9819e4691c8958
https://github.com/torvalds/linux/commit/f6dd975583bd8
4) 查看指定文件中各行最后一次修改的commit记录
$ git blame fs/pipe.c | less
b24413180f560 (Greg Kroah-Hartman 2017-11-01 15:07:57 +0100 1) // SPDX-License-Identifier: GPL-2.0
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 2) /*
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 3) * linux/fs/pipe.c
…
35f3d14dbbc58 (Jens Axboe 2010-05-20 10:43:18 +0200 15) #include <linux/log2.h>
…
$ git blame fs/pipe.c | grep PIPE_BUF_FLAG_CAN_MERGE
f6dd975583bd8 (Christoph Hellwig 2020-05-20 17:58:12 +0200 461) if ((buf->flags & PIPE_BUF_FLAG_CAN_MERGE) &&
f6dd975583bd8 (Christoph Hellwig 2020-05-20 17:58:12 +0200 528) buf->flags = PIPE_BUF_FLAG_CAN_MERGE;
$ git blame -L 461,461 fs/pipe.c
f6dd975583bd8 (Christoph Hellwig 2020-05-20 17:58:12 +0200 461) if ((buf->flags & PIPE_BUF_FLAG_CAN_MERGE) &&
显示长格式”commit hash”
$ git blame -l -L 461,461 fs/pipe.c
f6dd975583bd8ce088400648fd9819e4691c8958 (Christoph Hellwig 2020-05-20 17:58:12 +0200 461) if ((buf->flags & PIPE_BUF_FLAG_CAN_MERGE) &&
github界面上有blame操作
https://github.com/torvalds/linux/blob/master/fs/pipe.c
5) 查看修改指定文件的历次commit
$ git log — fs/pipe.c
$ git log –pretty=oneline –abbrev-commit — fs/pipe.c | less
6) 作者没有演示”git bisect”判定过程
其实我最感兴趣的是作者如何进行”git bisect”判定的,但作者没有演示这部分细节。也可能他讲过pipe相关的内核实现,基于静态源码审阅进行”git bisect”判定,只是我缺乏相关基础知识,没能明白罢了。
现在这些洞要想看明白太困难了,需要很多前置知识。我是没精力去看细节了,只能看热闹。
参考资源
[2] Show commit logs
http://git-scm.com/docs/git-log
Use binary search to find the commit that introduced a bug
http://git-scm.com/docs/git-bisect
(二分查找)
Show what revision and author last modified each line of a file
http://git-scm.com/docs/git-blame
[3] The Dirty Pipe Vulnerability – Max Kellermann <max.kellermann@ionos.com>
https://dirtypipe.cm4all.com/
https://github.com/Arinerron/CVE-2022-0847-DirtyPipe-Exploit
(提到git bisect)
上述情形之外的任何使用形式,均需提前向绿盟科技(010-68438880-5462)申请版权授权。如擅自使用,绿盟科技保留追责权利。同时,如因擅自使用博客内容引发法律纠纷,由使用者自行承担全部法律责任,与绿盟科技无关。