[특별기고] CPU 취약점 종합보고서 ①: 취약점 기본원리

초창기 CPU 구조에서부터 짚어보는 인텔 CPU게이트 사건
파이프라인·수퍼스칼라, 비순차실행·추측실행 원리분석으로
멜트다운과 스펙터 취약점의 공격원리 완전분석

[보안뉴스=노규남 가비아 클라우드사업부장 겸 최고기술책임자] 연초부터 일명 ‘CPU게이트’라는 사고가 업계를 떠들썩하게 하고 있다. 20여 년간 이런 취약점이 발견되지 않은 상태로 계속 제품들이 판매돼 왔다는 사실도 놀랍지만, 인텔의 ‘업계 모두가 비슷한 취약점을 가지고 있다’라는 물타기식 발표라든가, 긴급하게 BIOS 패치를 배포했지만 패치 자체가 문제를 일으켜 롤백하라고 했다든가, 취약점 패치가 성능저하를 일으킬 수는 있으나 전반적으로 무시할 만한 수준이라고 했지만 경우에 따라 30% 이상 성능이 낮아지는 경우가 보고됐다든가, 각자의 입장에 따른 주장과 확인되지 않은 얘기들로 업계가 매우 시끄럽다. 무엇보다 혼란스러운 건 사용자들이다. 사용자들은 업계 선도업체들이 명확한 가이드라인을 제시해줄 것이라는 초반의 기대를 어느 정도 접은 채 상황을 관망할 뿐이다.

[이미지=iclickart]

사실 이 취약점들의 발견은 누군가의 표현대로 CPU 업계에 새로운 지평이 열린 것이나 마찬가지다. 그렇기 때문에 그토록 오랜 기간 동안 확인하지 못한 것이 무리는 아니다. 하지만 실사용자 입장에서는 어떤 패치를 해야 하고 실제 어떤 위험이 있는지에 대해 누군가 명확하게 알려주었으면 하는 바람이 있을 것으로 본다. 이 취약점들은 모든 상황에서 동일하게 위험한 것은 아니며 패치에도 그에 따르는 트레이드오프가 있다. 그렇기 때문에 어떤 경우는 꼭 패치를 하기보다는 다른 보완책으로 대응하는 방법도 가능할 것이고, 어떤 경우는 패치를 하기가 매우 어려운 경우도 있을 것이다. 그렇지만 어떤 문제를 해결하기 위해서는 일단 그 문제에 대해서 잘 이해하는 일이 중요하다. 그리고 이 사안을 잘 이해하기 위해서는 지난 수십 년간 CPU가 어떤 식으로 발전해왔는지에 대해서 먼저 알아보지 않으면 안 된다.

파이프라인과 수퍼스칼라
초창기 CPU의 구조는 매우 단순하게, 지금 실행해야 하는 명령이 저장된 메모리 주소를 갖는 레지스터가 있었고(PC: Program Counter), CPU는 어드레스 버스를 통해 해당 메모리의 내용을 차례대로 읽어서 실행하는 구조였다. 모든 명령은 순차적으로 실행되며 앞의 명령이 완료되기 전에는 아무 것도 할 수 없이 대기해야 했다. 그런데 실제 명령어 실행은 하나의 분해할 수 없는 단위 작업이 아니라 메모리에서 읽기·해석·실행·결과쓰기 등의 더 작은 단위 작업으로도 나눌 수 있기 때문에, 이런 작업을 각각 분담하는 유닛을 따로 두어 전적으로 수행하는 구조로 바꾸면 훨씬 효율적이 될 수 있다. 이런 구조를 파이프라인(pipeline)이라 하고, 기존의 구조에 비해 성능 면에서 우월하다는 점이 증명됐기 때문에 지금에 와서는 시장에서 판매되는 어떤 CPU라도 이런 구조를 가지고 있다. CPU의 종류에 따라 파이프라인 깊이는 다르지만 일반적으로 Fetch-Decode-Execute-WriteBack의 네 단계는 반드시 포함되며 이것이 가장 단순화된 파이프라인 구조라 할 수 있다. 이 경우 각 단계의 기능은 다음과 같다.

