Claude Code: 23년간 숨어있던 Linux 커널 취약점 발견

서론

2024년 12월, 한 보안 연구자가 실험 삼아 Claude Code에게 Linux 커널 소스 코드를 분석해달라고 요청했다. 그 결과는 보안 커뮤니티를 뒤흔들었다. 무려 23년간 수천 명의 개발자, 수십 개의 정적 분석 도구, 그리고 수많은 코드 리뷰를 통과했던 취약점이 Claude에 의해 발견된 것이다.

이 사건은 단순히 “AI가 버그를 찾았다"는 이야기가 아니다. LLM이 **코드의 의미론적 맥락(Semantic Context)**을 이해하고, 인간조차 놓치기 쉬운 미묘한 논리적 결함을 탐지할 수 있음을 입증한 패러다임 시프트다. 기존 정적 분석 도구가 패턴 매칭과 규칙 기반 탐지에 의존했다면, LLM은 코드의 “의도"와 “불변식(Invariant)“을 추론할 수 있다.

이 글에서는 Claude Code가 발견한 취약점의 기술적 세부사항, LLM 기반 정적 분석의 작동 원리, 그리고 이를 실무 보안 파이프라인에 통합하는 방법을 깊이 있게 다룬다.


Claude Code와 23년 된 취약점

발견된 취약점: 참조 카운트 미스매치

Claude Code가 발견한 취약점은 Linux 커널의 Key Management Subsystem에서 발생했다. 핵심 문제는 참조 카운팅(Reference Counting)의 불일치였다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 취약한 코드 패턴 (실제 코드의 단순화 버전)
static struct key *find_key_by_id(struct key *keyring, key_serial_t id) {
    struct key *key;
    
    key = keyring_search(keyring, &key_type_user, id_str);
    if (!IS_ERR(key)) {
        // 문제점: key_ref_put이 아닌 key_put 호출
        // 내부적으로 이미 reference가 증가된 상태에서
        // 잘못된 decay 함수 사용
        key_put(key);  // ← Should be key_ref_put()
        return key;    // ← Use-after-free risk
    }
    return NULL;
}

이 취약점의 교묘함은 타입 시스템 우회에 있다. C 언어에서 struct key*struct key_ref*는 모두 포인터로 취급되지만, 참조 카운팅 의미론은 완전히 다르다. 기존 정적 분석 도구들은 이러한 **의미론적 구분(Semantic Distinction)**을 타입 레벨에서 포착하지 못했다.

LLM이 성공한 이유: 맥락 이해能力

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
graph TD
    A[Source Code Input] --> B[Tokenization & Parsing]
    B --> C[Contextual Embedding]
    C --> D[Semantic Analysis]
    D --> E[Invariant Reasoning]
    E --> F[Anomaly Detection]
    F --> G[Vulnerability Report]
    
    H[Training Knowledge] --> D
    I[API Documentation] --> E
    J[Historical CVE Patterns] --> F

Claude가 기존 도구와 다른 점은 세 가지 핵심 능력이다:

  1. API 의미론 이해: key_put()key_ref_put()의 차이를 문서와 코드 패턴에서 학습 2. 데이터플로우 추적: 변수의 생명주기를 함수 경계를 넘어 추적 3. 불변식 추론: “이 포인터는 이미 참조가 증가된 상태여야 한다"는 암시적 가정 발견

LLM 기반 정적 분석의 기술적 원리

Traditional Static Analysis vs. LLM-Based Analysis

| 비교 항목 | 전통적 정적 분석 | LLM 기반 분석 | | :— | :— | :— | | 탐지 방식 | 패턴 매칭, 규칙 기반 | 의미론적 추론, 맥락 이해 | | 오탐율 (False Positive) | 높음 (30-50%) | 낮음 (10-20%) | | 탐지 범위 | 정의된 규칙 내 한정 | 제로데이, 논리적 결함 포함 | | 처리 속도 | 빠름 (초당 MB 단위) | 느림 (토큰당 연산) | | 코드 이해도 | 구문적 (Syntactic) | 의미론적 (Semantic) | | 확장성 | 새 규칙 작성 필요 | 프롬프트 엔지니어링만으로 확장 |

Context Window와 코드 분석

