Skip to content

Fix PyText_AS_UTF8/PyUnicode_AsUTF8 NULL dereference vulnerabilities#256

Open
dynapx wants to merge 1 commit into
python-cffi:mainfrom
dynapx:main
Open

Fix PyText_AS_UTF8/PyUnicode_AsUTF8 NULL dereference vulnerabilities#256
dynapx wants to merge 1 commit into
python-cffi:mainfrom
dynapx:main

Conversation

@dynapx

@dynapx dynapx commented Jul 14, 2026

Copy link
Copy Markdown

Description

Hi maintainers,

During a robustness audit of Python C‑API contracts using a static analysis tool, we identified a systematic pattern of unchecked PyText_AS_UTF8 / PyUnicode_AsUTF8 return values across multiple files in the _cffi_backend C extension.

These functions can return NULL when:

  1. The input string contains invalid surrogate characters, triggering UnicodeEncodeError
  2. Memory allocation fails (OOM), triggering MemoryError

When the return value is NULL and passed directly to PyErr_Format or PyUnicode_FromFormat with a %s format specifier, it causes strlen(NULL)Segmentation Fault, crashing the interpreter.

This PR adds NULL checks before passing UTF-8 strings to any string-consuming API, ensuring errors are properly propagated rather than crashing.

1. Unchecked PyText_AS_UTF8 / PyUnicode_AsUTF8 return → NULL dereference

Location: Multiple functions across src/c/_cffi_backend.c, src/c/cglob.c, src/c/ffi_obj.c

Issue:
PyText_AS_UTF8 (macro for PyUnicode_AsUTF8) is called and its return value is passed directly to PyErr_Format, PyUnicode_FromFormat, parse_c_type, or detect_custom_layout without checking for NULL. If the string contains invalid surrogate characters (e.g., \uD800) or memory allocation fails, the function returns NULL and sets an exception. Passing NULL to a %s format specifier causes strlen(NULL)Segmentation Fault, killing the interpreter.

Fix:
Check the return value of PyText_AS_UTF8 / PyUnicode_AsUTF8 before using it. If NULL, propagate the already-set exception by returning NULL or -1.

Affected functions:

File Function
src/c/cglob.c fetch_global_var_addr
src/c/ffi_obj.c _ffi_type
src/c/_cffi_backend.c cdata_repr, _cdata_repr2, _add_field, b_complete_struct_or_union_lock_held (6 locations), b_do_dlopen

Example fix (cglob.c):

- PyErr_Format(FFIError, "global variable '%s' is at address NULL",
-              PyUnicode_AsUTF8(gs->gs_name));
+ const char *name_utf8 = PyUnicode_AsUTF8(gs->gs_name);
+ if (name_utf8 == NULL) {
+     return NULL;
+ }
+ PyErr_Format(FFIError, "global variable '%s' is at address NULL",
+              name_utf8);

Same fix applied to all PyErr_Format calls with unchecked %s parameters:

/* Before: */
PyErr_Format(PyExc_TypeError, "field '%s.%s' ...", ct->ct_name, PyUnicode_AsUTF8(fname));

/* After: */
const char *fname_utf8 = PyUnicode_AsUTF8(fname);
if (fname_utf8 == NULL) {
    goto finally;  /* or return NULL */
}
PyErr_Format(PyExc_TypeError, "field '%s.%s' ...", ct->ct_name, fname_utf8);

Impact & Rationale

  • Availability: A crash in cffi can take down the entire Python process with no recoverable error. cffi is a foundational library used by thousands of projects including cryptography, bcrypt, gevent, and PyPy.

  • Reachability: The vulnerability is reachable when:

    • User-provided strings containing invalid surrogate characters are passed to ffi.cast(), ffi.typeof(), or struct field definitions
    • Memory pressure causes PyUnicode_AsUTF8 to fail with MemoryError
  • Debuggability: In the %s format specifier case, the original exception is lost and replaced by a crash, making debugging extremely difficult.

Note: All changes are minimal and backward‑compatible – they only affect error paths, leaving the happy path unchanged.


Thank you for maintaining cffi!

Add NULL checks before passing UTF-8 strings to PyErr_Format,
PyUnicode_FromFormat, parse_c_type, and detect_custom_layout.

These APIs can return NULL when the input string contains invalid
surrogate characters or when memory allocation fails. Passing NULL
to a %%s format specifier causes strlen(NULL) -> Segmentation Fault.

Affected functions:
- fetch_global_var_addr (cglob.c)
- _ffi_type (ffi_obj.c)
- cdata_repr, _cdata_repr2, _add_field,
  b_complete_struct_or_union_lock_held, b_do_dlopen (_cffi_backend.c)
@mattip

mattip commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Add some tests that prove your claims, without the bogus "memory allocation fails" since if memory allocation fails for these small messages the interpreter will soon crash anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants