Back to Blog
critical SEVERITY5 min read

How out-of-bounds array access happens in C kernel modules and how to fix it

A high-severity out-of-bounds array access vulnerability was discovered in the natflow_conntrack.c kernel module where the `ct->proto.tcp.state` value was used directly as an array index without bounds validation. An attacker capable of manipulating TCP connection state could trigger reads beyond the `tcp_conntrack_names[]` array, potentially leaking kernel memory or causing system crashes. The fix adds a simple bounds check using `ARRAY_SIZE()` before array access.

O
By Orbis AppSec
Published July 5, 2026Reviewed July 5, 2026

Answer Summary

This is an out-of-bounds array access vulnerability (CWE-125) in a C Linux kernel module where `ct->proto.tcp.state` is used as an index into `tcp_conntrack_names[]` without validation. If the state value exceeds the array bounds, the kernel reads unintended memory, risking information disclosure or crashes. The fix uses a ternary operator with `ARRAY_SIZE()` to validate the index before access, returning "UNKNOWN" for invalid states.

Vulnerability at a Glance

cweCWE-125
fixAdded ARRAY_SIZE() bounds check before array access
riskKernel memory disclosure or system crash
languageC (Linux kernel module)
root causeTCP state used as array index without bounds validation
vulnerabilityOut-of-bounds array read

Introduction

In the natflow kernel module, we discovered a high-severity out-of-bounds array access vulnerability in natflow_conntrack.c at line 321. The conntrackinfo_read() function, which handles reading connection tracking information, used ct->proto.tcp.state directly as an index into the tcp_conntrack_names[] array without any validation.

This matters because kernel modules operate with the highest privileges on a Linux system. An out-of-bounds read in kernel space doesn't just crash an application—it can leak sensitive kernel memory (including cryptographic keys, credentials, or memory layout information useful for further exploits) or cause a complete system crash.

The Vulnerability Explained

The vulnerable code in natflow_conntrack.c looked like this:

case IPPROTO_TCP:
    ct_i->len += sprintf(ct_i->data + ct_i->len, "%s ", tcp_conntrack_names[ct->proto.tcp.state]);

The problem is deceptively simple: ct->proto.tcp.state is used directly as an array index with no validation. The tcp_conntrack_names[] array contains a fixed number of string entries corresponding to valid TCP connection states (like TCP_CONNTRACK_NONE, TCP_CONNTRACK_SYN_SENT, TCP_CONNTRACK_ESTABLISHED, etc.).

How Could This Be Exploited?

If an attacker can manipulate the TCP state value stored in a connection tracking entry—whether through crafted network packets, race conditions, or memory corruption elsewhere—they could set ct->proto.tcp.state to a value like TCP_CONNTRACK_MAX + 100.

When the kernel executes tcp_conntrack_names[ct->proto.tcp.state], it calculates the memory address as:

address = base_address_of_tcp_conntrack_names + (malicious_state * sizeof(char*))

This reads a pointer from kernel memory far beyond the intended array, then attempts to dereference that pointer in the sprintf() call. The result could be:

  1. Information Disclosure: The "string" printed could contain arbitrary kernel memory contents
  2. Kernel Crash: If the out-of-bounds pointer points to unmapped memory, the kernel panics
  3. Exploitation Primitive: Controlled memory reads can help attackers bypass KASLR (Kernel Address Space Layout Randomization)

Real-World Attack Scenario

An attacker on the local network sends specially crafted TCP packets designed to create connection tracking entries with corrupted state values. When an administrator runs a diagnostic tool that reads /proc entries populated by conntrackinfo_read(), the kernel reads beyond the array bounds, potentially including:
- Portions of the kernel heap containing credentials
- Function pointers useful for building ROP chains
- Memory layout information defeating KASLR

The Fix

The fix adds a bounds check before accessing the array, using the kernel's ARRAY_SIZE() macro:

Before (Vulnerable)

case IPPROTO_TCP:
    ct_i->len += sprintf(ct_i->data + ct_i->len, "%s ", tcp_conntrack_names[ct->proto.tcp.state]);

After (Fixed)

case IPPROTO_TCP:
    ct_i->len += sprintf(ct_i->data + ct_i->len, "%s ",
                     ct->proto.tcp.state < ARRAY_SIZE(tcp_conntrack_names) ?
                     tcp_conntrack_names[ct->proto.tcp.state] : "UNKNOWN");

Why This Works

  1. ARRAY_SIZE() macro: This kernel macro calculates the number of elements in a statically-allocated array at compile time, ensuring the bounds check always matches the actual array size.

  2. Ternary operator for inline validation: The check happens in the same expression as the access, making it impossible to forget the validation or have it optimized away.

  3. Safe fallback value: Invalid states now print "UNKNOWN" instead of causing undefined behavior. This maintains functionality while eliminating the security risk.

  4. No performance impact: The bounds check is a simple integer comparison—negligible overhead compared to the sprintf() call that follows.

Prevention & Best Practices

For Kernel Module Developers

  1. Always validate array indices: Any value from external sources (network, user input, other kernel subsystems) must be bounds-checked before use as an array index.

  2. Use ARRAY_SIZE() consistently: Never hardcode array sizes. The ARRAY_SIZE() macro automatically stays synchronized with array declarations.

  3. Prefer enums with explicit bounds: Define TCP_CONNTRACK_MAX or similar constants and use them for validation:
    c if (state >= TCP_CONNTRACK_MAX) { return -EINVAL; }

  4. Enable kernel static analysis: Run sparse and enable -Warray-bounds in your build configuration.

