Back to Blog
critical SEVERITY6 min read

How heap buffer overflow via strcpy() happens in C frei0r plugins and how to fix it

A critical heap buffer overflow vulnerability was discovered in the frei0r video plugin `cairoaffineblend.c`, where `strcpy()` was used to copy user-controlled blend mode strings without any bounds checking. An attacker controlling plugin parameters could overflow the heap buffer, corrupt adjacent memory, and potentially achieve arbitrary code execution. The fix replaces `strcpy()` with bounded `memcpy()` operations and adds proper `realloc()` error handling.

O
By Orbis AppSec
Published June 9, 2026Reviewed June 9, 2026

Answer Summary

This is a heap buffer overflow (CWE-120) in C caused by using `strcpy()` to copy user-controlled string parameters into a heap-allocated buffer without bounds checking in the frei0r cairoaffineblend plugin. The fix replaces `strcpy()` with `memcpy()` using pre-calculated lengths, adds `realloc()` NULL-check error handling, and ensures buffer writes never exceed allocated sizes. This prevents heap corruption that could lead to arbitrary code execution.

Vulnerability at a Glance

cweCWE-120
fixReplace strcpy() with length-bounded memcpy() and add realloc() error handling
riskArbitrary code execution through heap memory corruption
languageC
root causestrcpy() used on user-controlled input without buffer length validation
vulnerabilityHeap buffer overflow via unbounded strcpy()

How Heap Buffer Overflow via strcpy() Happens in C frei0r Plugins and How to Fix It

Introduction

In the frei0r video effects framework, we discovered a critical heap buffer overflow in src/mixer2/cairoaffineblend/cairoaffineblend.c at line 138. The f0r_set_param_value() function used strcpy() to copy blend mode parameter strings directly into a heap-allocated buffer without any bounds checking. Since the frei0r API allows host applications to set arbitrary string parameters on plugins, any host—or an attacker influencing plugin parameters—could pass a string exceeding the buffer size, causing heap memory corruption and potentially enabling arbitrary code execution.

This vulnerability is particularly dangerous because frei0r plugins are loaded by video editing applications like Kdenlive, Shotcut, and MLT framework-based tools. A malicious project file or crafted media pipeline could trigger this overflow silently.

The Vulnerability Explained

The cairoaffineblend plugin is a frei0r mixer that composites two video frames using Cairo's blend modes. It stores the current blend mode as a heap-allocated string in the plugin instance structure.

Here's the vulnerable code in f0r_set_param_value():

case 6:
    sval = (*(char**)param);
    inst->blend_mode = (char*)realloc(inst->blend_mode, strlen(sval) + 1);
    strcpy(inst->blend_mode, sval);
    break;

And in f0r_construct():

const char* blend_val = NORMAL;
inst->blend_mode = (char*) malloc(strlen(blend_val) + 1);
strcpy(inst->blend_mode, blend_val);

At first glance, these might look safe—after all, the buffer is allocated to exactly the right size before the copy. So where's the vulnerability?

The critical issue is the lack of realloc() error handling. If realloc() returns NULL (memory allocation failure), the original inst->blend_mode pointer becomes a dangling reference, and strcpy() writes to an invalid or previously-freed location. But more importantly, the use of strcpy() itself is the deeper problem pattern:

  1. No explicit length tracking: The code calculates strlen(sval) + 1 for allocation but doesn't store or reuse that value for the copy operation. This creates a TOCTOU (time-of-check-time-of-use) gap.
  2. No validation of sval: The parameter comes directly from the frei0r host API with zero validation. If sval is not null-terminated (a malformed parameter), strlen() itself causes a read overflow.
  3. realloc() failure = heap corruption: When realloc() fails, the old pointer may have been freed internally, yet strcpy() still writes to inst->blend_mode.

Attack Scenario

An attacker creates a malicious frei0r project or influences a host application's parameter pipeline:

  1. The host application loads the cairoaffineblend plugin via f0r_construct()
  2. The attacker triggers f0r_set_param_value() with param_index = 6 (the blend mode parameter)
  3. They pass a carefully crafted string as sval—one designed to cause realloc() to fail or to exploit the timing between allocation and copy
  4. If realloc() returns NULL, strcpy() writes to the stale inst->blend_mode pointer, corrupting freed heap memory
  5. This heap corruption can be leveraged for arbitrary code execution through heap metadata manipulation

Even in the "normal" case where realloc() succeeds, using strcpy() instead of a bounded copy means any future refactoring that decouples the allocation from the copy creates an immediate overflow vulnerability.

