Skip to content

标题: Unix系列(11)--git源码查错技巧

创建: 2022-03-10 14:58 修改: 2022-10-13 15:14 链接: https://scz.617.cn/unix/202203101458.txt


目录:

☆ 背景介绍
☆ git源码查错技巧
    1) 准备git测试环境
    2) git log
    3) git bisect
    4) git blame
    5) fix bug
        5.1) 切换到指定commit
        5.2) 新建本地分支
        5.3) 切换分支
☆ Dirty Pipe
    1) git下载Linux内核源码
    2) 寻找引入"PIPE_BUF_FLAG_CAN_MERGE"的commit
    3) 查看指定commit
    4) 查看指定文件中各行最后一次修改的commit记录
    5) 查看修改指定文件的历次commit
    6) 作者没有演示"git bisect"判定过程
    7) 查看指定commit所属内核版本
☆ 参考资源

☆ 背景介绍

参[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 start HEAD git bisect good git bisect bad git bisect reset

"git bisect"对[good,bad]区间的历次commit进行二分切换,"git bisect"只是不断 切换当前commit,并不负责"",也即不负责源码审阅、 编译、运行、测试等等。对"git bisect"进行good、bad标记需要依赖其他技术手段, 本例简化为"grep unknown gittest.txt"有无命中。

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 <[email protected]>
Signed-off-by: Al Viro <[email protected]>

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 ...

$ 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"判定,只是 我缺乏相关基础知识,没能明白罢了。

现在这些洞要想看明白太困难了,需要很多前置知识。我是没精力去看细节了,只能 看热闹。

7) 查看指定commit所属内核版本

在线查看指定commit

https://github.com/torvalds/linux/commit/9bb48c82aced07698a2d08ee0f1475a6c4f6b266 https://github.com/torvalds/linux/commit/9bb48c82aced

在这个页面上直接看到最早引入指定commit的内核版本,本例是v5.11-rc5

$ git describe --contains 9bb48c82aced v5.11-rc5~8^2~1^2

$ git tag --contains 9bb48c82aced | head -1 v5.11

第一行的5.11是最早引入指定commit的内核版本

git show 9bb48c82aced:Makefile git show 9bb48c82aced:Makefile | head -4 git show 9bb48c82aced:Makefile | head -4 | tail -3 | awk -v ORS='.' '{print $3}' | sed 's/.*$//'

实际是拼这几个字段

VERSION = 5 PATCHLEVEL = 10 SUBLEVEL = 0 EXTRAVERSION =

☆ 参考资源

[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)