취약점을 잘 찾고 싶은 생각에 개발 공부를 하기로 마음 먹었고 이런 저런 이유로 공개SW 컨트리뷰톤에 참여했습니다. uftrace라는 프로젝트에 6주 동안 컨트리뷰션을 진행했고 그 과정에서 배운 것들이 많아 글로 정리해보려고 합니다.

소개

공개SW 컨트리뷰톤?

공개SW 컨트리뷰톤은 기여(Contribute)와 마라톤(Marathon)의 합성어로, 여러 참여자들과 한 팀이 되어 정해진 기간 동안 팀 별로 정해진 공개SW(오픈소스)를 개발하고 기여하는 프로그램입니다. 자세한건 링크를 참고하세요.

저는 신규 취약점을 발굴하거나 기존의 취약점을 분석하는 연구에 관심이 많았는데, 취약점을 잘 찾으려면 어떻게 해야 할까에 대한 고민을 많이 했습니다. 그러던 중 규모가 큰 프로그램을 분석하다보니 개발 능력과 개발자 관점에서의 시각이 필요하다는 것을 많이 느꼈습니다. 개발 능력을 키우고자 이것 저것 시도해봤는데 공개SW 컨트리뷰톤이라는 것을 혁진님(개발 고수, git 마스터)을 통해 알게 돼서 실제 개발자들의 피드백도 구하고 개발 능력을 키우고자 참여하게 됐습니다.

20개의 오픈소스 프로젝트가 선정되어 있고 각 프로젝트별로 개발자 멘토분이 1~3명 정도 계십니다. 참여하고자 하는 사람은 참가 신청서에 원하는 프로젝트를 3지망까지 쓸 수 있고 자기 소개와 개발 경험 같은걸 썼던 것 같네요. 경쟁이 좀 있습니다.

uftrace

uftrace는 리눅스 환경에서 C/C++/Rust 언어로 작성된 프로그램에 대해 함수 흐름을 추적할 수 있는 도구입니다. 사용자 함수, 라이브러리 함수, 커널 함수, 시스템 이벤트 등에 대한 추적이 가능합니다. 기존에는 프로그램의 실행 흐름을 확인하기 위해 printf 함수를 사용해 메시지를 출력하거나 gdb 등의 디버거를 통해 흐름을 따라가는 방법을 사용했지만 uftrace는 개발자의 코드 수정 없이 각 함수별로 소용되는 시간과 함수 호출 시 전달되는 인자 정보 등을 확인할 수 있습니다. 개발자 뿐만 아니라 시스템 프로그래밍을 공부하는 학습자나 취약점을 분석하는 연구자에게도 유용하게 사용될 수 있습니다.

CppCon 2016/2017, Open Source Summit Europ 2017/North America 2017, C++ Korea 4th Seminar 2018, 삼성 오픈소스 컨퍼런스 2018 등에서 uftrace에 대한 발표가 이루어졌고 데비안 패키지에도 포함되어 있는 툴입니다.

원리

원리를 간단하게 설명드리면, 함수 호출 전과 후에 추적을 위함 함수(mcount, mcount_ret)를 각각 삽입해서 함수의 진입과 반환에 대한 정보를 기록합니다. 이 사이의 시간도 측정할 수 있습니다.

# DURATION      TID        FUNCTION
      1.446 us    [120218]  |  __monstartup();
      0.997 us    [120218]  |  __cxa_atexit();
                  [120218]  |  main()  {
      7.214 us    [120218]  |      printf();
      8.246 us    [120218]  |  } /* main */

트레이싱 결과는 대략 이런 모습입니다.

커널 함수 트레이싱

사용자가 작성하지 않은 함수에 대해서도 트레이싱 가능합니다. 라이브러리 함수나 커널 영역 함수에 대해서는 후킹을 사용하여 함수 호출 정보와 흐름을 나타내줍니다.

트레이싱 결과

트레이싱 결과는 여러가지 형태로 뽑아볼 수 있습니다. TUI, Flame graph, JSON style로 가공 가능한데 TUI 모드는 터미널이라 불편할 것 같지만 여러가지 편리한 기능들을 제공하고 있어서 굉장히 유용합니다.

이밖에도 다양한 기능들이 있기 때문에 자세한 내용은 github를 참고하세요.
uftrace github : https://github.com/namhyung/uftrace
uftrace tutorial : https://uftrace.github.io/slide/#1

uftrace 컨트리뷰터 되기!

