infiltr8 Lab
Home/ CVE/ 커널·OS/ Linux netfilter nf_tables verdict 더블프리
CVE · 커널·OS · 권한상승 · CVE-2024-1086
심층 분석 연구 분석

CVE-2024-1086 (Linux nf_tables 더블프리) 분석

리눅스 커널 넷필터(netfilter)의 nf_tables 컴포넌트에서, 패킷을 '버리라(drop)'는 판정값 하나가 동시에 '받아들이라(accept)'는 판정으로도 읽히는 CVSS 7.8의 더블프리(double-free) 취약점입니다(CVE-2024-1086). 2014년에 들어간 코드 한 줄이 10년 뒤인 2024년에야 되돌려졌고, 2024년 5월 CISA 긴급취약점 목록(KEV)에 등재된 데 이어 2025년 10월에는 실제 랜섬웨어 공격에 쓰인 사실이 확인됐습니다. 이 글은 원 발견자(Notselwyn)의 분석을 재구성하고, 실제 리눅스 커널 오픈소스를 고정 커밋 기준으로 직접 열어 근본원인·호출 흐름·패치를 분석합니다.

이 글의 코드 발췌(nf_tables_api.c의 nft_verdict_init()·nft_data_init(), core.c의 nf_hook_slow(), include/linux/netfilter.h의 NF_DROP_GETERR()·NF_HOOK())는 torvalds/linux 오픈소스를 패치 커밋(f342de4e2f33e0e39165d8639387aa6c19dff660)의 부모 커밋(b462579b2b86a8f5230543cadd3a4836be27baf7, 패치 전 상태) 및 패치 커밋 자체(패치 후 상태) 기준으로 raw.githubusercontent.com에서 직접 받아 파일·라인 단위로 byte-level 대조했습니다. 패치 diff는 GitHub의 .patch 원문을 그대로 받아 확인했습니다. 검증값이 '더블프리를 유발하는 구체적 원인'(NF_DROP_GETERR가 verdict 상위 16비트를 부호 있는 정수로 해석해 우연히 1(NF_ACCEPT와 동일한 정수)이 될 수 있고, 그 값을 NF_HOOK() 매크로가 '훅이 패킷을 통과시켰다'는 신호로 오판한다는 연쇄)은 이 세 파일의 실제 코드를 직접 대조해 저희가 계산·재구성한 것이며, 이 인과 서술 자체는 1차 소스(코드)로 확인됩니다. 다만 이 더블프리를 임의 물리 메모리 읽기/쓰기 프리미티브로 확장하는 후반부 익스플로잇 기법(더러운 페이지테이블 기법 등)은 원 발견자의 공개 기술 블로그(pwning.tech)와 NVD·벤더 권고문·CISA 보도자료를 교차 확인한 2차 분석이며, 저희가 익스플로잇 코드 자체를 직접 실행·재현하지는 않았습니다 — 그 한계는 §패치 완전성 평가·본문 배지에 명시했습니다.

infiltr8 team 직접 분석 #nf_tables#netfilter#double-free#LPE
무엇리눅스 커널 넷필터 nf_tables의 nft_verdict_init()이 사용자가 넘긴 판정(verdict) 값의 상위 16비트를 검증 없이 그대로 받아들여, 이미 해제한 네트워크 버퍼(skb)를 한 번 더 해제하게 만드는 더블프리 취약점입니다.
원리판정값의 하위 1바이트는 '버려라/받아들여라'를, 상위 16비트는 원래 '에러코드' 용도로 쓰이는데, 이 상위 비트를 검증하지 않고 그대로 통과시키면 NF_DROP_GETERR()가 그 값을 우연히 NF_ACCEPT와 같은 정수로 해석할 수 있습니다.
핵심패치는 2014년에 상위 비트 검증을 느슨하게 만든 커밋을 통째로 되돌렸을 뿐입니다 — 코드 한 줄의 실수가 10년간 남아 있었고, 2024년 5월 CISA KEV 등재·2025년 10월 랜섬웨어 실사용까지 확인됐습니다. CVSS 7.8(High), CWE-416.

