flawopen.com/Teardowns/cve-2024-26642-linux-nftables-anonymous-set-timeout
CVE-2024-26642: Linux nf_tables의 타임아웃 플래그가 붙은 익명 세트
CVE-2024-26642(NIST 기준 CVSS 7.8): nf_tables_newset()은 어떤 사용자 공간 도구도 만들지 않는 조합인, 타임아웃 플래그까지 붙은 익명 세트를 받아들였고, 그 결과 규칙에 묶인 세트가 요소 만료와 가비지 컬렉션의 대상이 되었습니다. Linux 6.8은 이를 -EOPNOTSUPP로 거부합니다.
외투 보관소에는 두 가지 방식이 있습니다. 번호 고리는 하룻저녁 동안 손님 한 명의 것이고 그 손님이 떠나는 즉시 비워집니다. 시간표 딱지는 직원이 한 시간마다 돌며 기한이 지난 것을 치웁니다. 신청서에서 같은 외투에 두 칸을 모두 체크할 수 있어서, 그 고리는 손님이 떠날 때도 비워지고 직원의 순회 때도 치워집니다. 두 사람이 같은 고리를 자기 담당으로 여기는데, 누구도 상대의 일정을 모릅니다. 해결책은 간단합니다. 이제 신청서가 이 두 칸의 조합을 받지 않습니다.
핵심 개념 및 용어
nf_tables set- nftables 규칙이 대조하는 주소·포트 등 키를 담는 커널 자료 구조로, netlink 메시지
NFT_MSG_NEWSET으로 만들고nf_tables_newset()이 처리합니다. 익명 세트(NFT_SET_ANONYMOUS)- 규칙 안에 바로 쓴
{ 22, 80 }처럼 이름 없는 세트입니다. 그 규칙 하나에 묶이고 같은 트랜잭션에서 규칙과 함께 파괴됩니다. NFT_SET_TIMEOUT- 세트 요소에 만료 시간을 주는 플래그입니다. 만료된 요소는 세트의 가비지 컬렉션 장치가 제거합니다.
NFT_SET_EVAL- 패킷 경로에서 갱신되는 세트용 플래그로, 예전
meter구문이 사용합니다. 수정 후에도 이들에게는 익명 + 타임아웃 + eval 조합이 허용됩니다. 사용자 네임스페이스의 CAP_NET_ADMIN- nf_tables는 자기 네트워크 네임스페이스에서 CAP_NET_ADMIN을 가진 모든 프로세스의 설정을 받아들입니다. 배포판이 허용하면 일반 사용자도 사용자 네임스페이스를 만들어 이 권한을 얻습니다.
근본 원인 분석 (Root Cause)
net/netfilter/nf_tables_api.c의 nf_tables_newset()은 세트 플래그에서 알 수 없는 비트와 MAP+OBJECT, EVAL+OBJECT 충돌만 검사했습니다. NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT은 받아들였기 때문에, 수명이 규칙 하나와 트랜잭션 하나에 묶인 세트가 만료되어 가비지 컬렉션에 회수되는 요소까지 가질 수 있었습니다. 사용자 공간은 이 조합을 만들지 않고, 수명 관리 코드도 이를 고려해 설계되지 않았습니다. 업스트림 수정은 NFT_SET_EVAL도 설정되지 않은 한 이를 -EOPNOTSUPP로 거부합니다. 공개 기록은 그 결과로 생기는 메모리 오류를 자세히 설명하지 않습니다. NIST는 7.8(기밀성·무결성·가용성 모두 높음), 커널 CNA는 5.5(가용성만)로 평가합니다.
단계별 공격 실행 흐름
CAP_NET_ADMIN 획득
로컬 사용자가 (비특권 사용자 네임스페이스가 켜진 환경에서) 사용자 네임스페이스와 네트워크 네임스페이스를 만들어, 그 네임스페이스의 nf_tables 상태에 대한 CAP_NET_ADMIN을 갖습니다.
잘못된 세트 요청
프로세스가 netlink 배치 안에서 NFTA_SET_FLAGS = NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT인 NFT_MSG_NEWSET을 보내고, lookup 표현식으로 세트를 새 규칙에 묶습니다.
커널이 수락
nf_tables_newset()은 금지된 플래그 쌍을 찾지 못하고, 요소에 만료 시간이 있는 규칙 결합 익명 세트를 만듭니다.
검증되지 않은 수명 경로
이후 규칙을 커밋·중단·삭제하면 세트 파괴와 요소 가비지 컬렉션이 함께 돌아가는데, nf_tables 트랜잭션 코드는 이 경로를 안전하게 처리하지 못합니다.
소스 코드 비교: 취약한 구현 vs 보안 패치
/* net/netfilter/nf_tables_api.c: nf_tables_newset(), before the fix */
if (nla[NFTA_SET_FLAGS] != NULL) {
flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS]));
if (flags & ~(NFT_SET_ANONYMOUS | NFT_SET_CONSTANT |
NFT_SET_INTERVAL | NFT_SET_TIMEOUT |
NFT_SET_MAP | NFT_SET_EVAL |
NFT_SET_OBJECT | NFT_SET_CONCAT | NFT_SET_EXPR))
return -EOPNOTSUPP;
/* Only one of these operations is supported */
if ((flags & (NFT_SET_MAP | NFT_SET_OBJECT)) ==
(NFT_SET_MAP | NFT_SET_OBJECT))
return -EOPNOTSUPP;
if ((flags & (NFT_SET_EVAL | NFT_SET_OBJECT)) ==
(NFT_SET_EVAL | NFT_SET_OBJECT))
return -EOPNOTSUPP;
/* BUG: NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT is accepted. A rule-bound
* anonymous set now also gets per-element expiry and garbage
* collection, a combination nft never creates and the set
* lifetime code was not written for. */
}
/* net/netfilter/nf_tables_api.c: nf_tables_newset(), commit 16603605b667 */
if (nla[NFTA_SET_FLAGS] != NULL) {
flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS]));
if (flags & ~(NFT_SET_ANONYMOUS | NFT_SET_CONSTANT |
NFT_SET_INTERVAL | NFT_SET_TIMEOUT |
NFT_SET_MAP | NFT_SET_EVAL |
NFT_SET_OBJECT | NFT_SET_CONCAT | NFT_SET_EXPR))
return -EOPNOTSUPP;
/* Only one of these operations is supported */
if ((flags & (NFT_SET_MAP | NFT_SET_OBJECT)) ==
(NFT_SET_MAP | NFT_SET_OBJECT))
return -EOPNOTSUPP;
if ((flags & (NFT_SET_EVAL | NFT_SET_OBJECT)) ==
(NFT_SET_EVAL | NFT_SET_OBJECT))
return -EOPNOTSUPP;
/* FIX: anonymous sets are never used with timeouts from userspace,
* so reject the combination before the set exists. NFT_SET_EVAL
* stays allowed so legacy meters keep working. */
if ((flags & (NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT | NFT_SET_EVAL)) ==
(NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT))
return -EOPNOTSUPP;
}
엔지니어링 및 시스템 보안 강화 체크리스트
- ✓커밋 16603605b667이 포함된 커널(Linux 6.8, 또는 변경 기록에 CVE-2024-26642가 있는 안정판·배포판 커널)을 사용하세요.
- ✓워크로드에 필요 없다면 비특권 사용자 네임스페이스를 끄세요(Debian·Ubuntu의
kernel.,unprivileged_userns_clone=0user., 또는 Ubuntu의 AppArmor userns 제한). 그러면 로컬 사용자가 nf_tables에 닿지 못합니다.max_user_namespaces=0 - ✓nftables를 쓰지 않는 호스트에서는
nf_tables모듈이 로드되지 않게 하세요. - ✓커널과 API 코드에서는 허용할 플래그 조합을 명시적 허용 목록으로 표현하고, 알려진 나쁜 쌍만 거부하는 대신 지원하는 클라이언트가 만들지 않는 조합은 모두 거부하세요.
- ✓모든 플래그 비트를 다루는 syzkaller 기술로 netlink 인터페이스를 퍼징해, 드문 조합이 테스트에서 먼저 객체 수명 코드에 닿게 하세요.