이전에는 git을 코드 저장소 정도로만 인식했고 단순히 github에 코드를 올려놓는 정도만 해본 상태였는데 이번 기회에 버전 관리와 협업에 대해서도 배우고 git과 github의 소중함을 많이 깨달았습니다. 여러가지 문제를 겪고 해결해나가면서 git을 드디어 제대로 사용해본 것 같습니다.

컨트리뷰션 방법

1. 준비하기

fork
github 사이트에서 uftrace 프로젝트를 fork 합니다.

$ git clone https://github.com/namhyung/uftrace

작업할 서버에서도 git clone을 이용해서 프로젝트를 받아옵니다.

2. 프로젝트 빌드하기

$ cd uftrace
$ sudo misc/install-deps.sh
$ ./configure
$ make
$ sudo make install

3. Git 사용하기

3-1. remote 설정

$ git remote add origin https://github.com/namhyung/uftrace
$ git remote add my_route [1번에서 fork 된 repository 주소]

originmy_route는 레파지토리에 대한 이름입니다. 다른 이름으로 설정하셔도 됩니다.

3-2. branch 생성
코드를 개발할 때는 master branch에서 작업하지 말고 pull request 단위로 새로운 branch를 생성해서 작업하는 것이 관리하기 좋습니다.

// branch 생성
$ git branch [branch 이름]
// branch 전환
$ git checkout [branch 이름]

3-3. 코드 수정
새로운 기능을 추가하거나 기존의 기능을 개선하는 등 코드를 수정하는 단계입니다. 뭘 추가하고 뭘 개선해야 할지 정말 막막했었는데 이것에 대한 팁은 밑에서 다시 다루겠습니다.

3-4. 수정 내용 반영

// 파일 단위로 추가하기
$ git add [파일명1] [파일명2] ...

// 폴더 단위로 추가하기
$ git add [폴더명]

// 모든 파일
$ git add *

git add *의 경우 원치 않는 파일이 추가될 수 있기 때문에 되도록이면 파일명을 직접 입력하거나 폴더명을 지정하는 것을 추천합니다.

// 잘 추가됐나 확인하기
$ git status

git status 명령어를 사용하면 add 된 파일명들이 나타납니다.

3-5. 커밋 메시지 작성

$ git commit -s

nano 또는 vi 화면이 열립니다. 거기서 커밋 메시지를 작성할 수 있습니다.

1: 첫줄은 커밋 제목, 깃허브에서도 제목으로 사용되니 잘 작성해야 됨
2: 제목 다음은 공백을 둠
3: 커밋 내용에 대한 설명을 알아먹게 작성
4: 공백
5: "Fixed: #[이슈번호]" 라고 작성하면 깃허브에서 이슈 번호에 해당하는 이슈와 자동으로 연결 되어 표시 됨 (이슈 해결에 대한 커밋일 경우 이렇게 Fixed 표시)
6: 공백
7: "Signed-off-by: Random J Developer <random@developer.example.org>"

uftrace 커밋 메시지 작성 요령입니다. 커밋 메시지를 작성하는 방식은 프로젝트마다 다르니 각 프로젝트의 CONTRIBUTING.md 파일을 참고하세요! 서로 다른 스타일의 개발자들이 하나의 프로젝트를 같이 작업하다보면 코딩 스타일이나 커밋 스타일 등이 서로 달라서 문제가 발생할 수 있습니다. 이 파일에는 해당 프로젝트가 어떤 스타일을 따라야하는지 적혀있으니 꼭 참고하셔야 합니다. uftrace의 CONTRIBUTING.md에는 커밋 스타일 외에도 else if의 위치와 python에서의 indent 규칙이 적혀있습니다.

git commit -s에서 -s옵션이 7번째 줄의 Signed-off-by를 자동으로 추가해주는 옵션입니다. 미리 설정된 이름과 이메일 정보를 사용하기 때문에 아래의 작업이 먼저 필요합니다.

$ vi ~/.gitconfig

...
[user]
		email = 98nba@naver.com
		name = MinJeong Kim
...

.gitconfig파일의 내용 중 [user]부분에 위와 같이 이메일과 이름을 추가할 수 있습니다. 이때 이름은 실명으로 작성해야 합니다. uftrace에서 요구하는 기준입니다.

만약 커밋 메시지를 수정하고 싶거나 추가하고 싶은 경우에는 아래 명령어를 사용합니다.