Fetch : 프로그램의 실행지점이 가리키는 명령을 가져온다.
Decode : 해당 명령들을 마이크로옵으로 변경한다. 인텔과 같은 CISC구조에서는 하나의 명령이 여러 개의 마이크로옵으로 쪼개지기도 한다.
Execute : 마이크로옵을 실행한다.
WriteBack : 실행결과를 저장한다.

이때 각 단계를 담당하는 유닛들이 모두 독립적으로 작동하므로 파이프라인을 여러 개 두어 명령을 동시에 실행할 수 있는 구조로 설계하면 성능을 많이 끌어올릴 수 있는데 이런 구조를 수퍼스칼라(SuperScalar)라고 한다. 따라서 동일한 클럭의 CPU라고 하더라도 파이프라인을 몇 개 가지고 있는지, 얼마나 효율적으로 작동하고 있는지 따라 성능은 많이 차이가 난다. 이 구조에서 중요한 것은 파이프라인이 잠시라도 쉬지 않게 하는 것이다. 따라서 이 경우 Fetch 단계에서는 명령을 한 번에 하나씩 가져오지 않고 여러 개를 동시에 가져와서 실행유닛에 적재하고 한꺼번에 실행하게 한다. 만약 파이프라인의 어딘가에서 병목이 생겨서 노는 유닛이 있게 되면 그만큼 효율이 떨어지게 되기 때문에 항상 파이프라인의 모든 유닛은 꽉꽉 채워서 실행되고 있지 않으면 안 된다. 그래서 파이프라인이 도입된 이후 항상 CPU 설계자들의 주된 관심사는 파이프라인을 놀지 않게 하는 것이 됐다. 이런 필요성에 의해 뒤에 설명하는 비순차실행(Out-Of-Order-Execution)이나 추측실행(Speculative Execution)이 고안된 것이다.

