Back to Blog
high SEVERITY6 min read

How c.lang.security.use-after-free.use-after-free happens in C and how to fix it

A use-after-free vulnerability was discovered in `ggml-alloc.c` where `galloc->leaf_allocs` could be referenced after being freed during graph memory reallocation. The fix nullifies the pointer immediately after `free()` and uses explicit `sizeof(struct leaf_alloc)` to prevent undefined behavior. This defensive hardening eliminates an exploit primitive in a speech-to-text processing pipeline.

O
By Orbis AppSec
Published August 22, 2026Reviewed August 22, 2026

Answer Summary

This is a use-after-free vulnerability (CWE-416) in C within the `ggml_gallocr_reserve_n_impl` function of `ggml-alloc.c`. The variable `galloc->leaf_allocs` was freed and then immediately used in the `sizeof` expression of the subsequent `calloc` call on line 885. The fix sets the pointer to NULL after freeing and replaces `sizeof(galloc->leaf_allocs[0])` with `sizeof(struct leaf_alloc)` to avoid dereferencing the freed pointer.

Vulnerability at a Glance

cweCWE-416
fixSet pointer to NULL after free, use `sizeof(struct leaf_alloc)` instead of `sizeof(galloc->leaf_allocs[0])`
riskUndefined behavior leading to potential code execution or memory corruption
languageC
root cause`galloc->leaf_allocs` dereferenced in `sizeof` expression after being freed on the preceding line
vulnerabilityUse-After-Free

Introduction

In the cordova-plugins/moonshine-stt repository — a speech-to-text plugin for Cordova — we discovered a high-severity use-after-free vulnerability in ggml-alloc.c at line 885. The ggml_gallocr_reserve_n_impl function, responsible for reserving memory for computation graph nodes, freed galloc->leaf_allocs and then immediately referenced the freed pointer in a sizeof expression on the very next line.

While sizeof on a dereferenced pointer is typically evaluated at compile time in most implementations, the C standard does not guarantee this for all cases, and static analysis correctly flags this as a use-after-free pattern. More critically, this creates an exploit primitive — a code pattern that automated attack tooling can chain with other weaknesses to achieve memory corruption or code execution in a speech processing pipeline that handles untrusted audio input.

The Vulnerability Explained

The Problematic Code

Here's the vulnerable code in ggml_gallocr_reserve_n_impl around line 882:

if (galloc->n_leafs < graph->n_leafs) {
    free(galloc->leaf_allocs);
    galloc->leaf_allocs = calloc(graph->n_leafs, sizeof(galloc->leaf_allocs[0]));
    GGML_ASSERT(galloc->leaf_allocs != NULL);
}

The problem is subtle but dangerous:

  1. Line 883: free(galloc->leaf_allocs) — the memory is deallocated
  2. Line 884: sizeof(galloc->leaf_allocs[0]) — the freed pointer is dereferenced

After free() is called, galloc->leaf_allocs becomes a dangling pointer. The expression galloc->leaf_allocs[0] dereferences this dangling pointer. While many compilers resolve sizeof on a known type at compile time, the C standard considers any access through a freed pointer as undefined behavior. This means:

  • The compiler is free to optimize this code in unexpected ways
  • On some platforms or with certain compiler flags, this could actually read freed memory
  • Future compiler optimizations could change the behavior silently

How Could This Be Exploited?

In this specific context, ggml-alloc.c is part of the GGML tensor library used in the Moonshine speech-to-text engine. Consider this attack scenario:

  1. An attacker crafts a malicious audio input that causes the computation graph to be resized (triggering graph->n_leafs > galloc->n_leafs)
  2. Between the free() and the calloc(), if the freed memory is reclaimed (e.g., by another thread or an allocator that coalesces immediately), the sizeof expression could evaluate to an attacker-controlled value on implementations where it's not resolved at compile time
  3. This could lead to an undersized allocation, followed by out-of-bounds writes when the leaf_allocs array is populated

Additionally, there was a secondary issue on line 837 where malloc was used instead of calloc for hash_values, leaving the allocated memory uninitialized — a potential information leak vector.

Real-World Impact

This code runs on Android devices as part of a Cordova plugin processing speech input. A malicious audio file or crafted input could potentially:
- Crash the application (denial of service)
- Corrupt memory used by the tensor computation engine
- In a worst case, achieve code execution on the user's device