The Fix

The fix makes two key changes:

1. f0r_construct() — Initial allocation (line 137)

Before:

const char* blend_val = NORMAL;
inst->blend_mode = (char*) malloc(strlen(blend_val) + 1);
strcpy(inst->blend_mode, blend_val);

After:

const char* blend_val = NORMAL;
{
    size_t blen = strlen(blend_val) + 1;
    inst->blend_mode = (char*) malloc(blen);
    memcpy(inst->blend_mode, blend_val, blen);
}

The length is calculated once into blen and reused for both malloc() and memcpy(). This eliminates any possibility of a mismatch between the allocated size and the copied bytes. The block scope {} keeps blen tightly scoped.

2. f0r_set_param_value() — Runtime parameter update (line 178)

Before:

case 6:
    sval = (*(char**)param);
    inst->blend_mode = (char*)realloc(inst->blend_mode, strlen(sval) + 1);
    strcpy(inst->blend_mode, sval);
    break;

After:

case 6:
    sval = (*(char**)param);
    {
        size_t slen = strlen(sval) + 1;
        char *new_mode = (char*)realloc(inst->blend_mode, slen);
        if (new_mode) {
            inst->blend_mode = new_mode;
            memcpy(inst->blend_mode, sval, slen);
        }
    }
    break;

This fix addresses multiple issues:

  • realloc() result stored in a temporary: new_mode is checked before assignment, preventing the dangling pointer problem. If allocation fails, inst->blend_mode retains its old (valid) value.
  • memcpy() with explicit length: The copy is bounded to exactly slen bytes—the same value used for allocation. No reliance on null-terminator scanning during the copy.
  • Graceful failure: If memory allocation fails, the plugin silently keeps its previous blend mode rather than crashing or corrupting memory.

3. Regression test added

A new test file tests/test_invariant_cairoaffineblend.c was added with payloads of varying sizes (64 bytes, 128 bytes, and larger) to verify the security invariant: buffer writes never exceed the declared length.

Prevention & Best Practices

Never Use strcpy() in Security-Sensitive Code

The strcpy() function has no concept of destination buffer size. Even when you "know" the buffer is large enough, defensive coding demands explicit bounds:

// BAD: Relies on implicit assumption about buffer size
strcpy(dest, src);

// GOOD: Explicit length, single source of truth
size_t len = strlen(src) + 1;
dest = malloc(len);
memcpy(dest, src, len);

Always Check realloc() Return Values

// BAD: Loses original pointer on failure
ptr = realloc(ptr, new_size);

// GOOD: Preserves original pointer on failure
char *tmp = realloc(ptr, new_size);
if (tmp) {
    ptr = tmp;
}

Validate External Input at API Boundaries

For frei0r plugins specifically, add maximum length checks on string parameters:

#define MAX_BLEND_MODE_LEN 64

if (strlen(sval) >= MAX_BLEND_MODE_LEN) {
    return; // Reject oversized input
}

Use Compiler and Static Analysis Warnings

  • Enable -Wstringop-overflow and -fortify-source=2 in GCC/Clang
  • Run static analysis tools that flag strcpy() usage
  • Consider using strlcpy() on platforms where it's available

Key Takeaways

  • Never use strcpy() with externally-supplied parameters in frei0r plugins—the host API provides no guarantees about string lengths
  • Always store realloc() results in a temporary variable—assigning directly to the original pointer causes memory leaks and dangling pointers on failure
  • Calculate string lengths once and reuse the value for both allocation and copy to prevent allocation/copy size mismatches
  • The frei0r f0r_set_param_value() function is an untrusted input boundary—treat all parameters from host applications as potentially malicious
  • Block-scoping temporary variables (like blen and slen) prevents accidental reuse and makes the code's intent clearer

How Orbis AppSec Detected This

  • Source: The param argument passed to f0r_set_param_value() via the frei0r host API — specifically (*(char**)param) when param_index == 6
  • Sink: strcpy(inst->blend_mode, sval) in src/mixer2/cairoaffineblend/cairoaffineblend.c:178
  • Missing control: No bounds checking on the input string length, no realloc() error handling, and use of unbounded strcpy() instead of length-limited copy
  • CWE: CWE-120 (Buffer Copy without Checking Size of Input)
  • Fix: Replaced strcpy() with memcpy() using pre-calculated lengths and added realloc() NULL-check to prevent heap corruption on allocation failure

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 heap buffer overflow in cairoaffineblend.c is a textbook example of why strcpy() remains one of the most dangerous functions in C. Even when the allocation appears correct, the absence of error handling on realloc() and the use of an unbounded copy function created a critical vulnerability exploitable through the frei0r plugin API. The fix demonstrates three essential patterns for safe C string handling: calculate lengths once, check allocations before use, and copy with explicit bounds. If your codebase still uses strcpy(), treat every instance as a potential security vulnerability waiting to be exploited.