Claude의 핵심 강점은 200K 토큰 컨텍스트 윈도우다. 이는 약 50만 줄의 C 코드를 단일 추론 컨텍스트에서 처리할 수 있음을 의미한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Claude Code를 활용한 취약점 탐지 예시
import anthropic
import os

def analyze_code_for_vulnerabilities(source_code: str, file_path: str):
    """
    Claude API를 사용한 정적 분석 파이프라인
    """
    client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
    
    system_prompt = """당신은 시스템 소프트웨어 보안 전문가입니다.
    분석 대상 코드에서 다음 취약점 유형을 탐지하세요:
    
    1. 참조 카운팅 오류 (double-free, use-after-free)
    2. 락(Lock) 획득/해제 미스매치
    3. NULL 포인터 역참조 가능성
    4. 버퍼 오버플로우 위험
    
    각 발견에 대해:
    - 심각도 (Critical/High/Medium/Low)
    - 취약한 라인 번호
    - 근본 원인 분석
    - 수정 제안
    """
    
    message = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=4096,
        system=system_prompt,
        messages=[{
            "role": "user",
            "content": f"파일: {file_path}

{source_code}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
        }]
    )
    
    return message.content[0].text

# 실제 사용 예시
if __name__ == "__main__":
    with open("kernel/key.c", "r") as f:
        code = f.read()
    
    report = analyze_code_for_vulnerabilities(code, "kernel/key.c")
    print(report)

추론 과정의 Deep Dive

Claude가 코드를 분석할 때 내부적으로 수행하는 추론 단계:

1
2
3
4
5
6
7
graph LR
    A[Code Tokens] --> B[Self-Attention]
    B --> C[Cross-Reference Resolution]
    C --> D[Control Flow Graph Construction]
    D --> E[Data Flow Analysis]
    E --> F[Invariant Checking]
    F --> G[Vulnerability Classification]

Self-Attention 메커니즘이 코드 분석에서 특히 중요하다. “이 변수가 어디서 초기화되었는가?“라는 질문에 대해, 어텐션 헤드가 정의 지점과 사용 지점을 동시에 바라볼 수 있기 때문이다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 어텐션 패턴을 활용한 변수 추적 (개념적 구현)
class CodeAttentionAnalyzer:
    def __init__(self, model):
        self.model = model
        self.attention_weights = []
    
    def trace_variable_flow(self, code: str, var_name: str):
        """
        특정 변수의 데이터플로우를 어텐션 패턴으로 추적
        """
        tokens = self.model.tokenize(code)
        var_positions = [i for i, t in enumerate(tokens) if var_name in t]
        
        attention_map = self.model.get_attention_weights(code)
        
        flow_trace = []
        for pos in var_positions:
            # 해당 토큰이 주목하는(attend to) 다른 토큰들
            attended = attention_map[pos].top_k(k=5)
            flow_trace.append({
                'position': pos,
                'token': tokens[pos],
                'attends_to': [tokens[i] for i in attended.indices]
            })
        
        return flow_trace

실무 적용: Step-by-Step 가이드

1단계: 환경 구성

1
2
3
4
5
6
7
8
# Claude Code CLI 설치
npm install -g @anthropic-ai/claude-code

# 또는 Python SDK 사용
pip install anthropic

# 환경 변수 설정
export ANTHROPIC_API_KEY="your-api-key"

2단계: 분석 파이프라인 구축

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/usr/bin/env python3
"""
LLM 기반 취약점 스캐너 파이프라인
"""

import os
import json
from pathlib import Path
from dataclasses import dataclass
from typing import List, Optional
import anthropic

@dataclass
class VulnerabilityFinding:
    file_path: str
    line_start: int
    line_end: int
    severity: str  # Critical, High, Medium, Low
    category: str
    description: str
    recommendation: str
    confidence: float

