Skip to content

Latest commit

 

History

History
152 lines (118 loc) · 8.36 KB

File metadata and controls

152 lines (118 loc) · 8.36 KB

Netfilter nft_payload VLAN 스택 버퍼 오버플로우 (CVE-2023-0179)

메타데이터

항목 내용
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 표현식과 레지스터

nftables 규칙은 여러 "표현식(expression)"의 나열로 구성되고, 각 표현식은 패킷의 특정 필드 값을 커널 내부의 레지스터(NFT_REG32_0 ~ NFT_REG32_15 등, 각 32비트)에 읽어오거나 그 값을 비교하는 식으로 동작한다. nft_payload 표현식은 "패킷의 이 위치(offset)에서 이 길이(len)만큼 읽어서 레지스터에 저장하라"는 식으로 쓰인다. 이 오프셋/길이 값은 사용자가 netlink로 규칙을 등록할 때 직접 지정하는 값이다.

VLAN 태그와 헤더 재구성이 필요한 이유

일부 네트워크 카드/드라이버는 VLAN 태그를 하드웨어 레벨에서 미리 제거해 커널에 전달한다 (성능 최적화, offload). 이 경우 실제 패킷 버퍼(skb)에는 VLAN 헤더가 없지만, 사용자가 nftables 규칙에서 "VLAN 헤더의 몇 번째 바이트"를 요청하면 커널이 이를 어떻게든 대답해줘야 한다. 그래서 nft_payload_copy_vlan 은 별도의 임시 버퍼(struct vlan_ethhdr veth, 스택에 위치)에 VLAN 헤더를 다시 조립한 뒤, 거기서 필요한 만큼만 잘라서 복사해준다.

u8 타입과 언더플로우

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) 이 침범 ▒▒
                                                                 ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒

공격이 성립하는 경로

  1. unprivileged user namespace 를 만들 수 있는 로컬 사용자가 자신만의 network namespace 안에서 nftables 규칙을 구성한다 (root 권한 없이도 가능한 환경이 많다).
  2. 이중 VLAN 태그가 있는 패킷 처리 경로를 타도록 nft_payload 표현식의 offset/len 을 위 조건(offset + len > 22)을 만족하게 설정한다.
  3. 이런 패킷을 흘려보내면 nft_payload_copy_vlan 이 250바이트 전후를 목적지 레지스터 위치부터 스택에 그대로 복사한다 — 인접한 스택 메모리(다른 지역변수, 저장된 레지스터 값 등)를 덮어쓰거나, 반대로 그 내용을 레지스터를 통해 읽어와 사용자에게 노출시킬 수 있다.
  4. 이를 이용해 스택/힙 주소를 유출시켜 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 의 길이가 항상 임시 버퍼 크기 이내로 제한된다.

참고 자료

미해결/불확실 지점

  • "덮어써진 스택 메모리를 이용해 실제로 어떻게 코드 실행까지 이어지는지"의 구체적인 익스플로잇 단계(레지스터 배치, 힙/스택 주소 유출 기법)는 이번에는 oss-security 요약 수준까지만 확인했고, 공개된 PoC 코드 자체를 열어보지는 않았다(정적 분석 범위를 지키기 위해 의도적으로 생략).