01개요

넷필터(netfilter)는 리눅스 커널이 패킷을 처리하는 각 지점(훅)마다 등록된 함수들을 호출해 '이 패킷을 어떻게 할지' 판정(verdict)을 받는 프레임워크입니다. nf_tables는 이 넷필터 위에서 동작하는 최신 방화벽/패킷 필터링 서브시스템으로, nft 명령이나 netlink API로 사용자가 직접 규칙을 등록합니다.

CVE-2024-1086은 이 규칙에 포함되는 '즉시 판정(immediate verdict)' 값을 커널이 받아들이는 코드(nft_verdict_init())가, 판정값의 일부(상위 16비트)를 전혀 검증하지 않는 데서 시작합니다. 이 값이 넷필터 훅 처리 루프(nf_hook_slow())까지 흘러가면, '패킷을 버렸다(drop)'는 판정과 '패킷을 받아들였다(accept)'는 판정이 같은 정수로 겹쳐 읽히는 순간이 생기고, 그 결과 이미 해제된 네트워크 버퍼가 한 번 더 해제됩니다(use-after-free/double-free, CWE-416).

이 취약점은 로컬 권한상승(local privilege escalation)에 쓰입니다 — 원격에서 직접 트리거되지는 않지만, 비특권 사용자 네임스페이스를 통해 CAP_NET_ADMIN을 얻을 수 있는 환경(다수의 리눅스 배포판 기본 설정, 컨테이너 런타임 포함)에서는 일반 사용자가 이 경로로 root 권한까지 올라갈 수 있습니다. 2024년 5월 CISA 긴급취약점목록(KEV)에 등재됐고, 2025년 10월에는 실제 랜섬웨어 공격 체인에서 이 취약점이 쓰였다는 사실이 확인됐습니다.

CVSS: 7.8 (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H, NVD) · CWE-416(Use After Free)

이 근본원인은 2014년 2월 커밋 e0abdadcc6e1("netfilter: nf_tables: accept QUEUE/DROP verdict parameters")에서 들어갔고, 2024년 1월 20일 커밋 f342de4e2f33e0e39165d8639387aa6c19dff660으로 그 커밋 전체가 되돌려지며 고쳐졌습니다(발견·신고자: Notselwyn, pwning.tech). NVD 발행일은 2024-01-31입니다. 별개 CVE로 쪼개진 변형(variant)은 확인되지 않았습니다 — 이 CVE 하나가 이 근본원인 전체를 대표합니다.

02영향받는 버전

대상취약 버전안전 버전
Linux 커널(mainline)v3.15 ~ v6.8-rc1 (커밋 e0abdadcc6e1이 들어간 이후 전체)커밋 f342de4e2f33e0e39165d8639387aa6c19dff660 포함 버전
stable/longterm 백포트5.15.148 이하 / 6.1.75 이하 / 6.6.14 이하 / 6.7.2 이하5.15.149 이상 / 6.1.76 이상 / 6.6.15 이상 / 6.7.3 이상
Ubuntu(대표 예시, 배포판마다 백포트 버전 상이)22.04 LTS: 5.15.0-101.111 미만 등각 배포판 보안공지의 패치 버전 이상(벤더별 개별 확인 필요) 분석 — 배포판별 백포트 시점은 벤더 공지 기준, 전수 확인은 하지 않음

03배경 — 판정값 하나에 두 가지 정보를 욱여넣은 인코딩

넷필터 훅 함수는 패킷마다 unsigned int 하나로 판정을 반환합니다. 이 정수는 include/uapi/linux/netfilter.h에서 두 부분으로 겹쳐 인코딩됩니다 — 하위 1바이트(NF_VERDICT_MASK = 0x000000ff)는 NF_ACCEPT(1)·NF_DROP(0)·NF_QUEUE(3) 같은 '무엇을 할지'를, 상위 16비트(NF_VERDICT_QMASK = 0xffff0000)는 NF_QUEUE일 땐 큐 번호를, NF_DROP일 땐 (선택적으로) 에러코드를 담습니다. 헤더 주석 자체가 이걸 "nice하지 않지만, 추가 함수 인자를 두는 것보다는 낫다"고 표현할 만큼 의도된 절충입니다.