$ git commit --amend

수정 내용에 대한 빠른 이해와 유지 보수를 위해 커밋은 기능 단위로 작성하는 것이 좋습니다.

4. pull request 보내기

$ git push remote my_route

여러 사람이 협업하는 프로젝트이기 때문에 원본 레파지토리(origin)에 직접 작업하지 말고 fork 해온 레파지토리(my_route)에서 작업하고 검토 후 원본 레파지토리로 pull request 보내는 것이 좋습니다. my_route로 pull request를 보낸 후 github 사이트에 접속하여 uftrace 레파지토리에 들어가면 원본의 uftrace 레파지토리로 pull request를 보내겠냐는 버튼이 생깁니다. 그 버튼을 클릭하면 커밋 내용과 코드의 변경 내용을 검토할 수 있고 최종적으로 send(맞나?) 버튼을 누르면 pull request 전송이 완료됩니다.

저는 검토 과정에서 커밋 실수나 코드 실수, 코딩 스타일 불일치와 같은 문제를 발견한 적이 많아서 꼭 검토하고 보냅니다.

5. 메인테이너의 반응 기다리기
pull request까지 완료했으면 메인테이너의 피드백을 기다립니다. 수정된 코드가 바로 받아들여질 수도 있지만 프로젝트의 방향과 맞지 않거나 개선할 여지가 있다면 피드백이 달립니다. 주눅들지 말고(이게 참 어렵습니다..) 의견을 나누고 다시 코드를 수정하면 됩니다.

컨트리뷰션 팁

뭘 수정하나요? ‘-‘..

우선 uftrace internal에 대한 이해가 필요합니다. uftrace를 처음 접했기 때문에 기능을 하나씩 사용해봤고, 동작 원리를 이해하기 위해 관심 있는 기능 몇 가지를 골라 코드를 따라가보기도 했습니다. 그래도 뭘 수정해야 할지 새로운 issue를 발견하기 어렵다면 이미 올라와 있는 issue들을 해결하면 됩니다.

issue 중에서도 good first issue라는 태그가 달린 issue는 해결하기 쉬운 내용입니다. 고맙게도 프로젝트에 처음 접근하는 사람들을 위해 해결하기 쉬운 issue들을 남겨놓은 것입니다.

또, 컨트리뷰션에는 코드 개발만 있는 것이 아닙니다. 사용자와 개발자들을 위해 프로젝트의 문서화 및 번역을 진행하거나 옥의 티가 될 수 있는 오탈자를 바로 잡을 수도 있습니다.

새로운 issue 찾기

issue를 찾고 싶어서 계속 시도하다가 버그/시큐리티 패치 쪽이면 할 수 있겠다 싶어서 인풋을 조작하면서 취약점 찾듯이 찾아봤더니 issue를 꽤 찾을 수 있었습니다.

쓰고 보니 별로 팁이랄 것도 없네요.

주눅들지 말기

지나친 신중함은 오히려 활동에 있어 장애다. 따라서 오픈소스 프로젝트를 즐기는데 있어서 필수 조건은 ‘고민하지 말고 무조건 저질러라’이다. 코드 한 줄, 패치 하나가 아까운 상황에서 개발자들의 시도는 오픈소스 프로젝트에 큰 도움이 된다고 한다.
.
‘이걸 올려도 정말 될까’같은 고민은 하지 않길 바란다
많은 개발자들이 자기가 열심히 코드를 만들어 놓고도 올리는데 부끄러워 하는 경우가 있는데, 자신있게 코드를 올릴수록 배울 수 있는 기회가 더 많아지니 주저하지 말라
.
[현장] “얘기해봐요, 오픈소스란…”

많은 시니어 개발자들 사이에서 질문 하나, 코멘트 하나 남기기가 그렇게 어려웠습니다. 커밋을 보내는데 계속 고민하고, 다 작성해놓고 이렇게 보내도 될까 또 고민하고 그러다보니 첫 커밋을 하는데 시간이 꽤 오래 걸렸습니다. 실수하더라도 시도해봐야 많이 배울 수 있습니다.

git 팁

git은 add, commit, push만 알면 되는 줄 알았습니다..
git을 사용하면서 여러 문제를 겪었고, git에 어떤 기능이 있는지도 몰라서 처음엔 멘붕이었습니다. 하지만 호들갑 떨면서 망했다고 혁진님한테 말하면 해결법을 알 수 있습니다.

