Unix系列(11)–git源码查错技巧

一、背景介绍

参[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)申请版权授权。如擅自使用,绿盟科技保留追责权利。同时,如因擅自使用博客内容引发法律纠纷,由使用者自行承担全部法律责任,与绿盟科技无关。

Spread the word. Share this post!

Meet The Author

C/ASM程序员