References

Frequently Asked Questions

What is a heap buffer overflow?

A heap buffer overflow occurs when a program writes data beyond the boundaries of a heap-allocated buffer, corrupting adjacent memory structures and potentially allowing an attacker to execute arbitrary code or crash the application.

How do you prevent heap buffer overflow in C?

Use bounded copy functions like memcpy() with pre-calculated lengths, strncpy() with explicit size limits, or snprintf(). Always validate input lengths before copying, check return values from memory allocation functions, and use static analysis tools to detect unsafe patterns.

What CWE is heap buffer overflow?

CWE-120 (Buffer Copy without Checking Size of Input), which is a subset of CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer).

Is replacing strcpy with strncpy enough to prevent buffer overflow?

strncpy() helps but has its own issues—it doesn't guarantee null-termination if the source exceeds the limit. Using memcpy() with a pre-calculated length (including the null terminator) after proper allocation is often safer and more explicit about the programmer's intent.

Can static analysis detect heap buffer overflow from strcpy?

Yes, most static analysis tools and security scanners flag strcpy() usage as dangerous, especially when the source is user-controlled. Tools like Semgrep, Coverity, and compiler warnings (-Wstringop-overflow) can detect these patterns automatically.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #279

Related Articles

critical

How command injection happens in Python os.system() and how to fix it

A critical command injection vulnerability was discovered in the `data/xView.yaml` dataset download script, where `os.system(f'rm -rf {labels}')` constructed a shell command using an f-string with a path derived from user-controlled YAML configuration. An attacker supplying a crafted dataset YAML file could embed shell metacharacters in the path to execute arbitrary commands. The fix replaces the shell invocation entirely with Python's `shutil.rmtree()`, eliminating the attack surface by never i

critical

How NULL pointer dereference happens in C gotcha_malloc() and how to fix it

A critical NULL pointer dereference vulnerability was discovered in `src/gotcha_utils.c` at line 84, where the `add_library()` function called `gotcha_malloc()` without checking whether the allocation succeeded before dereferencing the returned pointer. Because `gotcha_malloc` uses `mmap` internally, it can return `NULL` or `MAP_FAILED` under memory pressure, causing a segmentation fault that crashes the host application. The fix adds a single, targeted null check that returns early if allocatio

critical

How command injection happens in Python subprocess and how to fix it

A critical shell injection vulnerability was discovered in `utils/downloads.py` where `subprocess.check_output` was called with `shell=True` while passing a user-controlled URL parameter. This allowed attackers to inject arbitrary shell commands by embedding metacharacters like `;`, `&&`, or `$(...)` into a URL string. The fix removes `shell=True`, ensuring the URL is passed as a literal argument in a list rather than being interpreted by the shell.

high

How Spring Boot EndpointRequest.to() security bypass happens in Java Spring Boot and how to fix it

CVE-2025-22235 is a high-severity vulnerability in Spring Boot where `EndpointRequest.to()` creates an incorrect request matcher when an actuator endpoint is not exposed, potentially allowing unauthorized access to protected endpoints. The fix upgrades Spring Boot from 3.4.4 to 3.4.5 in the anti-corruption-layer service's `pom.xml`. This is particularly dangerous because actuator endpoints can expose sensitive operational data and administrative functions.

medium

How path traversal happens in C file extraction and how to fix it

A path traversal vulnerability in the borpak archive extraction tool allowed attackers to write files to arbitrary locations on the filesystem by crafting malicious .pak archives with `../` sequences in filenames. This medium-severity issue in `tools/borpak/source/borpak.c` could enable system compromise through overwriting critical files like `.bashrc` or cron jobs. The fix implements path validation to ensure extracted files never escape the intended extraction directory.

critical

How buffer overflow happens in C LZSS decompression and how to fix it

A high-severity buffer overflow vulnerability was discovered in `user/libprtos/common/lzss.c`, where the LZSS decompression routine failed to validate offset and length values decoded from compressed input before using them as indices into the `text_buf` ring buffer. An attacker supplying crafted compressed data could trigger out-of-bounds reads or writes, potentially leading to memory corruption, information disclosure, or arbitrary code execution. The fix introduces strict bounds validation on