The Fix

The pull request makes two targeted changes:

Change 1: Zero-initialized hash values (Line 837)

Before:

free(galloc->hash_values);
galloc->hash_values = malloc(sizeof(struct hash_node) * galloc->hash_set.size);
GGML_ASSERT(galloc->hash_values != NULL);

After:

free(galloc->hash_values);
galloc->hash_values = calloc(galloc->hash_set.size, sizeof(struct hash_node));
GGML_ASSERT(galloc->hash_values != NULL);

This change replaces malloc with calloc, ensuring the hash_values array is zero-initialized. This prevents information leakage from uninitialized memory and eliminates a class of bugs where stale hash values could cause incorrect graph allocation behavior.

Change 2: Eliminate use-after-free in leaf_allocs (Line 884-885)

Before:

free(galloc->leaf_allocs);
galloc->leaf_allocs = calloc(graph->n_leafs, sizeof(galloc->leaf_allocs[0]));
GGML_ASSERT(galloc->leaf_allocs != NULL);

After:

free(galloc->leaf_allocs);
galloc->leaf_allocs = NULL;
galloc->leaf_allocs = calloc(graph->n_leafs, sizeof(struct leaf_alloc));
GGML_ASSERT(galloc->leaf_allocs != NULL);

This fix addresses the vulnerability in two ways:

  1. galloc->leaf_allocs = NULL — Immediately nullifies the dangling pointer after free(). If any code path accidentally accesses it before reallocation, it will trigger a clean NULL pointer dereference (detectable crash) rather than silent undefined behavior.

  2. sizeof(struct leaf_alloc) instead of sizeof(galloc->leaf_allocs[0]) — Eliminates the dereference of the freed pointer entirely by using the type name directly. This is semantically identical but avoids any reference to the freed memory.

Prevention & Best Practices

Defensive Coding Patterns for C Memory Management

  1. Always NULL after free: Adopt the pattern of setting pointers to NULL immediately after freeing them:
    c free(ptr); ptr = NULL;

  2. Use type names in sizeof, not pointer dereferences: Prefer sizeof(struct type) over sizeof(*ptr) when the pointer might be in an invalid state.

  3. Prefer calloc over malloc: calloc zero-initializes memory, preventing information leaks and making bugs more deterministic. It also has overflow checking for the multiplication of count * size.

  4. Use AddressSanitizer (ASan): Compile with -fsanitize=address during development to catch use-after-free at runtime.

  5. Static Analysis Integration: Tools like Semgrep can catch these patterns before they reach production. The rule c.lang.security.use-after-free.use-after-free specifically targets this class of bug.

Relevant Standards

  • CWE-416: Use After Free
  • CERT C Rule MEM30-C: Do not access freed memory
  • OWASP: Memory management vulnerabilities in native code

Key Takeaways

  • Never dereference a freed pointer, even in sizeof expressions — while often resolved at compile time, it's technically undefined behavior and static analyzers will rightfully flag it.
  • The sizeof(galloc->leaf_allocs[0]) pattern is dangerous after free(galloc->leaf_allocs) — use sizeof(struct leaf_alloc) instead to reference the type directly.
  • Setting galloc->leaf_allocs = NULL between free() and calloc() converts a silent use-after-free into a detectable crash — this is a critical defensive hardening practice.
  • Replacing malloc with calloc for hash_values eliminates uninitialized memory bugs — zero-initialization is almost always worth the negligible performance cost in security-sensitive code.
  • Speech-to-text processing pipelines handle untrusted input (audio files) — memory safety bugs in these paths are directly reachable by attackers.

How Orbis AppSec Detected This

  • Source: Untrusted computation graph data (graph->n_leafs) derived from model/audio input processing in the Moonshine STT pipeline
  • Sink: sizeof(galloc->leaf_allocs[0]) dereference at ggml-alloc.c:885 after free(galloc->leaf_allocs) on line 883
  • Missing control: No pointer nullification between free() and reuse; sizeof expression referenced freed memory
  • CWE: CWE-416 (Use After Free)
  • Fix: Set galloc->leaf_allocs = NULL after free and replace sizeof(galloc->leaf_allocs[0]) with sizeof(struct leaf_alloc) to eliminate the dangling pointer dereference

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

