标题: 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 Date: Thu Mar 10 13:35:28 2022 +0800 Changing something 查看指定commit $ git show f2fe2fb commit f2fe2fb3263a56a04095b2d275c88fef65047ae0 Author: scz 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 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 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 Signed-off-by: Al Viro 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 https://dirtypipe.cm4all.com/ https://github.com/Arinerron/CVE-2022-0847-DirtyPipe-Exploit (提到git bisect)