아래는 제가 겪었던 문제들과 그런 상황에서 git의 어떤 기능들을 활용할 수 있는지 정리한 내용입니다. 명령어의 자세한 사용법은 굳이 다루지 않았습니다. 또한 문제 상황에 대해 해결 방법은 여러가지 존재할 수 있습니다.

커밋 합쳐주세요

코드 수정 요청을 받아서 다시 add, commit, push를 했더니 커밋이 두 개가 되어버렸습니다. 두 커밋이 같은 기능에 대한 내용이기 때문에 커밋을 하나로 합쳐(squash)달라는 요청을 받았습니다. 이럴땐 git rebase를 사용해서 커밋 메시지 여러개를 하나로 합칠 수 있습니다.

변경한 커밋을 push하려고 하면 문제가 발생합니다. 로컬 저장소에 존재하지 않는 커밋(처음에 push한 커밋)이 원격 저장소에 존재한다면 push 요청이 거절됩니다. 이런 경우엔 git push --force 또는 -f를 사용하여 force push를 할 수 있습니다. 원격 저장소에 로컬 저장소의 커밋 내용을 덮어씌우는 옵션입니다. 굉장히 많이 사용했습니다.

커밋 나눠주세요

코드를 수정하고 test case도 추가해서 하나의 커밋으로 전송했습니다. 하지만 test case에 대해서는 커밋을 따로 작성해야 하기 때문에 커밋을 두 개로 나눠달라는 요청을 받았습니다. 이때는 git reset을 사용하여 이미 작성한 커밋 메시지를 삭제하고 각각의 파일을 따로 add/commit 하여 두 개의 커밋으로 만들었습니다. 옵션에 따라 commit만 삭제할 수도 있고, commit을 삭제하고 파일을 add 하기 전으로 되돌릴 수도 있습니다.

커밋을 잘못 삭제했다

rebase나 reset을 사용하면서 커밋을 잘못 삭제하는 경우도 발생했습니다. 하지만 이러한 git 이력들이 보관되고 있기 때문에 복구 가능합니다. git reflog 명령어를 사용하면 작업한 git 이력들을 확인할 수 있고 여기서 복구할 작업의 commit hash를 확인할 수 있습니다. git reset --hard [commit hash]를 사용하여 복구합니다.

원격 저장소에 변경 사항이 발생했다

issue를 해결하기 위해 branch를 만들어서 개발중이었는데 원격 저장소에 새로운 코드가 추가되거나 기존 코드가 변경되는 등 수정사항이 발생할 수 있습니다. 또는 커밋을 전송했는데 수정 요청을 받고 수정하던 중에도 원격 저장소의 내용이 수정될 수 있습니다. 이런 상황에서 내 branch를 push하게 되면 로컬 저장소에 없는 커밋이 존재하기 때문에 push 요청이 거절됩니다. 다른 사람이 작업해서 merge 된 내용이기 때문에 force push로 해결해선 안됩니다. 내 branch에도 원격 저장소의 변경 내용이 반영되도록 해야합니다.

이때는 git reset --soft을 통해 commit을 되돌리고 git stash로 작업중인 파일을 보관한 후, git pull origin master로 원격 저장소의 소스를 가져옵니다. 그 후 git stash pop으로 보관된 작업 파일을 복원하고 다시 add/commit을 합니다.

하지만 원격 저장소의 수정 사항과 내가 작업한 수정 사항이 같은 파일에서 발생했다면 conflict가 발생합니다. 두 개의 수정 사항 중 어느것을 반영해야할지 알 수 없기 때문입니다. conflict가 발생한 파일을 열어보면 충돌 정보가 추가되어 있습니다. 일일이 확인해서 수정해주어야 합니다.

마무리: 컨트리뷰톤 후기

6주동안 uftrace 멘토님들과 메인테이너님에게 배운 것이 많습니다. 버그를 발견해서 패치를 작성했는데 개발자 관점에서 제가 생각하지 못한 부분을 짚어주시기도 하고 개발 관련해서 다양한 팁들을 얻었습니다. 이런 내용들을 기록해두고 싶었는데 갑자기 생각나서 횡설수설 정리해봤습니다. 포함하지 못 한 얘기가 많은데 다음에 또 갑자기 생각나면 잘 정리해서 써보겠습니다.

마지막으로, 6주 동안 진행한 컨트리뷰션 최종 보고서 입니다.
[19’ 김민정] 최종보고서