nf_tables에서는 이 판정값이 규칙의 '즉시(immediate)' 문(예: accept, drop, jump target)을 통해 사용자 공간(userspace)에서 netlink로 직접 전달됩니다. nft_immediate_init()이 이 값을 받아 nft_data_init()을 거쳐 nft_verdict_init()으로 넘기고, 이 함수가 그 값을 struct nft_data의 verdict.code 필드에 채워 넣습니다 — 이 필드가 나중에 훅이 실제로 반환하는 값이 됩니다.

여기서 핵심은 이 값이 커널이 계산한 값이 아니라 사용자가 netlink로 직접 써 보낸 32비트 정수라는 점입니다. 이 값을 커널의 다른 계층(nf_hook_slow(), NF_HOOK())이 얼마나 엄격하게 검증하고 받아들이는지가 다음 절의 단서입니다.

04취약점 발생 원인

이 취약점은 서로 다른 두 계층이 같은 32비트 정수를 서로 다른 전제로 읽으면서 성립합니다. 상위 16비트를 검증하지 않고 저장한 것(W1)과, 그 상위 비트를 무조건 유효한 값으로 믿고 계산에 쓴 것(W2)이 만나야 완성됩니다.

1"하위 1바이트만 확인하고, 상위 16비트는 그대로 저장했다"

핵심

nft_verdict_init()의 스위치문이 data->verdict.code & NF_VERDICT_MASK(하위 1바이트)만 NF_ACCEPT/NF_DROP/NF_QUEUE인지 확인하고, 원본 32비트 값 전체를 그대로 verdict.code에 저장합니다.

nft_verdict_init()은 netlink 속성에서 data->verdict.code = ntohl(nla_get_be32(...))로 32비트 값을 그대로 읽어들인 뒤, 이 값이 유효한지 스위치문으로 검사합니다. 문제는 검사 대상이 원본 32비트 전체가 아니라 data->verdict.code & NF_VERDICT_MASK — 즉 하위 1바이트만이라는 것입니다.

