안전한 어플리케이션을 위한 코딩 기법

프로그래밍 언어의 현대화, 그리고 좀 더 나은 코딩 기법의 중요성은 기계식 컴퓨터에서 현대적인 소프트웨어 개발 프로세스로의 발전과 깊이 관련되어 있습니다. 소프트웨어 개발은 고도로 전문화되며, 대부분 수학적인 형태로 구성되는 개발에서 컴파일러 기술의 덕택으로 인간의 언어 문법(신텍스)[1]에 근접한 고급 언어로 발전하였습니다. 하지만 그에 따라 문제도 생겨날 수 있는 길이 열리고 말았습니다. C, C++와 같은 고급 언어들은 반응이 정의되어 있지 않은 부분이 수 없이 존재하며, 이러한 부분들은 컴파일러마다 약간씩 해석이 달라집니다. 이로 인하여 예기치 못한, 또는 의도하지 않은 부작용이 발생해, 제품의 하자로 이어지게 됩니다.

하자를 발견하여 고쳐 내는 작업은 개발 조직의 성숙도에 따라 개발 과정에 소요되는 시간 중 최대 80%를 차지하기도 합니다. 이로 인해 코드의 품질이 큰 관건이 되는 것은 당연한 귀결입니다. 그렇다면, 하자를 완전히 제거하고, 디버깅에 들이는 시간을 줄이는 것이 당연할 것입니다.  

참고로, 우리는 아직도 소프트웨어 개발 과정에서 '버그', 그리고 '디버깅'이라는 개념을 사용하고 있습니다. 이것은 하버드 대학에서 처음 기계식 컴퓨터가 사용되는 당시에 유래된 용어로, 당시 나방이 계전 장치에 달라붙어 있는 상황이 컴퓨터 개발 역사상 처음의 시스템 '버그', 즉 하자로 기록되었던 것입니다.

같은 실수를 자꾸만 반복하는 이유 

웹, 앱, 데스크톱, 또는 임베디드 분야의 개발자들은 예기치 않게 같은 종류의 실수를 코드 작성 과정에서 계속 반복한다는 것은 알려져 있는 사실입니다. 이러한 결과는 NASA, 벨 연구소, MITRE 등의 유명 기관에서 장기간에 걸쳐 진행된 설문 및 연구를 통해 도출된 것입니다. 대표적인 예가 C++에서(심지어 C에서도) 할당 해제 없이 다시 할당을 하는 경우, 그리고 프로토타이핑 없이 함수를 사용하는 경우입니다. 그렇게 되면 컴파일 중 철저한 타입 체킹이 이루어지지 않게 됩니다. 당시 연구의 결과로, 코딩 작업 중 위험하고 좋지 않은 습관을 나타내는, 다양한 프로그래밍 작업 권고 사항이 도출되었습니다. 

자주 범하는 실수, 그리고 이를 방지하기 위한 방법을 바탕으로 코드의 품질을 높이기 위한 코딩 지침 및 요령은 다양하게 존재합니다. 일부 기법 및 요령은 오늘날 익히 알려져 있는 표준으로 발전하기도 하였습니다(예: MISRA-C, CERT-C). 특히 자동차나 의료, 철도와 같이 중요도가 높은 업계에서는 어플리케이션 내의 코드 안전성 및 코드 보안성을 확보하기 위해 이러한 표준을 활용하고 있습니다. IEC 61508[2], EN 50128[3], ISO 26262[4]와 같은 기능적 안전성 표준에서는 표준에 부합하기 위해 정적 및 런타인 분석 툴을 사용할 것이 권장(또는 안전 무결성 수준(SIL)이나 자동차 안전 무결성 수준(ASIL)에 따라 적극적으로 권장)되기도 합니다. 안전상 중요도가 높은 시스템에 하자가 존재하는 경우, 인명의 손실이나 환경 피해 등 중대한 결과로 이어질 수 있습니다. 

신뢰성에 초점 