[출처=https://en.wikipedia.org/wiki/Superscalar_processor]

위 그림은 수퍼스칼라 CPU의 파이프라인 예시다. 각각 IF=Instruction Fetch, ID=Instruction Decode, EX=Execute, MEM=Memory Access, WB=Register WriteBack 단계를 나타낸다.

비순차실행과 추측실행
파이프라인을 놀지 않게 하려면 당연히 실행유닛은 항상 마이크로옵을 적재해서 구동되고 있어야 한다. 그리고 현대적인 CPU는 실행유닛을 복수 개 가지고 있기 때문에 이 유닛들 중 어느 것도 쉬지 않고 명령을 계속 반복해서 실행하고 있는 상황이 가장 효율이 좋다. 그런데 어떤 상황에서는 명령을 바로 실행하지 못하고 대기해야 하는 경우가 생긴다. 파이프라인이 3개인 가상의 CPU가 있는데 다음 명령들을 실행하려고 한다고 가정해 보자.

A : 레지스터 R1과 R2를 더해서 R3에 저장한다.
B : R3의 내용이 100보다 크면 C를, 그렇지 않으면 D를 실행한다.
C : R3에서 100을 빼서 R4에 저장한다.
D : R3에 100을 더해서 R5에 저장한다.

이 경우 명령은 3개 이상이지만 모든 파이프라인을 한꺼번에 구동하는 것은 불가능하다. 왜냐하면 A의 결과를 확정하기 전까지는 B에서의 판단결과가 어떻게 될지 알 수 없고, 그에 따라 C와 D 양쪽 한군데의 명령만을 실행하게 되기 때문이다. 이때는 A와 B까지만 파이프라인에 적재한 다음 A의 결과가 나올 때까지 대기한 후 B를 실행하고, 그 결과에 따라 C나 D를 다시 파이프라인에 올려서 실행하는 과정을 거쳐야 한다. 이렇게 되면 A의 결과가 나올 때까지 파이프라인이 멈추게 되고 복수개의 파이프라인을 둔 의미가 없어진다. 만약 이 파이프라인들을 중단 없이 모두 채워서 실행할 수 있다면 훨씬 효율적인 수행이 가능하겠지만 명령 B는 명령 A의 결과에 의존적이므로 A가 실행완료 되기 전까지 C를 실행할지 D를 실행할지 판단할 수 없다. 이런 문제를 어떻게 해소할 수 있을까?

여기서 도입된 것이 추측실행(Speculative Execution)으로, 위의 예에서처럼 명령의 실행결과에 다음 명령수행이 의존적이라면 CPU는 여러 가지 데이터에 근거해 판단결과를 추측한 후 다음 명령을 파이프라인에 적재해 버린다. 추측이 맞으면 계속해서 명령을 수행하면 되는 것이고 맞지 않다면 실패한 이후의 파이프라인을 비우고 그 부분부터 다시 실행하게 한다. 따라서 추측이 정확하다면 파이프라인의 효율성이 매우 높아지고 성능은 올라간다. 앞서의 예는 추측실행을 적용하면 다음과 같이 다시 쓸 수 있다.

A : 레지스터 R1과 R2를 더해서 R3에 저장한다.
B : R3의 내용이 100보다 크면 C를, 그렇지 않으면 D를 실행한다. 그런데 이 분기의 기존 결과를 보면 C가 실행되는 경우가 많았다. 따라서 추측실행으로 C를 실행한다.
C : R3에서 100을 빼서 R4에 저장한다.

이 경우 A, B, C를 같이 파이프라인에 적재하고 실행할 수 있다. 물론 A의 결과는 동일하게 대기해야 하지만 그 결과로 인한 수행명령인 C는 이미 Fetch와 Decode를 거쳐서 실행유닛에 적재돼 있으므로 B의 결과가 추측과 일치한다면 파이프라인에 명령을 올리는 과정 없이 바로 실행할 수 있고 따라서 시간이 크게 단축된다. 만약 추측이 맞지 않았다면 그 시점에서부터의 파이프라인은 취소되고 다시 실행해야 하므로 성능 면에서 불이익이 생긴다. 하지만 대부분의 조건이나 분기는 매우 루틴하게 실행되기 때문에(반복문이라든가) 실험을 해보면 놀랍게도 이 추측실행의 적중도는 매우 높다. 그렇기 때문에 전체적으로 보면 쓰지 않는 것보다 쓰는 편이 훨씬 성능을 높이는데 도움을 줄 수 있고, 이런 이유로 추측실행도 현대적인 CPU라면 기본적으로 모두 적용돼 있다.

비순차실행(Out-Of-Order-Execution)도 추측실행과 마찬가지로 명령을 최대한 병렬적으로 많이 실행할 수 있게 하기 위해서 고안됐다. Fetch단계에서는 명령을 복수개로 가져오지만 이 명령들을 모두 순서대로 실행하게 되면 중간의 조건문이나 각 명령간의 의존성 때문에 한꺼번에 실행하지 못하는 경우가 많다. 만약 명령들의 순서를 일부 바꾸어도 최종적인 결과에 영향을 미치지 않으며 그렇게 순서를 바꿈으로써 효율을 높일 수 있다면 CPU는 그렇게 실행하게 설계돼 있다. 따라서 명령은 항상 입력한 순서대로 실행되지는 않고 최대 효율을 낼 수 있는 순서로 바꾸어서 실행하지만 결과는 순서대로 실행되는 것처럼 다시 정렬하게 된다. 현대적인 CPU라면 모두 가지고 있는 재정렬 버퍼(ROB, Re-Order Buffer)에서 Decode된 마이크로옵들의 순서를 바꾸게 되는 것이다.

▲인텔 스카이레이크 CPU의 간략화된 싱글코어 구조 [출처=https://meltdownattack.com/meltdown.pdf]

MeltDown과 Spectre 공격의 기본원리
이번에 공개된 취약점인 MeltDown과 Spectre는 이런 현대적 CPU의 실행구조 자체를 공격하는 것이다. 앞서 언급한대로 CPU는 입력된 명령을 가급적 한꺼번에 병렬적으로 실행하려고 하며 필요하다면 실행 순서를 바꾸기도 하고 가능성이 높은 명령은 추측해 미리 실행하기도 한다. 그리고 추측이 틀렸다면 파이프라인으로부터 이 명령을 지움으로써 오실행의 가능성을 막는다. 다시 말해 최대한 많이 추측해 먼저 실행하고 추측이 맞지 않는다면 취소한다, 라는 것이 기본적인 실행방식이고, 이 과정에서 실행돼서는 안 되는 명령이 실행될 수도 있으나 바로 취소돼 무효화되기 때문에 그로부터 어떤 이득을 취하거나 데이터를 유출하는 일은 불가능하다는 것이 지난 20여 년간 정설이었다.

그런데 이번에 발표된 구글 프로젝트 제로의 논문은 이 정설을 뒤집어엎은 것이다. 양 공격의 기본 원리는 아주 흡사하고 이론적으로는 모든 현대적 CPU에 다 적용된다. 다만 실제 필드에서 공격이 유효한지 아닌지는 구현의 차이나 기타 요소들의 영향을 받기 때문에 테스트해보기 전에는 판단할 수 없다. 두 개 공격의 핵심을 간단하게 정리하면 다음과 같다.

1) 데이터를 확인하기 위한 메모리를 준비하고 캐쉬를 삭제한다(Flush).
2) 공격자는 비순차실행 또는 추측실행을 통해 원래 실행돼서는 안 되는 코드를 실행하게 한다.
3) 2)의 코드는 원래 ‘실행돼서는 안 되는’ 것이기 때문에, 이 사실이 확인되는 즉시 파이프라인에서 지워지고 취소된다. 하지만 이렇게 취소돼야 한다는 사실을 알고 실제 취소하기 전까지 약간의 시간이 걸리는데 이 시간 동안을 misspeculation window라 한다. 공격자는 misspeculation window를 늘리기 위해서 접근해야 하는 메모리의 캐쉬를 리셋하는 등 다양한 장치를 사용한다.
4) misspeculation window 동안 실행된 공격자의 코드가 보호되는 정보에 접근한 후 이 값에 대응하는 1)의 메모리 부분을 읽어서 캐쉬에 남긴다. 만약 공격이 너무 느리거나 취소가 빨리 되면 캐쉬를 남기는 일은 불가능해지고, 공격은 실패한다.
5) 1)의 메모리를 차례대로 읽으면서 속도를 측정하면 캐쉬가 있는 부분을 확인함으로써 보호된 정보를 간접적으로 알아낼 수 있다(Reload). 이런 방법을 Flush+Reload attack이라 한다.

