| 항목 | 내용 |
|---|---|
| CVE ID | CVE-2023-0179 |
| 영향 버전 | Linux 5.5 ~ 6.2-rc3 (커밋 f6ae9f120dad ~ 696e1a48b1a1 사이) |
| CVSS v3.1 | 7.8 (High) — AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| 공개일 | 2023-01-13 (oss-security 공개) |
| 발견자 | Davide Ornaghi |
Netfilter의 nft_payload 표현식이 VLAN 헤더 필드를 읽어오는 코드(nft_payload_copy_vlan)에
정수 언더플로우가 있어, 특정 오프셋/길이 조합을 주면 부호 없는 8비트 변수가 음수로 계산되어
250 근처의 매우 큰 값으로 래핑된다. 이 값이 곧바로 memcpy 의 복사 길이로 쓰여, 의도한
작은 스택 버퍼(VLAN 헤더 크기)를 훨씬 넘는 데이터가 스택에 복사되는 스택 버퍼 오버플로우로
이어진다. unprivileged user namespace 를 통해 로컬 사용자가 자신만의 netfilter 규칙을
만들 수 있는 환경이면 트리거 가능하다.
nftables 규칙은 여러 "표현식(expression)"의 나열로 구성되고, 각 표현식은 패킷의 특정 필드
값을 커널 내부의 레지스터(NFT_REG32_0 ~ NFT_REG32_15 등, 각 32비트)에 읽어오거나
그 값을 비교하는 식으로 동작한다. nft_payload 표현식은 "패킷의 이 위치(offset)에서 이
길이(len)만큼 읽어서 레지스터에 저장하라"는 식으로 쓰인다. 이 오프셋/길이 값은 사용자가
netlink로 규칙을 등록할 때 직접 지정하는 값이다.
일부 네트워크 카드/드라이버는 VLAN 태그를 하드웨어 레벨에서 미리 제거해 커널에 전달한다
(성능 최적화, offload). 이 경우 실제 패킷 버퍼(skb)에는 VLAN 헤더가 없지만, 사용자가
nftables 규칙에서 "VLAN 헤더의 몇 번째 바이트"를 요청하면 커널이 이를 어떻게든 대답해줘야
한다. 그래서 nft_payload_copy_vlan 은 별도의 임시 버퍼(struct vlan_ethhdr veth, 스택에
위치)에 VLAN 헤더를 다시 조립한 뒤, 거기서 필요한 만큼만 잘라서 복사해준다.
C에서 부호 없는 정수(u8, 0~255)에서 더 큰 값을 빼면 음수가 아니라 256에서 그 차이를 뺀
값으로 감싸진다(wrap-around). 예를 들어 (u8)(5 - 10) 은 -5 가 아니라 251 이 된다.
이 특성이 이번 취약점의 직접적인 원인이다.
취약한 코드(패치 전, net/netfilter/nft_payload.c)는 다음과 같다.
static bool
nft_payload_copy_vlan(u32 *d, const struct sk_buff *skb, u8 offset, u8 len)
{
...
u8 *vlanh, *dst_u8 = (u8 *) d;
struct vlan_ethhdr veth;
u8 vlan_hlen = 0;
...
if (offset < VLAN_ETH_HLEN + vlan_hlen) {
u8 ethlen = len;
...
if (offset + len > VLAN_ETH_HLEN + vlan_hlen)
ethlen -= offset + len - VLAN_ETH_HLEN + vlan_hlen; // ← 부호 오류
memcpy(dst_u8, vlanh + offset - vlan_hlen, ethlen);
...
}
}VLAN_ETH_HLEN 은 18, 이중 태그(QinQ)일 때 vlan_hlen 은 4가 추가된다. 문제의 줄은
"offset + len 이 헤더 범위를 얼마나 넘어섰는지"를 계산해서 ethlen 에서 빼야 하는데, 실제
연산은 - VLAN_ETH_HLEN + vlan_hlen 으로 되어 있어 부호가 하나 잘못됐다. 이중 태그가
있는 상황에서 offset/len 을 적절히 고르면, 이 계산 결과가 음수가 되어 u8 래핑으로
250 전후의 큰 값이 나온다.
정상적인 경우 (offset=0, len=18, VLAN 없음)
ethlen = 18, 감산 없음 → memcpy 18바이트 → veth 버퍼(스택) 안에서 끝남
┌──────────────── struct vlan_ethhdr veth (스택, 약 20바이트) ────────────────┐
│ memcpy 대상 (ethlen 바이트만큼) │
└──────────────────────────────────────────────────────────────────────────┘
취약한 경우 (offset=19, len=4, 이중 태그로 vlan_hlen=4)
offset + len = 23 > VLAN_ETH_HLEN + vlan_hlen = 22 → 감산 조건 진입
ethlen = 4 - (23 - 18 + 4) = 4 - 9 = -5 → u8 래핑 → ethlen = 251
┌──────────── struct vlan_ethhdr veth (약 20바이트) ────────────┐▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
│ │▒ 스택의 다른 부분까지
└────────────────────────────────────────────────────────────────▒ memcpy(..., 251) 이 침범 ▒▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
unprivileged user namespace를 만들 수 있는 로컬 사용자가 자신만의 network namespace 안에서 nftables 규칙을 구성한다 (root 권한 없이도 가능한 환경이 많다).- 이중 VLAN 태그가 있는 패킷 처리 경로를 타도록
nft_payload표현식의offset/len을 위 조건(offset + len > 22)을 만족하게 설정한다. - 이런 패킷을 흘려보내면
nft_payload_copy_vlan이 250바이트 전후를 목적지 레지스터 위치부터 스택에 그대로 복사한다 — 인접한 스택 메모리(다른 지역변수, 저장된 레지스터 값 등)를 덮어쓰거나, 반대로 그 내용을 레지스터를 통해 읽어와 사용자에게 노출시킬 수 있다. - 이를 이용해 스택/힙 주소를 유출시켜 KASLR을 무력화하거나, 정교하게 제어된 오버플로우로 커널 코드 실행 흐름을 조작해 로컬 권한 상승까지 이어질 수 있다.
수정 커밋(696e1a48b1a1, "netfilter: nft_payload: incorrect arithmetics when fetching
VLAN header bits", 작성자 Pablo Neira Ayuso)은 연산자 하나를 고쳤다.
--- a/net/netfilter/nft_payload.c
+++ b/net/netfilter/nft_payload.c
@@ nft_payload_copy_vlan()
if (offset + len > VLAN_ETH_HLEN + vlan_hlen)
- ethlen -= offset + len - VLAN_ETH_HLEN + vlan_hlen;
+ ethlen -= offset + len - VLAN_ETH_HLEN - vlan_hlen;
memcpy(dst_u8, vlanh + offset - vlan_hlen, ethlen);+ vlan_hlen 을 - vlan_hlen 으로 바꾸면, 같은 입력(offset=19, len=4, vlan_hlen=4)에서
ethlen = 4 - (23 - 18 - 4) = 4 - 1 = 3 으로 항상 0 이상, 원래 헤더 크기 이내로만
계산된다.
취약한 경우 (패치 후, offset=19, len=4, vlan_hlen=4)
ethlen = 4 - (23 - 18 - 4) = 3 ← 언더플로우 없음
┌──────────── struct vlan_ethhdr veth (약 20바이트) ────────────┐
│ memcpy(..., 3) ← 항상 veth 버퍼 내부에서 끝남 │
└────────────────────────────────────────────────────────────────┘
수학적으로 부호를 바로잡은 것뿐이지만, 그 결과 ethlen 이 절대 음수(u8 래핑)로 갈 수 없게
되어 memcpy 의 길이가 항상 임시 버퍼 크기 이내로 제한된다.
- CVE-2023-0179: Linux kernel stack buffer overflow in nftables — oss-security (원문 PoC+writeup)
- CVE-2023-0179 — NVD
- 수정 커밋 696e1a48b1a1 — torvalds/linux
- CVE-2023-0179 — Red Hat
- "덮어써진 스택 메모리를 이용해 실제로 어떻게 코드 실행까지 이어지는지"의 구체적인 익스플로잇 단계(레지스터 배치, 힙/스택 주소 유출 기법)는 이번에는 oss-security 요약 수준까지만 확인했고, 공개된 PoC 코드 자체를 열어보지는 않았다(정적 분석 범위를 지키기 위해 의도적으로 생략).