안전한 코딩 기법은 코드 품질, 코드 안전성, 그리고 코드 보안성으로 구성됩니다. 코드 안전성은 소프트웨어의 신뢰성에 초점을 두고 있는 반면, 코드 보안성은 의도하지 않은 활동을 방지하고, 공격으로부터 시스템의 보안을 유지하는 것을 최우선으로 합니다. 이 둘 모두 코드 품질에 크게 의존하고 있으며, 코드 품질은 모든 솔리드 어플리케이션의 근간을 이룬다고 할 수 있습니다. 

안전한 코딩 기법, 그리고 표준은 소프트웨어의 안전을 담보하고, 이를 통해 필요로 하는 신뢰성을 확보할 수 있도록 합니다. 그렇지만 소스코드의 가독성, 그리고 관리 효율성을 유지하는 것도 중요합니다. 코드의 효율성과 가독성을 높인다는 것은 코드의 하자를 줄이고, 코드 재사용을 가능토록 하여 미래의 확장에 대비하는 것입니다. 

MISRA C는 개발 과정에서 자주 발생하는 에러나 취약점을 방지하는 것을 목적으로 하는 것으로, 소프트웨어 개발 표준 중에서도 가장 널리 사용되고 있습니다. 하지만 CWE나 CERT-C와 같은 다른 표준도 존재하며, 이들 표준 역시 임베디드 어플리케이션 개발에 적극적으로 권장되는 표준들입니다. 그러면, 코딩 표준에 대해 좀 더 자세히 알아보도록 하겠습니다. 

MISRA C 표준

MISRA C는 자동차 산업 소프트웨어 신뢰성 협회(Motor Industry Software Reliability Association)에서 개발한 표준으로, 임베디드 시스템, 특히 ISO C를 사용하는 시스템의 개발 과정에서 코드의 안전성과 포터빌리티, 신뢰성을 높이는 것을 목적으로 하고 있습니다. 

MISRA C의 최초 버전, “차량 기반 소프트웨어에서의 C언어 사용을 위한 지침”은 1998년도에 최초로 소개되었으며, 이것을 MISRA-C:1998이라 합니다. 이후 2004년 및 2012년에 새로운 규칙을 추가하는 업데이트가 이루어지기도 하였습니다. 또한 MISRA C++ 2008 표준도 있는데, 이 표준은 C++ 2003을 바탕으로 한 것입니다. 최근에는 MISRA C:2012 1차 개정본을 통해 14개의 규칙이 새롭게 추가, ISO C 보안 가이드라인에서 지적하고 있는 보안상 문제점에 대한 대비를 강화하였습니다. 이러한 규칙 중 일부는 신뢰할 수 없는 데이터의 사용에 대한 내용을 담고 있습니다. 이것은 다양한 임베디드 어플리케이션 내에서 흔히 발견되는 보안 취약점의 하나입니다. 

MISRA는 정식의 빌드를 위해 코드를 점검하기에 앞서 문제점을 미리 찾아 낼 수 있도록 해 줍니다. 그러므로 이러한 방식으로 버그를 찾아내게 되면, 하자의 발생 자체를 미연에 방지할 수가 있는 것입니다. 다시 한 번 말씀드리지만, MISRA 규칙은 안전성과 신뢰성에 방점을 두고 설계된 것이지만, 코드를 다른 툴이나 아키텍쳐로 포팅하는 과정도 용이하게 해 줄 수 있습니다. 

CWE 및 CERT C/C++ 

CWE는 공통 취약점 목록(Common Weakness Enumeration)으로, 커뮤니티 차원에서 개발된, 소프트웨어 취약점 종류를 집대성한 목록입니다. CWE는 통합되며 측정 가능한 소프트웨어 취약성을 제공해, 이를 좀 더 용이하게 이해 및 관리할 수 있도록 해 주며, 이들을 찾을 수 있는 소프트웨어 보안 툴이나 서비스의 이용을 가능하게 합니다.  

CERT C/C++ 보안 코딩 표준(Secure Coding Standard)는 CERT(Computer Emergency Response Team)에서 편찬한 표준으로, C/C++ 프로그래밍 언어와 관련한 보인 코딩 규칙 및 권고 사항을 제시하고 있습니다. 