class LLMVulnerabilityScanner:
    def __init__(self, model: str = "claude-sonnet-4-20250514"):
        self.client = anthropic.Anthropic()
        self.model = model
        self.max_tokens_per_request = 8000  # 출력 토큰 제한
        
    def scan_file(self, file_path: Path) -> List[VulnerabilityFinding]:
        """단일 파일 스캔"""
        
        with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
            code = f.read()
        
        # 파일 크기 체크 (200K 토큰 ≈ 800KB 소스코드)
        if len(code) > 500_000:
            return self._scan_large_file(file_path, code)
        
        prompt = self._build_analysis_prompt(code, str(file_path))
        
        response = self.client.messages.create(
            model=self.model,
            max_tokens=self.max_tokens_per_request,
            messages=[{"role": "user", "content": prompt}]
        )
        
        return self._parse_response(response.content[0].text, str(file_path))
    
    def _build_analysis_prompt(self, code: str, file_path: str) -> str:
        return f"""다음 C 코드를 보안 관점에서 분석하세요.

파일 경로: {file_path}

{code}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

분석 요구사항:
1. 메모리 안전성: Use-after-free, Double-free, Buffer overflow
2. 동시성: Race condition, Deadlock 가능성
3. 참조 카운팅: refcount leak, premature free
4. 입력 검증: NULL 체크 누락, Boundary check

발견된 각 취약점에 대해 JSON 형식으로 출력:
{{
  "findings": [
    {{
      "line_start": <int>,
      "line_end": <int>,
      "severity": "Critical|High|Medium|Low",
      "category": "<취약점 유형>",
      "description": "<상세 설명>",
      "recommendation": "<수정 방법>",
      "confidence": <0.0-1.0>
    }}
  ]
}}
"""
    
    def _parse_response(self, response_text: str, file_path: str) -> List[VulnerabilityFinding]:
        """응답 파싱"""
        try:
            # JSON 블록 추출
            json_start = response_text.find('{')
            json_end = response_text.rfind('}') + 1
            json_str = response_text[json_start:json_end]
            data = json.loads(json_str)
            
            findings = []
            for item in data.get('findings', []):
                findings.append(VulnerabilityFinding(
                    file_path=file_path,
                    line_start=item['line_start'],
                    line_end=item['line_end'],
                    severity=item['severity'],
                    category=item['category'],
                    description=item['description'],
                    recommendation=item['recommendation'],
                    confidence=item['confidence']
                ))
            return findings
        except (json.JSONDecodeError, KeyError) as e:
            print(f"파싱 오류: {e}")
            return []
    
    def scan_directory(self, root_path: Path, extensions: List[str] = ['.c', '.h']) -> List[VulnerabilityFinding]:
        """디렉토리 전체 스캔"""
        all_findings = []
        
        for ext in extensions:
            for file_path in root_path.rglob(f'*{ext}'):
                print(f"스캔 중: {file_path}")
                findings = self.scan_file(file_path)
                all_findings.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
extend(findings)
                
                # Rate limiting
                import time
                time.sleep(1)  # API 호출 간격
        
        return all_findings

# 실행 예시
if __name__ == "__main__":
    scanner = LLMVulnerabilityScanner()
    
    # Linux 커널 소스 특정 서브시스템 스캔
    kernel_path = Path("./linux/kernel/")
    findings = scanner.scan_directory(kernel_path)
    
    # 결과 출력
    for f in sorted(findings, key=lambda x: x.line_start):
        print(f"[{f.severity}] {f.file_path}:{f.line_start}")
        print(f"  Category: {f.category}")
        print(f"  Description: {f.description}")
        print(f"  Fix: {f.recommendation}")
        print()

3단계: CI/CD 통합

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# .github/workflows/llm-security-scan.yml
name: LLM Security Scan

on:
  pull_request:
    branches: [main, develop]
  push:
    branches: [main]

jobs:
  vulnerability-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        
      - name: Setup Python

출처: https://news.google.com/rss/articles/CBMingFBVV95cUxNRnNUS1NON1Faa2F4bDhTd1RyajNjc1F4eGwxZjU0ajRHX0dyekdEM1BDT2kxRWF3Y29NVVFOSW1pNXFIdFhCTUpHX1VmdWJDWWxlMElHelhxTWlreFhwOUYxVzZxVTZsMzZod0Y4VjRaVmV0TGg2Vm41YlZPQUdrTG5odnY2X3RsVzJNaFVqc080ZWRLVmliay1Pcm5rQQ?oc=5

Hugo로 만듦
JimmyStack 테마 사용 중