[하루한줄] CVE-2024-1086: Linux Kernel 내 Use-After-Free
URL
Target
- Linux Kernel 6.1 ~ 6.5 버전
Explain
이 취약점은 Linux Kernal 내 리눅스 패킷 필터링 및 네트워크 주소 변환(NAT) 프레임워크인 netfilter의 nf_tables 구성 요소에서 UAF가 발생하는 취약점입니다.
이 취약점은 nftables의 패킷 처리 과정에서 발생하며,  nf_hook_slow()와 nft_verdict_init() 함수에서 발생합니다.
아래는 nf_hook_slow() 함수의 일부입니다.
int nf_hook_slow(struct sk_buff *skb, struct nf_hook_state *state, 
                 const struct nf_hook_entries *e, unsigned int s)
{
    unsigned int verdict;
    int ret;
    for (; s < e->num_hook_entries; s++) {
        verdict = nf_hook_entry_hookfn(&e->hooks[s], skb, state);
        
        switch (verdict & NF_VERDICT_MASK) {
            case NF_ACCEPT:
                break;
            case NF_DROP:
                kfree_skb(skb);
                ret = NF_DROP_GETERR(verdict);
                if (ret != 0)  // 오타 수정
                    ret = -EPERM;
                return ret;
            case NF_QUEUE:
                ret = nf_queue(skb, state, s, verdict);
                if (ret == 1)
                    continue;
                return ret;
            default:
                break;
        }
        return 0;
    }
    return 1;
}위 코드는 netfilter 모듈 내의 nf_hook_slow() 함수의 일부로, 패킷 처리 규칙을 반복문 안에서 평가합니다. 패킷에 대한 verdict 값을 얻고, 그 값과 NF_VERDICT_MASK 매크로 값을 통해 패킷 처리를 결정합니다. verdict 값은 패킷 처리 결과값을 나타내며, 이는 사용자가 설정할 수 있는 값입니다.
nf_hook_slow() 함수에서는 NF_DROP일 경우 kfree_skb()를 호출하여 패킷을 해제합니다. 이후 NF_DROP_GETERR(verdict)에서 공격자가 설정한 verdict 값이 “0xFFFF0000”이라면, 함수 내부 연산을 통해 ret 값은 -65535로 설정됩니다. 이 값은 이후 NF_ACCEPT로 처리되어 패킷이 계속해서 처리됩니다.
아래는 nft_verdict_init() 함수입니다.
static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data, struct nft_data_desc *desc, const struct_nlattr *nla)
{
    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;
        }
    }
    return 0;
}nft_verdict_init() 함수에서 data->verdict.code는 규칙 설정에 대한 리턴 값으로 사용되며, 공격자가 “0xFFFF0000” 값을 설정할 경우 취약점이 발생합니다.
NF_VERDICT_MASK 매크로를 통해 verdict.code의 하위 16비트를 추출하면, “0xFFFF0000 & 0x0000FFFF”가 되어 “0x00000000”이 됩니다. 이 값은 NF_DROP을 의미하기 때문에 해당 패킷을 드롭하는 것으로 처리됩니다. 그러나 nf_hook_slow() 함수에서 패킷을 드롭하려는 처리 후, NF_DROP_GETERR(verdict) 연산에 의해 ret 값은 -65535로 설정되며, NF_ACCEPT로 처리됩니다.
이로 인해 이미 kfree_skb()로 해제된 패킷이 다시 처리되어 이전에 해제된 소켓 메모리(skb)를 참조하게 되어 Use-After-Free(UAF) 상황이 발생하고, 그 후 다시 소켓 메모리를 해제하면서 이중 해제(double-free)가 발생하게 됩니다.
패치된 nft_verdict_init() 함수는 다음과 같습니다:
switch (data->verdict.code) {
  case NF_ACCEPT:
  case NF_DROP:
  case NF_QUEUE:
     break;
  case NFT_CONTINUE: 
  case NFT BREAK:
  case NFT_RETURN:
     data->verdict.chain-chain;
     break;
  default:
     return -EINVAL;
  }data->verdict.code 값에 대해 검증이 추가되었습니다. 이제 verdict.code 값은 허용된 값들만 처리하고, 잘못된 값이 들어오면 무조건 -EINVAL을 반환하여 악의적인 입력을 차단합니다. 이로 인해 패킷 처리가 정상적으로 이루어지며, 취약점을 방지할 수 있습니다.
결론적으로, 이 취약점은 nf_hook_slow()에서 패킷을 처리하고, 잘못된 verdict 값으로 인해 이미 해제된 메모리를 참조하게 되어 Use-After-Free(UAF)와 이중 해제(double-free) 문제가 발생하는 것입니다. 패치된 버전에서는 verdict.code 값을 직접 검증하여 유효하지 않은 값에 대해 오류를 반환함으로써 이 문제를 해결합니다.
Reference
본 글은 CC BY-SA 4.0 라이선스로 배포됩니다. 공유 또는 변경 시 반드시 출처를 남겨주시기 바랍니다.