안전한 코딩 기법의 적용

일반적으로, 모든 임베디드 어플리케이션은 최소한으로 CWE 및 CERT C/C++ 표준을 따르는 것이 바람직합니다. MISRA C는 안전상 중요한 시스템에 대해서는 필수적으로 따를 것이 요구됩니다. 

동일한 개념을 바탕으로, 런타임 동안 에도 산술적 문제, 버퍼 오버플로우, 바운즈(bounds) 문제, 힙 무결성(heap integrity), 메모리 누출(memory leaks)와 관련된 문제에 노출될 수가 있습니다. 이러한 에러들은 특정한 계측 코드나 조건을 잠재적으로 에러가 발생할 수 있는 모든 지점에 주입하는 방식을 통해 탐지해 낼 수가 있습니다. 그러나 수동으로 명령을 추가하여 조건을 점검하고, 런타임에서의 문제를 밝혀 내는 방식은 상당한 시간이 소요됩니다.  

모든 지침 및 표준을 적용하기 위해서는 계측을 통한 코드 점검과 더불어 700여개의 규칙과 요구 사항을 준수해야 한다는 것을 의미합니다. 과연 이러한 안전 코딩 기법을 적용하면서 관련 규칙을 모두 따르는 것이 가능할까요?

자동화 툴의 사용 

소프트웨어 품질과 안전성, 그리고 보안성을 확보하는 가장 좋은 방법은 자동화 툴을 사용하는 것입니다. 이것은 좋은 성능의 컴파일러와 링커를 사용하는 것을 통해 실현할 수가 있습니다. 여기서 사용되는 컴파일러와 링커는 안전 기능 인증을 받은 것을 쓰는 것이 바람직합니다. 또한 자동화된 정적 분석 도구 및 런타임 분석 도구도 함께 사용하면 좋습니다. 

컴파일러와 링커는 C(ISO/IEC 9899:2018) 및 C++(ISO/IEC 14882, 또는 최신 C++17 버전)와 같은 현대적인 언어를 지원하는 것이 바람직합니다. 이 경우, 의심스러운 상황이나 신텍스 취약점에 관련되는 경고를 발동할 수 있기 때문입니다. 평가 순서가 어플리케이션의 로직에 영향을 미칠 수 있는 휘발성 메모리 접근이 그 예입니다. 

경고 메시지는 최우선적으로 주어지는 정적적 분석 점검의 결과이며, 이를 절대로 무시하지 않는 것이 좋습니다. 특히, 기능적 안전성이 요구되는 상황에서는 더욱 그렇습니다. 최선의 방책은 컴파일러의 설정을 조절해 모든 경고를 에러로 취급하도록 하고, 이를 통해 경고를 에러로 변환시키는 것입니다. 이 경우, 개발자는 코든 상에 불명확한 부분을 모두 수정하지 않으면 안됩니다. 이러한 모든 잠재적 문제점이 실제의 문제로 취급될 것이기 때문입니다. 

정적 분석 툴은 코드상에서 가장 많은 실수의 원인이 되는 부분을 찾는 데에 도움을 주지만, 개발자가 코드를 작성하는 과정에서 생각지 못하거나, 무시해 버리는 문제를 찾는 데에도 도움을 줍니다. 특히, 뭔가를 시험해 보기 위해 대략적인 코드를 작성해 보는 단계에서 이러한 실수가 나오기 쉽습니다. 이러한 종류의 툴들은 코딩 표준을 강제로 적용해, 좀 더 나은 코드를 작성하는 데에 큰 도움을 줍니다. 뿐만 아니라, 이들은 동적 분석, 런타임 분석 툴로서, 오직 런타임 중에만 나타나는 결함을 포착하거나, 이를 발동시키기도 합니다. 런타임 분석툴은 소프트웨어 디버거 상에서 프로그램을 실행하는 동안 코드 상의 실제 또는 잠재적 문제를 찾아낼 수가 있습니다. 

