Syzkaller를 활용한 fuzzing에 대한 내용은 언젠가 따로 해둘 것이며 본 내용은 code coverage에 대해 아래의 reference들을 통해 정리 및 번역해둔 것이다.
[+] 아직 지식이 많이 부족하기에 글의 오류가 있다면 지적에 주시면 감사하겠습니다( _ _ ) .
[+] 더욱 알아가며 내용을 보충해 보겠습니다.
Kernel fuzzing을 수행해보면, dashboard에서 아래와 같이 많은데 coverage에 대해서 본 글에서는 정리할 것이다.
기본적인 상태에서는 filtered coverage가 안뜨고
아래와 같이 customizing했을때, filtered coverage가 뜬다. 그리고 filtered coverage의 파일쪽으로 control flow graph(이하 cfg) 가 형성되는 것을 볼 수 있다.
본인의 경우 특정 1-day보고서를 작성하며 fuzzing을 통해 생성되는 report를 직접 눈으로 확인해보고 싶어서 위와 같이 구성하여 시도해보는 중이었다.
하지만, 이러한 목적성 외에도 linux kernel내의 cfg에 대한 이해도가 높다거나, 본인처럼 관심있는 특정 서브시스템 같은 것이 있다면 그 쪽에 대한 contributing을 목적으로 fuzzing을 수행하기 위해 사용하기에도 용이할 것 같다는 생각이 든다.
자 이제 저 많은 dashboard 중 coverage가 뭐냐! 라고하면 주로, 정적 분석 도구에서 사용되며 코드를 어디어디를 얼마나 순회했는지 체크할 수 있는 지표(?)라고 표현할 수 있을 것 같다.
이제보니 위키피디아에도 다음과 같이 나와있다.
In computer science, test coverage is a measure used to describe the degree to which the source code of a program is executed when a particular test suite runs. A program with high test coverage, measured as a percentage, has had more of its source code executed during testing, which suggests it has a lower chance of containing undetected software bugs compared to a program with low test coverage. - wikipedia
뭐.. 내가 한말이랑 같은 말이다 ㅋㅋ
소프트웨어의 코드가 얼마나 실행되었는지 수치화시킨것이다.
그렇다면 Code Coverage는 대체 무엇을 기준으로 지표를 생성할까?
구문(statement), 조건(condition), 결정(decision)의 구조로 코드가 존재할 때, 이를 얼마나 순회(커버(?))했는가에 기반하여 측정된다.
구문(statement)
- 코드 한줄이 한번이상 실행된다면 충족된다.( linux kernel의 pipe.c파일을 예시로 설명)
{
BUG_ON(pipe1 == pipe2); //1
if (pipe1 < pipe2) { //2
pipe_lock_nested(pipe1, I_MUTEX_PARENT); //3
pipe_lock_nested(pipe2, I_MUTEX_CHILD); //4
} else {
pipe_lock_nested(pipe2, I_MUTEX_PARENT); //5
pipe_lock_nested(pipe1, I_MUTEX_CHILD); //6
}
}
가령 위의 코드를 기반으로 테스트한다 했을때, pipe1< pipe2를 만족하는 데이터를 사용한다면, if문의 조건을 만족하는 3,4번 line은 수행되고, 1,5,6번 line은 수행되지 않으므로 구문 coverage는 40%를 만족한다고 볼 수 있다.
조건(condition)
- 모든 조건식의 내부조건이 true/false를 가지게 된다면 충족된다.
{
BUG_ON(pipe1 == pipe2); //1
if (pipe1 < pipe2) { //2
pipe_lock_nested(pipe1, I_MUTEX_PARENT); //3
pipe_lock_nested(pipe2, I_MUTEX_CHILD); //4
} else {
pipe_lock_nested(pipe2, I_MUTEX_PARENT); //5
pipe_lock_nested(pipe1, I_MUTEX_CHILD); //6
}
}
여기서 내부 조건이란 말그대로 조건식 안의 조건(위의 경우, pipe< pipe2)이라고 보면 된다.
위의 경우엔, test case가 "pipe1 > pipe2", " pipe1 < pipe2" 각각에 만족하는 경우라고 볼 수 있다. "pipe1 = pipe2"의 경우 조건 문을 수행하지 않기에 제외해야 한다고 생각하여 뻈다.
뭔가 이해하기 쉽게 하기 위해선 둘에 그냥 숫자를 대입해보는게 좋은 것 같다.
가령, (1) pipe1 = 1, pipe2 = 2 / (2) pipe1 = 2, pipe2 = 1 이러한 식으로..?
대입해 보았을 때, (1) 경우는 3,4번 line이 수행되고, (2)의 경우는 5,6번 line이 수행된다. 이 경우, 조건 coverage를 만족하게 하는 test case 는 (1),(2)이다. 모두 true/false를 만족하기 때문이다.
사실 조건 coverage는 아래의 링크에서 설명하신 예시로 이해하는게 훨씬 더 도움이 될 듯하다.
https://tecoble.techcourse.co.kr/post/2020-10-24-code-coverage/
왜냐하면 조건 커버리지를 만족해도, 구문 커버리지 혹은 이후에 설명할 결정 커버리지를 만족하지 못하는 경우가 존재하기에 이에 대한 예시로는 본인이 사용한 code가 적절하지 않아서이다. 이를 위한 예시로는 사용하신 x>0 && y<0이러한 것처럼, 조건이 두개 이상 존재하는 code가 더 적절해 보인다.
결정(decision)
- 모든 조건식이 true/false를 가지게 되면 충족한다.
{
BUG_ON(pipe1 == pipe2); //1
if (pipe1 < pipe2) { //2
pipe_lock_nested(pipe1, I_MUTEX_PARENT); //3
pipe_lock_nested(pipe2, I_MUTEX_CHILD); //4
} else {
pipe_lock_nested(pipe2, I_MUTEX_PARENT); //5
pipe_lock_nested(pipe1, I_MUTEX_CHILD); //6
}
}
위의 코드와 위에서 언급했던 (1), (2)예시의 경우 조건식에 대해 true혹은 false가 존재하므로, 결정 coverage를 충족하는 code라고 볼 수 있다.
언급한 3가지 code coverage구문 중에서는 구문 커버리지가 가장 대표적으로 많이 사용되며, 조건문이 존재하지 않는 경우, 결정/조건 coverage 의 대상에서 제외된다고 한다. 또한, 라인 coverage가 가장 많이 사용되는 이유는 모든 code에 대해 실행된다는 의미이며 물론, 모든 시나리오를 순회한다는 보장은 없지만, 어떠한 시나리오에도 문제없음은 보장될 수 있기 때문이다.
syzkaller에서는 이러한 coverage를 표현하기 위해 KCOV를 활용한다.
해당 옵션을 사용하기 위해서는 커널을 구성할 때, 아래의 문장을 넣어주어야 한다.
CONFIG_KCOV=y
또한, syzkaller은 sanitizer coverage도 같이 활용하는데, 해당 부분에 있어서는 아직 더 공부가 필요하여, 참고용 링크를 남겨둔다.
https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs
이제 다시 돌아와서 얘기해보면 syzkaller는 우선, compiler에 의해 object code(compiler나 assembler에 의해 컴파일 이후에 생성된 결과물) 룰 기반으로 coverage point들을 넣어 tracing한다. 이 coverage point는 basic block혹은 cfg의 edge(cfg의 마지막 부분)를 기반으로 생성된다.
[+] basic block은 본인의 이해 방식으로는 entry point외에는 들어오는 분기(branch)가 없으며, exit(return)외에는 분기가 없는 쭈우우욱 실행되는 코드 문자열이라고 보면된다.
[+] gcc의 경우 default가 basic block, clang/Linux의 경우 defualt가 cfg edge이다.
여하튼, 이 coverage point들은 compiler에 의해 많은 변환 및 최적화 과정을 거친 후 삽입된다. 그리고 이는 당연하게도 소스코드와 많은 연관성을 가진다. 그리고 아래와 같은 모습으로 나타난 것을 볼 수 있다.
횟수야 뭐.. 아래의 글을 참고하여 code를 보면 어느정도 알 수 있겠지만, 사실 색 표시에 대한 정리를 위해 이 글을 작성하게 되었다.
https://www.kernel.org/doc/html/latest/dev-tools/kcov.html
커버리지의 지표를 색깔로 표시한게 상기의 사진에서 보인다. 물론 이 부분에 대해서도 build option에서 조절하면 더웃 정확한 수치를 확인할 수 있다고 한다.
그리고 색깔에 대한 정의는 아래의 code를 따른다.
https://github.com/google/syzkaller/blob/master/pkg/cover/report.go#L504
검정색, 주황색, 크림슨 레드(?), 회색, 빨간색 요런 색으로 표시된다.
아래의 순서대로 coverage 만족이 더 높은 순서로 numbering하였다.
1. 검정(Covered)
해당 라인이 coverage 를 만족한다는 의미(?)인 듯 하다. 왼쪽에 해당 줄과 관련된 pc값의 실행을 trigger한 프로그램이 몇개 있는지 보여준다. 그리고, 번호를 누를 시에 마지막으로 실행된 프로그램이 열린다.
2. 주황(Uncover && Cover)
몇몇의 PC값이 해당 라인과 연관있으며 모든 경우에 대해 커버리지를 만족하지는 못한다는 의미인 듯 하다. 즉, coverage를 만족하는 것과, 만족하지 못하는 것이 둘다 있는 경우를 뜻한다고 보면된다. 해당 색 역시 번호를 누를 경우 마지막으로 trigger할때 수행된 PC값들을 볼 수 있다. (block단위의 coveragepoint기준)
3. 크림슨 레드(빨강이지만 볼드체가 아닌것) (Weak- uncovered)
이 색의 경우 함수(symbol)가 어떠한 coverage를 만족하지 못한다는 의미이다. 즉 , 함수가 실행되지 않는다는 의미이다.
하지만, 해당 라인은 컴파일 시 활용되는 부분이라고 볼 수 있다.
4. 빨강(볼드체) (Uncovered)
해당 라인이 coverage를 어떠한 것(라인에 대해/ 함수에 대해)도 전혀 만족하지 못한다.
5. 회색 ( Not instrumented)
해당 라인과 연결된 PC값을 전혀 확인할 수 없거나, code를 생성하지 않을 때 나타난다고 하는데, 이는 본인 생각에 전혀 callback(다른 함수의 실행 이후 실행되는 함수)이 이루어지지 않는 code들을 의미하는 것 같다.
-ref-
https://en.wikipedia.org/wiki/Basic_block
https://tecoble.techcourse.co.kr/post/2020-10-24-code-coverage/
https://www.kernel.org/doc/html/latest/dev-tools/kcov.html
https://lwn.net/Articles/677764/
https://github.com/google/syzkaller/blob/master/docs/coverage.md
https://github.com/hardenedlinux/harbian-qa/blob/master/syzkaller/cover_filter.md
'Kernel' 카테고리의 다른 글
Smart seed selection‑based effective black box fuzzing for IIoT protocol 번역본 (0) | 2024.03.21 |
---|---|
Linux kernel - memory leak in batadv_iv_ogm_aggregate_new (0) | 2024.01.09 |
Linux Kernel Debugging (0) | 2023.10.09 |
Kernel self protection (0) | 2023.01.17 |
Fuzzing (0) | 2023.01.17 |