Linux is known for its robust security, but every now and then, even the world’s leading open-source operating system faces flaws. One notable example is CVE-2024-27019, which was discovered and promptly patched in the Linux kernel’s netfilter subsystem, specifically in the nftables implementation.
In plain English, this vulnerability allowed the possibility of a "data race"—where two or more operations could conflict and corrupt memory—when two particular parts of the nftables code ran at the same time. Let’s break down what went wrong, how it was fixed, and how it could have been exploited.
What’s the Vulnerability?
The problem was found in the __nft_obj_type_get() function. In Linux kernel programming, especially with firewall functionalities (which nftables provides), multiple threads or CPUs might try to access or change the same data. If you don’t handle this right, nasty bugs and even security holes can pop up. This exact issue happened when the kernel was working with the nf_tables_objects list.
__nft_obj_type_get(): This scans the list to find a specific object type.
The bug? These could happen at the same time (they’re “concurrent”). Without proper locking, if one thread was removing an object while another was searching for it, the kernel could crash, leak data, or cause unpredictable behavior.
Before the fix, the code used a simple loop
list_for_each_entry(obj_type, &nf_tables_objects, list) {
if (obj_type->family == family &&
obj_type->type == type)
return obj_type;
}
This is not safe when something else could change the list while we’re reading it.
The fix: The maintainers rewrote the code to use Linux’s "RCU" (Read-Copy-Update) mechanism—a safe way to read lists that can be changed from another thread. Specifically, they used list_for_each_entry_rcu() and wrapped the call with rcu_read_lock() and rcu_read_unlock(). Here’s the new pattern:
struct nft_obj_type *nft_obj_type_get(u8 family, u8 type)
{
struct nft_obj_type *obj_type;
rcu_read_lock();
obj_type = __nft_obj_type_get(family, type);
rcu_read_unlock();
return obj_type;
}
static struct nft_obj_type *__nft_obj_type_get(u8 family, u8 type)
{
struct nft_obj_type *obj_type;
list_for_each_entry_rcu(obj_type, &nf_tables_objects, list) {
if (obj_type->family == family && obj_type->type == type)
return obj_type;
}
return NULL;
}
This guards readers (searching) from writers (removal). See the patch on lore.kernel.org.
Potentially escalate privileges (depends on what’s in memory)
Scenario: A local user with the ability to trigger nftables operations could cause race conditions by rapidly registering and unregistering objects, while another process queried the list. If an attacker times it right, they could trigger a use-after-free, leading to memory corruption.
Process B blitzes the kernel with calls to an nftables query, which calls nft_obj_type_get().
If things overlap just right, Process B could get a pointer to an object that got freed by Process A—this risks use-after-free.
Simple code to trigger the race (in real C code, you’d use the Netlink API)
import threading
def register_and_unregister():
while True:
# toolkit_call_add_object()
# toolkit_call_remove_object()
pass
def query_type():
while True:
# toolkit_query_object_type()
pass
t1 = threading.Thread(target=register_and_unregister)
t2 = threading.Thread(target=query_type)
t1.start()
t2.start()
Note: You’d need real kernel interface calls, rights, and a vulnerable kernel for this to work.
References & Further Reading
- The Original Patch on lore.kernel.org
- CVE Record on NVD
- Linux Kernel Commit in upstream git
- Netfilter nftables Documentation
Conclusion
CVE-2024-27019 is a classic example of why concurrent programming, especially in something as important as the Linux kernel firewall, is hard! Luckily, it was caught and resolved before any known real-world exploitation. If you manage Linux kernels—especially running firewalls or containers—update to a patched kernel to stay safe.
Timeline
Published on: 05/01/2024 06:15:20 UTC
Last modified on: 06/17/2024 17:46:01 UTC