그러므로, 시스템 내에 발생할 수 있는 모든 문제점을 점검하고자 할 때에는 정적 분석을 통해서 발견할 수 있는 문제점이 있는 반면, 런타임 분석을 통해서 찾는 것이 더 수월한 것들도 있는 것입니다. 경우에 따라서는 이 둘이 겹치기도 하지만, 또 둘 중 어느 한 가지 방식으로만 찾아낼 수 있는 것도 있습니다. 최선의 코드 분석을 이끌어 내기 위해서는 이 두가지 방식을 동시에 사용하는 것이 필요하며, 이를 최상의 빌드 툴과 함께 이용할 것을 권장합니다. 아래의 표는 각 툴 별로 담당하는 결함의 종류를 나타낸 것입니다.  

Safetycodingtechniques_totalcoverage

허점의 포착

이러한 효과는 아래의 사진을 통해서 가장 잘 설명할 수 있습니다. 독일의 빌레펠트 대학을 촬영한 사진[5]으로, 지난 2005년, 이름을 알 수 없는 작성자에 의해 촬영되어 큰 화제를 불러 일으킨 사진입니다:

Safetycodingtechniques_Loopholes

시스템을 무너뜨리는 가장 쉬운 방법은 그것을 돌파하는 것이 아니라 우회하는 것일 수도 있습니다. 특히 보안이 취약한 코드로 작성된 소프트웨어 취약성은 대부분이 이와 같은 경우에 해당됩니다. 위의 사진은 이러한 취약성을 상징적으로 잘 드러내 주고 있습니다. 게이트가 권고 사항에 따라 설치되어 있으며, 사양에 따라 잘 작동하고는 있지만, 이러한 보안 수단이 쉽게 우회될 수가 있으며, 이러한 문제점은 런타임 분석(예: 눈이 내린 경우)을 실시할 때에만 비로소 드러나는 것입니다. 보안 시스템 상에서는 문제가 잘 드러나지 않을 수가 있습니다. 하지만 자동화된 런타임 분석은 코드를 스캔한 뒤, 잠재적인 허점을 찾아주며, 이는 위와 같은 종류의 문제를 발견하는 데에 있어 매우 좋은 방법이 되기도 합니다. 

이 경우에는 하드웨어 상의 픽스를 통해 안전상 취약점을 해결하였습니다. 예를 들어 아래 2020년 구글 스트리트 뷰로 찍은 사진에서는 콘크리트 블록을 설치해 우회를 차단한 모습을 보실 수 있습니다:

Safetycodingtechniques_Lastimage

코딩 표준은 코드의 미래 확장성을 확보하는 데에 도움을 주며, 재사용을 더 용이하게 합니다. 이것은 코드의 품질이 코드의 재사용성에 영향을 미치는 것을 의미하며, 성숙된 조직이 신 제품을 개발할 때에 발현되는 조직 문화가 바로 이것입니다. 이와 같이 안전 코딩 기법을 적용하는 선순환 구조는 저희가 항상 말하고 있는 전제, 즉 모든 것은 코드 품질로부터 시작된다는 개념을 다시금 확인해 주고 있습니다.

작성: Rafael Taubinger, 기술 마케팅 전문가 

참고문헌

[1] Hopper (1978) p. 16.
[2] https://www.iec.ch/functionalsafety/standards/page2.htm
[3] https://standards.globalspec.com/std/14256883/EN%2050128
[4] https://www.iso.org/standard/43464.html
[5] https://wiki.sei.cmu.edu/confluence/display/seccode/Top+10+Secure+Coding+Practices?focusedCommentId=88044413
[6]https://www.google.cz/maps/@52.0366688,8.4912691,3a,75y,35.86h,98.92t/data
=!3m6!1e1!3m4!1soRzmyEEoGqUoVhlyGWmyEw!2e0!7i13312!8i6656

죄송하지만, 당사 사이트에서는 Internet Explorer를 지원하지 않습니다.보다 편안한 사이트를 위해 Chrome, Edge, Firefox 등과 같은 최신 브라우저를 사용해 주시길 부탁드립니다.