General C Best Practices

  • Use static analysis tools (Coverity, CodeQL, Semgrep) in CI/CD pipelines
  • Consider using bounded string functions like snprintf() instead of sprintf()
  • Implement defensive programming: validate all inputs, even from "trusted" sources

Key Takeaways

  • Never use external values as array indices without bounds checking—the ct->proto.tcp.state field should have been validated before use in tcp_conntrack_names[]
  • Use ARRAY_SIZE() for bounds checks in kernel code—it's compile-time evaluated and always matches the actual array size
  • Kernel memory disclosure vulnerabilities are critical—even a "simple" array overread can leak KASLR offsets, credentials, or cryptographic material
  • The fix pattern is reusable: index < ARRAY_SIZE(array) ? array[index] : default is a safe idiom for any array access with potentially untrusted indices
  • Regression tests should cover boundary conditions—the included test explicitly checks TCP_CONNTRACK_MAX and values beyond it

How Orbis AppSec Detected This

  • Source: The ct->proto.tcp.state field from connection tracking structures, which can be influenced by network packet processing
  • Sink: Direct array indexing at tcp_conntrack_names[ct->proto.tcp.state] in natflow_conntrack.c:321
  • Missing control: No bounds validation before using the state value as an array index
  • CWE: CWE-125 (Out-of-bounds Read)
  • Fix: Added ARRAY_SIZE() bounds check with ternary operator to return "UNKNOWN" for invalid state values

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This vulnerability demonstrates a fundamental truth about C programming: every array access is a potential security boundary. The tcp_conntrack_names[] array access seemed innocuous—just converting a state enum to a human-readable string—but the missing bounds check created a kernel memory disclosure vulnerability.

The fix is elegant in its simplicity: a single conditional expression with ARRAY_SIZE() transforms dangerous undefined behavior into safe, predictable output. When writing kernel code or any security-sensitive C, make bounds checking reflexive. The few CPU cycles spent on validation are infinitely cheaper than the cost of a kernel memory leak or system crash.

References

Frequently Asked Questions

What is an out-of-bounds array access vulnerability?

An out-of-bounds array access occurs when code reads or writes memory outside the allocated bounds of an array, typically caused by using an unchecked index value that exceeds the array's size.

How do you prevent out-of-bounds array access in C?

Always validate array indices against the array size before access using macros like ARRAY_SIZE(), sizeof(), or explicit bounds constants. In kernel code, use kernel-provided macros and static analysis tools.

What CWE is out-of-bounds array access?

Out-of-bounds read is classified as CWE-125 (Out-of-bounds Read), while out-of-bounds write is CWE-787 (Out-of-bounds Write).

Is using unsigned integers enough to prevent out-of-bounds access?

No, unsigned integers only prevent negative indices. You must still validate that the index is less than the array size to prevent access beyond the upper bound.

Can static analysis detect out-of-bounds array access?

Yes, static analysis tools like Coverity, Sparse, and Coccinelle can detect many out-of-bounds access patterns, especially when array sizes are known at compile time.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #28

Related Articles

critical

How Server-Side Request Forgery (SSRF) happens in Node.js IP address parsing and how to fix it

A critical SSRF vulnerability (CVE-2026-69192) was discovered in the ip-address npm package version 10.2.0, which could allow attackers to bypass IP address validation and access internal services. The fix upgrades the dependency to version 10.3.1, which properly handles edge cases in IP address parsing that previously allowed trust-boundary bypasses.

critical

How Sensitive Data Exposure happens in Python web applications and how to fix it

A critical sensitive data exposure vulnerability was discovered in `nodes/google_gemini.py` where the Google Gemini API key was returned in plaintext through a web endpoint. The fix masks the token in API responses, preventing credential theft from any client that queries the token endpoint. This protects downstream users of this Node.js library from unauthorized access to their Google Gemini services.

high

How Authentication Bypass happens in Next.js App Router with Turbopack and how to fix it

A critical authentication bypass vulnerability (CVE-2026-64642) was discovered in Next.js versions prior to 16.2.11, specifically affecting App Router applications using Turbopack with a single locale configuration. This vulnerability allowed attackers to bypass middleware and proxy protections, potentially gaining unauthorized access to protected routes and resources that should have been secured by authentication checks.

critical

How SQL Injection Happens in CSV-to-SQL Converters and How to Fix It

A critical SQL injection vulnerability was discovered in the `csv2sql()` function in `src/data/converter/csv.js`, where CSV data and table names were directly interpolated into SQL INSERT statements without sanitization. The fix implements input validation through identifier sanitization and proper value escaping, eliminating the attack surface while preserving legitimate functionality.

critical

How Command Injection happens in Python subprocess and how to fix it

A critical command injection vulnerability was discovered in the `open_directory` method of `src/jm_view_server/app.py`, where user-controlled path input was passed directly into a shell command via `subprocess.Popen`. By switching from string-based shell execution to a list-based argument format, the fix eliminates the ability for attackers to inject malicious shell commands through crafted directory paths.

critical

How Hardcoded Cryptographic Keys in JavaScript Proxy Scripts Get Exposed and How to Fix Them

A critical vulnerability was discovered in `ghs/91Pornad.js` where AES encryption keys, initialization vectors, and HMAC signing salts were stored as plaintext string constants in a publicly distributed proxy script. Since these scripts are fetched from GitHub raw URLs by Quantumult X and Surge users, anyone could extract the cryptographic credentials and forge API requests or decrypt responses. The fix applies base64 encoding via `atob()` to obfuscate the sensitive values at rest.