GHSA-cqjp-jf4r-h5q9HighCVSS 7.5

Thumbor convolution filter allows divide-by-zero in C extension leading to remote DoS

Published
July 31, 2026
Last Modified
July 31, 2026

🔗 CVE IDs covered (1)

📋 Description

Summary

Thumbor's filters:convolution(<matrix>, <columns>, <should_normalize>) filter passes the user-controlled <columns> value to a C extension (thumbor/ext/filters/_convolution.c) where it is used as a divisor (for % and /) without validating columns > 0. When columns=0, the C code triggers undefined behavior; on x86_64 this reliably results in a fatal divide-by-zero trap (SIGFPE) and crashes the Thumbor process (confirmed on Linux x86_64 and macOS Intel x86_64), causing a remote denial of service.

Details

Root cause

The Python filter accepts columns=0, and the native C extension uses columns_count as a divisor without validating it.

  1. Python filter entry point allows columns=0 (thumbor/filters/convolution.py):
@filter_method(
    r"(?:[-]?[\d]+\.?[\d]*[;])*(?:[-]?[\d]+\.?[\d]*)",
    BaseFilter.PositiveNumber,  # accepts 0
    BaseFilter.Boolean,
)
async def convolution(self, matrix, columns, should_normalize=True):
    ...
    imgdata = _convolution.apply(..., matrix, columns, should_normalize)

BaseFilter.PositiveNumber matches "0" (thumbor/filters/__init__.py):

class BaseFilter:
    PositiveNumber = {"regex": r"[\d]+", "parse": int}  # matches "0"
    PositiveNonZeroNumber = {"regex": r"[\d]*[1-9][\d]*", "parse": int}
  1. C extension divides/modulos by columns_count without a zero check (thumbor/ext/filters/_convolution.c):
kernel_size = PyTuple_Size(kernel_tuple);
if ((kernel_size % columns_count != 0) ||
    (kernel_size % 2 == 0) ||
    ((kernel_size / columns_count) % 2) == 0) {
    // TODO: error, not a valid kernel
    return NULL;
}

PoC

Test environment

  • Linux x86_64

Preconditions

  • The convolution filter is enabled (it is enabled by default via BUILTIN_FILTERS).
  • Either:
    • /unsafe/ URLs are allowed (ALLOW_UNSAFE_URL=True), OR
    • /unsafe/ is disabled, and the attacker has a valid signed URL (i.e., the attacker is an authorized user/partner, or can obtain signed URLs from a trusted signing service).

Example request (signed URL)

http://<host>:<port>/<url-sign>/400x400/filters:convolution(1;2;1;2;4;2;1;2;1,0,true)/example.jpg

Example request (/unsafe/)

http://<host>:<port>/unsafe/400x400/filters:convolution(1;2;1;2;4;2;1;2;1,0,true)/example.jpg

Impact

  • Remote Denial of Service via process crash (SIGFPE) on x86_64 (confirmed on Linux x86_64 and macOS Intel x86_64).
  • Exploitability depends on deployment:
    • If /unsafe/ is enabled: unauthenticated remote DoS.
    • If /unsafe/ is disabled: the attacker needs a valid signed URL.(i.e., the attacker is an authorized user/partner, or can obtain signed URLs from a trusted signing service)

Suggested remediation

  • In the C extension (thumbor/ext/filters/_convolution.c):
    • Reject columns_count <= 0 before any % or /.
  • In the Python filter (thumbor/filters/convolution.py):
    • Require columns to be non-zero (e.g., use BaseFilter.PositiveNonZeroNumber).

🎯 Affected products1

  • pip/thumbor:<= 7.7.7

🔗 References (4)