data->verdict.code = ntohl(nla_get_be32(tb[NFTA_VERDICT_CODE])); switch (data->verdict.code) { default: switch (data->verdict.code & NF_VERDICT_MASK) { case NF_ACCEPT: case NF_DROP: case NF_QUEUE: break; default: return -EINVAL; } fallthrough; case NFT_CONTINUE:

Linux 커널 오픈소스 실제 소스 — net/netfilter/nf_tables_api.c · nft_verdict_init() (패치 전, 커밋 b462579b2b86a8f5230543cadd3a4836be27baf7 = 패치 커밋 f342de4e2f33e0e39165d8639387aa6c19dff660의 부모). 10996행의 마스킹이 하위 1바이트만 검증하는 지점입니다.

이 마스킹된 값이 NF_ACCEPT·NF_DROP·NF_QUEUE 중 하나이기만 하면, 검증되지 않은 상위 16비트를 포함한 원본 32비트 전체가 그대로 verdict.code에 저장된 채 통과합니다. 즉 하위 1바이트는 화이트리스트로 걸러지지만, 상위 16비트는 사실상 사용자가 원하는 어떤 값이든 넣을 수 있습니다.

왜 이렇게 됐나이 코드는 '판정 코드가 커널이 아는 값인가'라는 질문에만 답하도록 설계됐습니다. 판정값이 상위 비트에 부가 정보(에러코드·큐 번호)를 함께 실어 나른다는 사실, 그리고 그 부가 정보를 사용자가 임의로 채울 수 있다는 사실은 이 함수가 검증할 질문으로 다뤄지지 않았습니다.

2"상위 16비트는, 유효한 에러코드일 거라고 믿었다"

핵심

nf_hook_slow()가 NF_DROP 판정을 처리할 때, 이미 버퍼를 해제한 뒤 상위 16비트를 NF_DROP_GETERR()로 부호 있는 정수로 변환해 그대로 반환하는데, 이 반환값을 상위 호출자(NF_HOOK())가 '1이면 훅이 패킷을 통과시켰다'는 별개의 계약으로 재해석합니다.

nf_hook_slow()는 하위 1바이트로 분기한 뒤 NF_DROP이면 kfree_skb_reason()으로 패킷 버퍼(skb)를 즉시 해제합니다. 그다음 NF_DROP_GETERR(verdict)를 호출해 상위 16비트를 -(verdict >> 16)로 계산한 값을 반환값으로 씁니다 — 이 값은 원래 'drop한 이유(에러코드)'를 호출자에게 알려주기 위한 것입니다.

switch (verdict & NF_VERDICT_MASK) { case NF_ACCEPT: break; case NF_DROP: kfree_skb_reason(skb, SKB_DROP_REASON_NETFILTER_DROP); ret = NF_DROP_GETERR(verdict); if (ret == 0) ret = -EPERM; return ret;

Linux 커널 오픈소스 실제 소스 — net/netfilter/core.c · nf_hook_slow() (커밋 b462579b2b86a8f5230543cadd3a4836be27baf7, 패치 대상이 아니라 이 취약점의 두 번째 축 — 이 함수 자체는 이 CVE로 수정되지 않았습니다). 631~633행이 '해제 후 상위비트를 에러코드로 신뢰'하는 지점입니다.

그런데 W1 때문에 상위 16비트가 검증되지 않은 사용자 값이므로, 이 계산 결과가 우연히 1 — 즉 NF_ACCEPT와 같은 정수 — 이 될 수 있습니다(예: 상위 16비트를 0xffff로 채우면, 부호 있는 정수 연산에서 -(-1) = 1이 됩니다). nf_hook_slow()를 호출하는 NF_HOOK() 매크로는 반환값이 정확히 1일 때 '이 훅이 패킷을 받아들였으니 다음 처리 함수(okfn)를 마저 실행하라'는 완전히 다른 계약으로 이 숫자를 읽습니다. 그 결과 이미 kfree_skb_reason()으로 해제된 skb가 okfn() 경로에서 정상 패킷인 것처럼 다시 처리됩니다 — 사용후해제이자, 그 경로가 다시 skb를 해제하면 더블프리입니다.

왜 이렇게 됐나nf_hook_slow()·NF_DROP_GETERR()·NF_HOOK() 세 조각은 각자 합리적인 계약 위에서 짜였습니다. 문제는 '상위 16비트=에러코드'라는 계약과 '반환값 1=훅이 통과시킴'이라는 계약이 같은 정수 공간을 공유한다는 점입니다. 이 두 계약이 우연히 충돌하는 값(에러코드로 해석하면 1이 되는 상위 비트)이 존재한다는 것 자체가 이 인코딩 방식의 구조적 위험이고, W1이 그 값을 사용자가 고를 수 있게 열어준 것입니다.

분석 결론

무게 중심은 W1(nft_verdict_init()이 상위 16비트를 검증하지 않은 것)입니다.

W2(nf_hook_slow()가 상위 비트를 에러코드로 신뢰하고, NF_HOOK()이 반환값 1을 '통과'로 해석하는 것)는 그 자체로는 결함이 아닙니다 — 판정값 인코딩 규약이 원래 그렇게 설계됐고, 이 함수들은 2024년 패치에서도 바뀌지 않았습니다. W1이 사용자에게 이 상위 비트에 대한 통제권을 열어주지만 않았다면 두 계약은 절대 충돌하지 않았습니다.

한 문장으로 묶으면 이렇습니다. 한 정수 안에 두 가지 다른 의미(무엇을 할지 / 부가 정보)를 겹쳐 담는 인코딩에서, 그중 절반(상위 비트)의 입력 검증을 빠뜨렸습니다. 그 틈에서 '버림'을 뜻하는 값과 '받아들임'을 뜻하는 값이 같은 숫자로 겹쳤습니다.

05공격 과정

5.1 공격 과정 요약

공격 흐름도
공격 흐름도 (직접 작성)

5.2 상세 공격 과정

예를 들어 공격자가 판정값의 하위 1바이트를 NF_DROP(0)으로, 상위 16비트를 0xffff로 채운 32비트 값(플레이스홀더 — 실제로는 0xffff0000 형태)을 규칙에 넣는다고 가정합니다. nft_verdict_init()의 검증(& NF_VERDICT_MASK)은 하위 1바이트만 보므로 이 값을 그대로 통과시킵니다.

이 규칙에 매치되는 패킷이 지나가면 nf_hook_slow()가 NF_DROP 분기로 들어가 kfree_skb_reason()을 호출합니다 — 이 시점에 skb는 이미 해제됩니다. 그다음 줄의 NF_DROP_GETERR(verdict)는 -(verdict >> 16)을 계산하는데, 상위 16비트가 0xffff(부호 있는 정수로는 -1)이므로 -(-1) = 1이 되어 nf_hook_slow()는 1을 반환합니다.

이 '1'이라는 숫자는 NF_DROP_GETERR() 입장에서는 그냥 계산된 에러코드지만, 이 함수를 호출한 NF_HOOK() 매크로 입장에서는 완전히 다른 뜻입니다 — NF_HOOK()의 코드는 if (ret == 1) ret = okfn(net, sk, skb);로, 반환값이 정확히 1이면 '이 훅이 패킷을 받아들였으니(NF_ACCEPT) 다음 정상 처리 함수를 실행하라'고 해석합니다. 그 결과 이미 해제된 skb가 okfn() 경로로 다시 흘러 들어가 사용후해제 상태에서 계속 처리되고, 그 경로가 skb를 다시 해제하는 지점에서 더블프리가 성립합니다.

여기서부터 안정적인 root 권한 획득까지 이어지는 구체적 힙 그루밍·주소 유출·페이지테이블 조작 절차는 원 발견자의 공개 기술 블로그가 상세히 다루고 있으나, 이 글은 그 절차를 재현하거나 요약하지 않습니다(무해화). 실제 동작하는 익스플로잇 코드나 트리거 값 조합은 이 글에 싣지 않습니다.

5.3 미흡점 ↔ 공격 흐름 매핑

미흡점(§4)발화 지점(§5.2)여는 프리미티브→ 다음으로 잇는 것
W1 — 상위 16비트 미검증nft_verdict_init() 저장 시점(§8.2 2~3단계)검증 안 된 원시 32비트 값이 verdict.code로 확정nf_hook_slow()가 이 값을 그대로 신뢰
W2 — 상위 비트=에러코드 계약과 ret==1=통과 계약의 충돌nf_hook_slow()의 NF_DROP_GETERR() 계산(§8.2 4~5단계)해제 후 계산값이 NF_ACCEPT(1)와 정수적으로 충돌NF_HOOK()이 '통과'로 오판, okfn() 재진입
(귀결) 사용후해제/더블프리okfn() 경로의 skb 재사용(§8.2 6단계)이미 해제된 메모리가 정상 객체로 재해석·재해제힙 그루밍을 거친 임의 R/W 프리미티브 → 로컬 권한상승

06취약점 패치

net/netfilter/nf_tables_api.c·nft_verdict_init()·커밋 f342de4e2f33e0e39165d8639387aa6c19dff660 · 부모 b462579b2b86a8f5230543cadd3a4836be27baf7 (Florian Westphal, 2024-01-20 작성 · stable 백포트 5.15.149/6.1.76/6.6.15/6.7.3)
switch (data->verdict.code) { default: switch (data->verdict.code & NF_VERDICT_MASK) { case NF_ACCEPT: case NF_DROP: case NF_QUEUE: break; default: return -EINVAL; } fallthrough; case NF_ACCEPT: case NF_DROP: case NF_QUEUE: break; case NFT_CONTINUE: / NFT_BREAK: / NFT_RETURN: / NFT_JUMP: / NFT_GOTO: (변경 없음) default: return -EINVAL; }

왜§7 W1에서 지목한 자리 — 하위 1바이트만 검증하고 상위 16비트를 포함한 원본 값을 그대로 저장하는 로직 — 을 직접 겨냥한 수정입니다. 커밋 메시지는 이 취약한 로직을 만든 2014년 커밋(e0abdadcc6e1)을 명시적으로 지목하며, core.c:nf_hook_slow()가 'NF_DROP 판정의 상위 16비트에 유효한 에러코드가 들어 있다고 가정한다'고 정확히 짚습니다.

어떻게패치는 새 검증 로직을 추가한 게 아니라, 2014년 이전의 엄격한 switch (data->verdict.code) 구조를 그대로 복원했습니다 — NF_ACCEPT/NF_DROP/NF_QUEUE는 하위 비트 마스킹 없이 정확히 그 값(상위 16비트가 0인 경우)일 때만 통과하고, 스위치문 끝에 default: return -EINVAL;을 추가해 그 외의 모든 값(상위 비트가 채워진 값 포함)을 거부합니다. 즉 '상위 16비트가 0이 아닌 판정값은 애초에 nft_tables에 들어올 수 없다'로 계약을 되돌린 것입니다. 커밋 메시지는 "NF_QUEUE는 nftables가 쓰지 않는다(queue 규칙은 별도의 nft_queue 표현식을 쓴다)"고 덧붙이며, 훗날 정말 에러코드를 사용자에게 열어줘야 한다면 NF_DROP_GETERR()로 err <= 0을 확인하는 별도 검증이 필요하다고 명시합니다.

비고패치가 하는 일은 새 방어선을 세우는 게 아니라 '2014년에 왜 이 검증을 느슨하게 했는지 모르겠다(Its not clear to me why this commit was made)'는 커밋 메시지 그대로, 원래 있던 엄격한 검증으로 되돌아가는 것뿐입니다 — 10년 전 최적화 혹은 편의를 위한 변경이 검증되지 않은 채 남아 있었다는 뜻입니다.

07대응방안

근본 해결은 커널 업그레이드입니다. mainline 기준 커밋 f342de4e2f33e0e39165d8639387aa6c19dff660을 포함한 버전(사실상 v6.8 이상), stable/longterm 라인은 §영향받는 버전 표의 5.15.149/6.1.76/6.6.15/6.7.3 이상, 그 외 배포판(Ubuntu·Debian·RHEL 등)은 각 벤더 보안공지가 지목하는 패치 버전 이상으로 올립니다.

08왜 이게 반복되는가

이 취약점이 흥미로운 이유는 코드가 복잡해서가 아니라, 정확히 반대이기 때문입니다. 검증 로직 자체는 몇 줄짜리 스위치문이고, 패치도 '10년 전 상태로 되돌리기'가 전부입니다. 위험한 건 한 정수 안에 서로 다른 두 가지 의미(무엇을 할지 + 부가 정보)를 겹쳐 담는 인코딩에서, 그 절반만 검증하면 나머지 절반이 다른 계약과 충돌할 수 있다는 사실이었습니다. 저희가 Jenkins CLI 임의 파일 읽기(CVE-2024-23897)에서 본 것과 결이 비슷합니다 — 그때는 '로컬 신뢰 전제로 만들어진 라이브러리 기본값'이 네트워크 노출 컨텍스트로 옮겨지며 다시 검토되지 않았고, 여기서는 '두 계층이 같은 정수 공간을 공유한다'는 사실이 입력 검증 설계에 반영되지 않았습니다. V8 Maglev 힙 손상(CVE-2024-0517)에서도 컴파일러가 자신이 통제하지 못하는 다른 서브시스템(GC)의 규칙을 몰랐다는 같은 계열의 이야기를 다뤘습니다 — 세 사건 모두 개별 코드는 각자 정상인데, 그 코드들이 공유하는 경계에서 전제가 어긋났습니다.

패치완전성 관점에서 이 사건은 비교적 깔끔한 사례입니다. 패치는 새 검증을 추가한 게 아니라, 검증을 느슨하게 만든 2014년 커밋 전체를 되돌렸습니다 — 원인 지점 하나를 정확히 겨냥한 수정이고, 커밋 메시지도 '왜 이 커밋이 만들어졌는지 모르겠다'며 원래 검증이 불필요하게 약화됐었다는 점을 스스로 인정합니다. 다만 이 CVE가 남기는 진짜 질문은 코드가 아니라 시간입니다 — 검증을 느슨하게 만든 변경이 10년간 아무도 문제 삼지 않았고, 2024년 1월에 조용히 패치된 뒤 2024년 5월 CISA KEV, 2025년 10월 랜섬웨어 실사용까지 갔습니다. '오래된 코드라서 안전하다'는 가정 자체가 이 취약점의 수명을 늘린 셈입니다.

이 한 틈은 고쳐졌지만, '한 값에 두 가지 의미를 겹쳐 담을 때 그중 절반만 검증하지 않았는가'라는 질문은 다른 서브시스템, 다른 프로토콜에서 또 나옵니다 — 그래서 저희는 이 커밋 하나가 아니라 이 질문을 남깁니다.

09결론

리눅스 커널 nf_tables의 nft_verdict_init()이 판정값의 상위 16비트를 검증하지 않은 채 저장한 것이 근본원인입니다. 이 검증되지 않은 값이 nf_hook_slow()의 에러코드 계산과 NF_HOOK()의 '통과' 판정 계약을 우연히 충돌시켜, 이미 해제된 네트워크 버퍼를 다시 처리하는 더블프리가 성립합니다. CVSS 7.8(High), CWE-416이며 2014년에 들어간 코드가 2024년 1월에야 되돌려졌습니다. 패치는 원인 지점을 정확히 되돌린 것으로 확인되지만, 10년간 방치됐다가 CISA KEV 등재·랜섬웨어 실사용까지 이어진 시간 자체가 이 사건의 핵심 교훈입니다.

이 CVE의 표준판(요약본)은 아직 별도로 작성되지 않았습니다.

참고 자료

이 글의 코드 발췌(nf_tables_api.c의 nft_verdict_init()·nft_data_init(), core.c의 nf_hook_slow(), include/linux/netfilter.h의 NF_DROP_GETERR()·NF_HOOK())는 torvalds/linux 오픈소스를 패치 커밋(f342de4e2f33e0e39165d8639387aa6c19dff660)의 부모 커밋(b462579b2b86a8f5230543cadd3a4836be27baf7, 패치 전 상태) 및 패치 커밋 자체(패치 후 상태) 기준으로 raw.githubusercontent.com에서 직접 받아 파일·라인 단위로 byte-level 대조했습니다. 패치 diff는 GitHub의 .patch 원문을 그대로 받아 확인했습니다. 검증값이 '더블프리를 유발하는 구체적 원인'(NF_DROP_GETERR가 verdict 상위 16비트를 부호 있는 정수로 해석해 우연히 1(NF_ACCEPT와 동일한 정수)이 될 수 있고, 그 값을 NF_HOOK() 매크로가 '훅이 패킷을 통과시켰다'는 신호로 오판한다는 연쇄)은 이 세 파일의 실제 코드를 직접 대조해 저희가 계산·재구성한 것이며, 이 인과 서술 자체는 1차 소스(코드)로 확인됩니다. 다만 이 더블프리를 임의 물리 메모리 읽기/쓰기 프리미티브로 확장하는 후반부 익스플로잇 기법(더러운 페이지테이블 기법 등)은 원 발견자의 공개 기술 블로그(pwning.tech)와 NVD·벤더 권고문·CISA 보도자료를 교차 확인한 2차 분석이며, 저희가 익스플로잇 코드 자체를 직접 실행·재현하지는 않았습니다 — 그 한계는 §패치 완전성 평가·본문 배지에 명시했습니다.