Use-after-free vulnerabilities remain one of the most dangerous classes of memory safety bugs in C code. This specific instance in ggml-alloc.c demonstrates how even seemingly innocuous patterns like sizeof(ptr[0]) can become hazardous when the pointer has been freed. The fix is minimal — nullify the pointer, use the type name directly — but the security improvement is significant: it eliminates an exploit primitive from a code path that processes untrusted audio input on mobile devices.

For developers working with GGML or similar tensor libraries in C, remember: every free() call should be immediately followed by a NULL assignment, and sizeof expressions should never depend on potentially-invalid pointers.

References

Frequently Asked Questions

What is use-after-free?

Use-after-free is a memory safety vulnerability where a program continues to use a pointer after the memory it references has been deallocated, leading to undefined behavior including crashes, data corruption, or arbitrary code execution.

How do you prevent use-after-free in C?

Always set pointers to NULL immediately after calling free(), avoid dereferencing freed pointers in any expression (including sizeof on pointer dereferences), and use static analysis tools like Semgrep to detect these patterns automatically.

What CWE is use-after-free?

CWE-416: Use After Free — the product references memory after it has been freed, which can cause a program to crash, use unexpected values, or execute code.

Is setting pointers to NULL enough to prevent use-after-free?

Setting pointers to NULL after free is a critical defensive practice that turns use-after-free into a NULL pointer dereference (which is easier to detect and less exploitable), but comprehensive prevention also requires careful code review, ownership semantics, and static analysis.

Can static analysis detect use-after-free?

Yes, static analysis tools like Semgrep can detect many use-after-free patterns by tracking pointer lifecycle through free/use sequences, though complex interprocedural cases may require more advanced tools like AddressSanitizer or Valgrind for runtime detection.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #45

Related Articles

critical

How Command Injection via Unescaped Line Terminators Happens in Node.js and How to Fix It

A critical command injection vulnerability (CVE-2026-9277) was discovered in the shell-quote npm package version 1.8.3, where unescaped line terminators could allow attackers to execute arbitrary code. This fix upgrades shell-quote to version 1.9.0 using npm overrides to ensure all instances in the dependency tree are patched, eliminating the attack vector across the entire application.

critical

How Distributed Lock Takeover Happens in Node.js and How to Fix It

A critical vulnerability in `redis-lock/server.mjs` allowed any authenticated client to release another client's lock by guessing predictable holder identifiers like process IDs or hostnames. The fix implements cryptographically random `lockId` values that are minted on lock acquisition and validated on release, eliminating the exploit primitive entirely.

high

How Denial of Service via Infinite Loop happens in JavaScript (nanoid) and how to fix it

A high-severity denial of service vulnerability (CVE-2026-67213) was discovered in nanoid versions before 5.1.6 and 3.3.18, where the `customAlphabet` function could enter an infinite loop during random ID generation. The fix upgrades the transitive nanoid dependency from 3.3.16 to 3.3.18 using pnpm overrides, ensuring the vulnerable code path is eliminated from the entire dependency tree including PostCSS.

high

How Information Disclosure via Unstripped Credential Headers Happens in Electron Apps and How to Fix It

A high-severity vulnerability (CVE-2026-54673) in the builder-util-runtime package allowed sensitive credential headers to leak during HTTP redirects in Electron applications. The fix upgrades builder-util-runtime from version 9.5.1 to 9.7.0, which properly strips authentication headers before following redirects to prevent information disclosure.

high

How Command Injection happens in PHP and how to fix it

A high-severity command injection vulnerability was discovered in `lib/Controller/Helper.php` where the `corruptline()` method used `exec()` to run sed and awk commands with user-controlled input. The fix replaced all shell command execution with native PHP file operations using `SplFileObject`, eliminating the command injection attack surface entirely.

high

How Missing CSRF Middleware happens in Express.js and how to fix it

A high-severity CSRF vulnerability was discovered in `libProxy.js` of an Express.js application — the app had no CSRF middleware protecting its state-changing routes, leaving them open to cross-site request forgery attacks. The fix introduces a `csrf` token library, a `/csrf-token` endpoint to issue tokens, and a middleware that validates `x-csrf-token` headers or `_csrf` body fields on all non-safe HTTP methods. This proactive hardening removes an exploit primitive that could be chained with ot