Kernel Mainline 에서 Live patching 에 대한 기능을 추가하는 작업은 여러해 동안 이루어져 왔고, 마침네 4.0 커널에 기본적인 Live patching support 기능을 추가하여 배포하게 되었다.
문제는 이 Live patching 에 대한 Validation 매카니즘을 어떻게 구현하는 것이 옳을까에 대한 문제에서 시작된다.
이번 Linux Plumber Conference 2016 에서 해당 내용을 주제로 토론하였다.
이 글은 토론의 내용을 요약하거나 정리해서 알려주는 기사가 아니라, 라이브패치를 개발하는 개발자들이 나아가야 하는 방향과 해결해야 하는 과제, 극복과정 등을 중점으로 작성되었다.
Unhelpful optimizations (최적화 로 쓰고 계륵이라고 한다.)
Smart optimization 기능은 컴파일러에서 제공되는 개발자들을 위한 아주 중요한 기능이고 안쓰면 안되는 기능에 속하지만, 이게 너무 똑똑해도 탈이다.
커널의 동시성을 중요시하는 컴파일러 개발자들은 너무 극단적인 최적화에 몰입해 있었는데 Miroslav Benes 에 따르면, 라이브패칭 개발자도 이와 같이 극단적 최적화문제를 염두해 두어야 할 필요가 있다고 한다. 그는 컴파일러의 최적화 기능은 컴파일 시 작성된 코드를 미묘하게 변경할 수 있고, 이는 멘붕(대혼란-Mayhem) 을 일으킬 수 있다고 주장하였다.
어려운건 제끼고 그나마 '쉬운' 부분부터 이야기하자면,
최적화 옵션으로 인해 모든 함수에 대한 호출자를 위한 패치가 라이브패치 결과단계에서 한번 더 수정(패치)되야 하는 문제이다.
Benes 는 몇가지 최적화 옵션들이 가져오는 문제를 리스트 하였다.
-fpartial-inlining 옵션은 함수의 일부를 인라인화 시켜 복잡한 문제를 발생할 수 있고, -fipa-src 옵션의 경우는 사용되지 않는 함수파라메터를 없애버리거나 변경해 버리게 되므로 상황을 더 미묘 꼬아 버릴 수 있다는 것이다. 이것들은 ABI 를 변경해버리는 등의 일들을 초래할 수 있다고 한다. -fipa-pure-const 옵션을 사용할 경우, 만약 어떤 함수의 동작을 변경하게 되면 이 호출자들을 다시한번 변경해야 할 수 있다.
"돌아버리게" 만드는 옵션은 동일한 내용의 코드를 폴딩해주는 옵션인, -fipa-icf 이라고 하고있으며, 이 옵션으로 인해 전혀 다른 함수의 내용으로 바뀌어 버릴 수 있고, 심지어 언더/오버플로우를 일으킬 가능성도 생긴다고 설명했다
그는 최적화를 위해 사용한 옵션에 대한 기록을 요청하는 것이 필요할 수 있다고 하였다.
그는 가장 끔찍한 옵션으로, -fipa-ra 를 언급한다. 이 옵션은 Register 에 대한 최적화 옵션이고, 이 옵션으로 인해 호출된 함수에서 변경이 발생하는 경우를 감지하기 어렵다고 하며, 이는 Live patch 의 효율(The continuous uptime) 을 매우 감소시키는 영향을 미친다고 한다.
물론 위 옵션은 ftrace 서브시스템을 사용하거나 -pg 옵션을 사용할 경우 기본적으로 Disable 되지만, -pg 옵션이나 ftrace 시스템을 반드시 사용하도록 만들어야 할 당위성이 없어서 언제든지 바뀔 수 있는 점이라고 한다.
여기까지는 오로지 Benes 가 나열한 라이브패치를 어렵게 만드는 최적화 옵션일 뿐이었다
이는 컴파일러 개발자들이 더욱 더 극한의 최적화를 위해 개발할 경우 악화될 수 있는 문제임을 전하였다.
Patch building ( 패치 개발하기 )
현재 제공되는 커널은 라이브패치를 적용함에 있어 표준적인 방법만 제공할 뿐 구체적인 라이브패치 제작에 관련된 매카니즘은 제공되지 않고있다.
Josh Poimboeuf 에 의해 몇가지 오픈소스 툴들이 소개되었다.
첫번째로, kpatch-build 이다. 이것은 패치여부에 상관 없이 패치된 커널을 빌드하고 해당 빌드된 커널의 Binary 비교 검색.. 차이점을 추출하여 "프랑켄슈타인 커널모듈" 이라는 라이브패치 모듈에 패키징한다. 사용자는 이 새로운 프랑켄슈타인 모듈만 내렸다 올리면 되며, 앞서 언급된 최적화부분에 대한 고려 없이도, 문제점들 대부분을 모두 처리할 수 있는 가장 강력하고 많은 이점을 지닌 간단한 방법이라고 설명했다.
단점은 "매우" 복잡하다는 것!
이 툴은 커널에 대한 컴파일 옵션이나 섹션들에 대한 깊은 이해가 필요하며 오로지 "X86" 아키텍쳐에서만 동작하는 데다가, 멀티 아키텍쳐마다 커널 섹션이 차이가 나므로 표준도구로 사용되기에는 불가능하다는 "극악"한 부분을 갖고 있다고 한다.
게다가...... 소개해준 Poimboeuf 스스로가 이 툴은 유지보수가 개 어렵고 꼬일 가능성이 많다고 한다.....(ㅋㅋㅋ)
** Notes: 사실 오라클 ksplice 도 비슷한 방식의 스택을 제공하긴 하는데, 자칫 빌드를 잘못하면 이전 적용 스택이 없어지는 경우가 발생하곤 한다. 이는 현재와 앞으로도 발생하지 않도록 수정되었다.
다른 툴은 일반적인 kernel build 시스템과 모듈제작 방식을 이용하는 것이다. 이는 표준적인 모듈 제작 방식으로 변경점들을 새로운 모듈로 등록하여 load 하는 방식이다. 하지만 이 방식은 일부 symbolic 들에 대한 접근이 불가능한 단점이 있다.
물론 이 단점은 kallsyms_lookup_name() 이라는 커널 함수를 이용하면 극복 할 수 있지만, 오류발생이 많고 느리며, "토나오는" 방법이라고 설명했다.. (ㅎㅎㅎ)
그는 기존의 kernel Build system 를 이용하는 표준적 방식을 사용하면서도 위에서 언급한 일부 symbol 들에 대한 접근까지 해결하는 방식을 설명하였다.
하지만........ 그 방법에 대한 관심을 보이는 이는 거의 없었다고 한다... (저자가 직접 그렇게 표현했다 물론 좀 더 부드럽게지만 ㅎㅎㅎㅎ 결국 지꺼 쓰자는 이야기..)
** Notes : 링크된 메일링리스트를 보면 결국 kallsyms_lookup_name 함수를 교묘하게 비틀어 쓰는 매크로로... 의미 없어보인다. 채택여부는 미지수.. 알아서들 하겠지 뭐. 난 지금 그쪽 개발 안하니 -_-
Module dependencies (모듈 의존성)
** Notes : 개인적으로 Live patch 에서 꽤 중요한 부분에 속한다고 생각한다.
라이브 패치를 통해 모든 로드가능한 모듈에 패치를 적용 할 수 있는데(커널모듈). "만약 라이브 패치(라이브패치모듈)가 적용되는 당시에는 없던 모듈이, 라이브패치 이후 로드된다면 어떻게 해당 모듈에 대한 수정을 할 것인가?" 에 대한 질문이 생길 수 있다.
Jessica Yu 는 이 부분을 해결할 수 있는 매카니즘이 이미 구현되어 있다고 하며, 어떤 모듈이 로드되기 전에 라이브패치 모듈이 해당 모듈을 패치하기 위해서는 상당한 양의 정보와 인프라가 필요한데 이것은 일반적인 모듈 로드 형태로는 가능하지 않아, 라이브패치에서 이 모듈 로드 부분을 우회하는 코드등 상당부분을 추가할 수 밖에 없었다고 설명했다.
이 부분에 대한 아이디어들이 몇가지 이야기 되었다.
첫번째로, 라이브패치 모듈을 적용하기 전에 사용가능한 모든 모듈들을 강제로 리스트업(로드) 하여 적용하는 방법 ( Brute 한 부분이 있다 ),
이 방법은 Live patch 입장에서는 효율적이며 간단하다고 할 수 있지만, 리눅스 자체에서 필요한 의존되는 모든 모듈들을 찾아 강제로 로드할 수 있는 방법이 없다고 한다. depmod 는 종속성에 대한 분류들을 인식하지 못한다. ( net/scsi/fs 같은 종류 )
FreeBSD 에서는 MODULE_DEPEND() 라는 매크로를 구현하여 적용이 가능하지만, 리눅스에서는 이 부분이 필요없고 구현되지 않았다고 한다.
** Notes : 결론은 적용안됨.
두번째로, 라이브패치 모듈을 여러 커널모듈단위로 분할하여 각 분야의 커널 모듈을 관리하게 하는 것이라고 한다... 쉽게 설명하자면, Live patch 관련 모듈을 HBA/FS/Block/Net 등 세부 커널 모듈 분야별로 쪼갠 Child module 을 만들어 각 해당 모듈들이 필요할때 reload 되도록 하자는 것. - 마치 xenblk 등 pv drivers 같은...
두번째 방법의 문제는 다양한 범주의 변경이 필요할 경우 문제가 발생할 수 있다고 한다. 그는 CVE-2016-7097 를 예로 들면서 다양한 계층의 API 변경이 필요한 패치의 경우 Load 될 모듈에 대한 목록이 길어지거나 복잡해 질 수 있다고 한다.
** Notes : 역시 PV driver 같은 케이스..
결론은, 이 방법 역시, 이미 지금 구현한 것도 사실상 복잡하고 비용이 많이 들었는데, 더 복잡하게 구현 할 필요가 없으므로, (거부하겠음!) 이라는...
이렇게 모듈 종속성 부분들에 대한 효율적 구현등에 대해서 활발한 토의가 이루어 지긴 했지만,
현재와 같이 Live patch 에서 모듈 로드와 코드 복제, 적용에 대한 새로운 매카니즘을 따로 제작하여 사용하고 있는 부분에 큰 문제들은 없고 대체할 방법이 딱히 없을것으로 합의했다고 전했다.
( 이 섹션은 사실 모듈동작에 대한 복잡한 설명이 필요해서 그냥 생략하기로 했음.. 귀찮.. 기사 댓글에서도 이해못하겠다는 이야기가 나옴-_-)
Other topics
논의 된 다른 주제들로는,
1. 구조체 변경 구조체에 대한 변경을 구현하는 방식에 대해서 Cast 형태로 숨겨진 변수들을 감지하거나 수정하는 것을 고려해야 하는데, 이에 적합한 기법인 Shadow structure 등의 기법은 비용 및 성능면에서 비효율적이고, 라이브패치가 해제될 때 기존으로 되돌리는 방법이 매우 어렵다는 이야기가 있었다.
2. Scheduler 관련 스택 반환에 대한 보안이슈 - Miroslav Benes 이 이 부분에 대해 컨텍스트 스위칭 시, 컨텍스트 포인트를 추가/변경 부분을 비교하여 적용하는 방식등의 해결책을 제시했고 이와 관련되어 Jiri Kosin, Mel Gorman 등이 참여하고 있고 좋은 결과가 있을 것 같다고 전했다.
3. 다른 아키텍쳐에 대한 사항 - AMD64 에 대한 포팅은 Kosina 에 의해 진행되고 있으며 별다른 문제가 없다고 전함. PowerPC 는 Balbir Singh 의 질문으로 언급되었으나, 다들 별 관심은 없었다고 전함.
4. 기타 광범위한 주제들 by Balbir Singh ( 질문자인듯 ) - Userpace 에 대한 Live patch 개발 여부 : 관심있는 유저 있었음 정도로 전하고 있다.... - Cluster 환경에서의 잇점 : 클러스터 환경에서 Rolling update 대신 Live patch 를 사용하면 어떤 잇점이 있는 건가? 라는 질문을 하였다고 전한다. (답은 기사에는 없는데 뭐.. 뻔할듯... 고객이 알아서 선택....ㅎㅎ)
** Notes: 여기서는 Live Cluster update 라고 표현했는데 대충 보니 Service 를 이중화해서 한대 패치하고 난 뒤 서비스를 넘기고 다시 나머지 서버를 패치하는 방식 ( Rolling update ) 를 의미하고 있었다. 만약.. 최후의 마지막 서버의 클러스터 모듈이 상위호환이 안되어 서비스 이전이 불가능하면 어쩔??? 바보임..약간 Anoing
- 보안 관련 주제 이 Singh 이란 놈은 라이브패치 모듈로 인해 루트킷 삽입 가능성여부를 제기하였으나, Kosina 가 이미 모듈 자체를 커널에 심을 수 있다는 것 자체가 게임오버라고 어이없어했다고 전했다.....
---------
여기까지가 Corbet 의 기사를 설명한 부분이다.
아무래도 스크롤의 압박이 강하고, 복잡한 부분도 많지만, 사실상 상당히 간단히 설명한 부분이며, 해석에 오역이 있을 수 도 있다고 생각하지만,
상당히 잘 요약했다고 생각한다. (나말이야...난 잘 한거같다고... ㅎㅎㅎ)
약간 미국식 스크립트(코멘터리) 형태로 표현하긴 했는데 재미없을수도 있겠다..
그냥.. 패스... 귀찮아.. 일주일 뒤 공개되니까 그때 기사 읽어....미안 영알못이라 ㅠㅠ
그래도 요즘 보안이슈등으로 라이브패치에 대한 관심이 많아져서 흥미로운 기사라 오랜만에 포스팅한다