---

Introduction

The Linux kernel serves as the backbone for countless systems worldwide. One of its critical networking components is *netfilter*, which powers the modern firewall system called *nftables*. In April 2021, security researchers and *syzkaller*, Google’s open-source kernel fuzzer, discovered a vulnerability (CVE-2021-46992) within the netfilter core. This bug can lead to dangerous integer overflows, exposing Linux-powered systems to potential exploits and system crashes.

This article provides an exclusive, plain-language walkthrough of this bug, its technical background, the exploitation process, and how it was fixed.

Vulnerability Explained

Component: Linux Kernel - net/netfilter/nft_set_hash.c
Vulnerable Versions: Up to 5.12.-rc7
Bug: Improper handling and validation of 32-bit bucket counts in nft_hash_buckets(), leading to integer overflows.

What Happened?

Within the netfilter subsystem, when creating a new hash set for packet filtering, the system must estimate and allocate a number of buckets for storing rules (like bans, routing, etc.). This count is stored in a 32-bit variable. With no proper upper-bound checks, a sufficiently large input can cause this bucket value to overflow, especially when using functions for rounding up to the nearest power of two.

How the Bug Was Found

The excellent *syzkaller* fuzzing framework threw buckets of random and semi-random data at the kernel. It discovered that by setting the bucket count to a whopping x40000000 (over a billion), the system triggers an undefined behavior sanitization (UBSAN) error:

UBSAN: shift-out-of-bounds in ./include/linux/log2.h:57:13
shift exponent 64 is too large for 64-bit type 'long unsigned int'
...
nft_hash_buckets net/netfilter/nft_set_hash.c:411 [inline]
...
nft_hash_estimate.cold+x19/x1e net/netfilter/nft_set_hash.c:652
...

Translation: The function roundup_pow_of_two() tries to calculate the nearest power of two that's *not less than* your desired bucket count. When fed a value like x40000000, internal math computes a shift by 64, which is illegal for a 64-bit int.

Who Could Exploit This?

Any user with network namespace privileges or netlink access. On many systems, that can be an unprivileged user or container process.

What Can Happen?

- Denial of Service (DoS): The kernel crashes immediately, leading to machine shutdown and requiring a reboot.
- Undefined Behavior: Depending on the kernel version and system settings, other unintended side-effects may possibly be triggered, including information leaks or memory corruption.

Pseudocode Exploit Example

Here’s an illustrative example of exploiting the bug. This code isn’t weaponized, but demonstrates triggering the overflow:

// Compile and run as root or with CAP_NET_ADMIN capability
#include <sys/socket.h>
#include <linux/netlink.h>
#include <stdio.h>
#include <string.h>

#define HUGE_BUCKET x40000000

int main() {
    int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER);
    if (sock < ) {
        perror("socket");
        return 1;
    }

    struct {
        struct nlmsghdr nlh;
        char data[256];
    } req;

    memset(&req, , sizeof(req));
    req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req.data));
    req.nlh.nlmsg_type = /* NFT_MSG_NEWSET or similar */;
    req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;

    // Set the attribute representing bucket count to the huge value
    *((uint32_t *)req.data) = HUGE_BUCKET;

    send(sock, &req, req.nlh.nlmsg_len, );

    printf("Sent payload with huge bucket count. Check dmesg or syslog for crash/UBSAN message.\n");
    return ;
}

> Note: This code needs to be adapted to actually create a nftables set with a custom bucket count, but this skeleton is enough for educational demonstration.

Technical Deep Dive & Code Snippet

Source Location: net/netfilter/nft_set_hash.c

Vulnerable Code

static unsigned int nft_hash_buckets(const struct nft_set_desc *desc)
{
    unsigned int buckets = roundup_pow_of_two(...); // No upper limit!
    //...
}

What goes wrong:
If desc->size is very large, roundup_pow_of_two may shift bits too far, triggering undefined behavior or overflow.

The Failing Call Stack (from syzbot)

UBSAN: shift-out-of-bounds in ./include/linux/log2.h:57:13
shift exponent 64 is too large for 64-bit type
...
nft_hash_buckets net/netfilter/nft_set_hash.c:411

The Fix

Patch Summary:
The Linux kernel maintainers added extra checks to ensure that bucket values cannot grow beyond what a 32-bit int (or memory) can handle.

Fixed Code Snippet

#define NFT_SET_MAX_BUCKETS   (UINT_MAX / sizeof(struct nft_hash_bucket))

static unsigned int nft_hash_buckets(const struct nft_set_desc *desc)
{
    ...
    if (desired_buckets > NFT_SET_MAX_BUCKETS)
        desired_buckets = NFT_SET_MAX_BUCKETS;

    unsigned int buckets = roundup_pow_of_two(desired_buckets);
    ...
}

Now, even if the input is huge, it’s capped to a safe max.

Official fix:
- Commit on kernel.org

Impact & Recommendations

Affected: All Linux systems before 5.12.
Fixed in: Linux 5.12+

References

- syzkaller issue report
- Patch commit
- nftables documentation


Summary:
CVE-2021-46992 is a classic example of why bounds-checking and fuzz testing matter, even in advanced kernel code. If your devices run affected kernels and use nftables, patch promptly before an attacker sends your system into undefined behavior!


(c) 2024 – This technical write-up is exclusive, designed for clarity and practical security understanding.

Timeline

Published on: 02/28/2024 09:15:37 UTC
Last modified on: 12/24/2024 14:34:12 UTC