Ner filter는 네트워크 통신 도중에 hook하여 패킷을 검사하는 네트워크 필터링을 위한 프레임워크입니다. 패킷의 검사는 expression의 집합인 rule에 의해 정해지며 해당 rule은 chain, table들을 통해 관리됩니다.
/net.netfilter/nf_tables_core.c 의 nft_do_chain
함수의 지역변수로 net filter 프레임워크 expression의 레지스터를 나타내어 index로 접근이 가능합니다. 이때 새로운 rule을 추가하는 로직에서 OOB취약점이 발생합니다.
해당 취약점은 /net/netfilter/nf_tables_api.c 에 정의된 [nft_parse_register](<https://elixir.bootlin.com/linux/v5.12/C/ident/nft_parse_register>)
와 [nft_validate_register_load](<https://elixir.bootlin.com/linux/v5.12/C/ident/nft_validate_register_load>)
에서 나타납니다.
static int nft_validate_register_load(enum nft_registers reg, unsigned int len)
{
if (reg < NFT_REG_1 * NFT_REG_SIZE / NFT_REG32_SIZE)
return -EINVAL;
if (len == 0)
return -EINVAL;
if (**reg * NFT_REG32_SIZE + len** > sizeof_field(struct nft_regs, data))
return -ERANGE;
return 0;
}
static unsigned int nft_parse_register(const struct nlattr *attr)
{
unsigned int reg;
reg = ntohl(nla_get_be32(attr));
switch (reg) {
case NFT_REG_VERDICT...NFT_REG_4:
return reg * NFT_REG_SIZE / NFT_REG32_SIZE;
**default**:
return reg + NFT_REG_SIZE / NFT_REG32_SIZE - NFT_REG32_00;
}
}
먼저 nft_parse_register
에서는 유효한 레지스터 번호 이상의 값을 검사하는 로직이 없어 유효하지 않은 레지스터 번호를 넣었을 때도 함수가 동작하여 결과를 리턴합니다. 두번째로 nft_validate_register_load
부분에서는 **reg * NFT_REG32_SIZE + len** > sizeof_field(struct nft_regs, data)
에서 nft_parse_register
의 리턴값인 reg
와 userlevel에서 온 값인 len
의 합을 통해 유효성 검사를 진행합니다. 이 경우 inteager overflow를 사용자가 트리거할 수 있어 검사를 우회할 수 있습니다.
예)
reg = 0xfffffffc, len = 0x20의 경우
(uint32_t)(((uint32_t)(4 * (0xfffffffc - 4)) + 0x20)
= 0x00000000
이 경우 사용자는 허용된 레지스터 범위를 넘어선 레지스터 시작 위치부터 0x3e0
에 위치한 스택 정보를 0x20
만큼 읽는 것이 가능합니다. (4*(0xfc -0x4) = 0x3e0
)
이 취약점을 통해 허용된 범위가 아닌 영역을 읽거나 쓸 수 있는 rule이 netfilter프레임워크에 등록되고 nf_tables
모듈에 정의된 skb_store_bits
, skb_copy_bits
를 통해 실제 읽고쓰기가 발생합니다. 해당 함수의 위치는 nft_payload_set_eval+269
, nft_payload_eval+121
에 존재하므로 디버깅에 참조할 수 있을 것입니다.
제공받은 코드를 바탕으로 커널 베이스 주소를 leak하기 위한 전략을 세우면 아래와 같습니다.