또 이 원고를 정리하고 있는 와중에 프린스턴 대학과 엔비디아에서 MeltDownPrime과 SpectrePrime이라는 새로운 공격방법에 대한 논문을 내놓았는데 기본적인 원리는 기존의 MeltDown, Spectre와 크게 다르지 않은 것으로 보인다. 다만 공격 시 Flush+Reload처럼 메모리 읽기로 캐쉬를 남기는 것이 아니라 Prime+Probe, 즉 쓰기로 인한 캐쉬 탈락으로 데이터를 유출한다는 점이 다르다. 즉 MeltDownPrime과 SpectrePrime은 위의 방식과 비교할 때 1) 4) 5)의 단계에서 약간의 차이가 나는데 같은 방식으로 정리하면 다음과 같다.

1) 데이터를 확인하기 위한 메모리를 준비하고 읽어 들여서 캐쉬에 적재한다(Prime).
2) 공격자는 비순차실행 또는 추측실행을 통해 원래 실행돼서는 안 되는 코드를 실행하게 한다.
3) 2)의 코드는 원래 ‘실행돼서는 안 되는’ 것이기 때문에, 이 사실이 확인되는 즉시 파이프라인에서 지워지고 취소된다. 하지만 이렇게 취소돼야 한다는 사실을 알고 실제 취소하기 전까지 약간의 시간이 걸리는데 이 시간 동안을 misspeculation window라 한다. 공격자는 misspeculation window를 늘리기 위해서 접근해야 하는 메모리의 캐쉬를 리셋하는 등 다양한 장치를 사용한다.
4) misspeculation window 동안 실행된 공격자의 코드가 보호되는 정보에 접근한 후 이 값에 대응하는 1)의 메모리 부분에 쓰면 그 위치의 캐쉬가 탈락된다. 만약 공격이 너무 느리거나 취소가 빨리 되면 캐쉬를 탈락시키는 일은 불가능해지고, 공격은 실패한다.
5) 1)의 메모리를 차례대로 읽으면서 속도를 측정하면 캐쉬가 없는 부분을 확인함으로써 보호된 정보를 간접적으로 알아낼 수 있다(Probe). 이를 Prime+Probe attack이라 한다.

여기서 MeltDown‘Prime’ 또는 Spectre‘Prime’이라는 이름이 붙은 이유는 마지막의 데이터 확인 단계에서 Prime+Probe 공격을 사용하기 때문이라는 점을 쉽게 인지할 수 있을 것이다. 하지만 공격의 핵심원리는 MeltDown, 또는 Spectre와 동일하며 단지 Side Channel Attack의 방식만 다르므로 다른 공격 기법이라기보다는 기존 방식의 변형 내지 변주 정도로 보아야 할 것이다. 따라서 MeltDown과 Spectre에 대한 대응책으로 MeltDownPrime이나 SpectrePrime도 거의 똑같이 막을 수 있을 것으로 추정한다. 다만 해당 논문에서는 CPU 아키텍처 레벨의 대응책에 대해서는 다시 검토가 필요할 것이라는 정도의 의견을 남겨두었다. 두 가지 공격은 핵심적인 부분에서 동일하므로 추가적인 설명은 불필요할 것이나 앞으로 이렇게 일부의 방식을 바꾸는 변형공격들이 계속해서 출현할 가능성은 있다.

▲노규남 가비아 클라우드사업부장/CTO

첫 번째 연재를 마치며
이렇게 해서 MeltDown과 Spectre 공격을 이해하기 위한 가장 기본적인 내용들에 대해서 먼저 알아봤다. 사실 이 취약점은 대부분의 사람들이 익숙하지 않은 하드웨어의 버그에 기초한 데다 CPU의 파이프라인 기작과 OS의 메모리 관리, 캐쉬, 분기예측 등 아주 많은 선수지식이 있어야 설명이 가능한 것이므로 실제 보안이슈를 담당하고 있는 최고정보책임자(CIO)나 실무자 입장에서는 매우 곤란하지 않을 수 없을 것이다. 그럼에도 불구하고 우리는 이 취약점들에 대해 충분히 이해할 필요가 있는데, 잘 모르는 취약점에 대응하는 것은 보이지 않는 적과 싸우는 것과 다를 바 없는 위험한 일이기 때문이다. 그리고 앞서 언급한대로 어떤 문제를 해결하기 위해서는 우선 그 문제에 대해서 잘 이해하는 것이 순서다.

다음 회에는 이 내용을 바탕으로 실제 MeltDown과 Spectre가 어떤 식으로 작동하고 어떤 상황에서 어떤 공격 시나리오가 가능한지, 해결책은 무엇인지, 그에 따르는 비용은 무엇인지 등에 대해서 보다 구체적으로 얘기해보도록 하겠다. 모쪼록 이번 연재가 많은 사람들에게 도움이 되기를 기대한다.
[글_노규남 가비아 클라우드사업부장 겸 최고기술책임자(ngn@gabia.com)]

■ CPU 취약점 종합보고서 연재순서
① 취약점의 기본원리
② MeltDown과 Spectre Variant 1
③ Spectre Variant 2

필자 소개_ 노규남은 가비아 클라우드사업부장 겸 최고기술책임자(CTO)로서, IT 업계에서 수십 년간 쌓은 경험을 바탕으로 가비아의 인프라를 고도화하고 발전시키는 역할을 맡고 있다. 최근에는 내부에 쌓이는 서비스 관련 데이터를 신경망으로 처리해 운영을 자동화하고 보안과 클라우드 인프라를 고도화하는 것에 관심을 갖고 있다. 가치투자사이트 밸류스타 기술이사, 영상보안서비스 에버뷰 대표이사를 역임했다.
[오다인 기자(boan2@boannews.com)]

<저작권자: 보안뉴스(www.boannews.com) 무단전재-재배포금지>

※ 본 기사는 가비아 노규남 CTO가 보안뉴스에 특별기고한 내용을 정리한 것입니다.