`Record.get()` with invalid positional argument count segfaults
Calling asyncpg.Record.get() with an invalid number of positional arguments can crash the Python process instead of raising TypeError.
Confirmed crashing calls:
record.get()record.get("a", 2, 3)
Valid and separately handled cases behave as expected:
record.get("a")returns the value.record.get("a", default=2)raisesTypeError: Record.get() takes no keyword arguments.
Affected Component
- File:
asyncpg/protocol/record/recordobj.c - Function:
record_get() - Method exposed as:
asyncpg.Record.get - Observed commit:
db8ecc2a38e16fb0c090aef6f5506547c2831c24
Impact
This is a native crash / process-level denial of service in the CPython extension. It is not a PostgreSQL wire-level remote issue by itself; it requires same-process Python code to call Record.get() with an invalid positional argument count. This can still matter for applications that expose generic object dispatch, plugins, scripting hooks, template helpers, or RPC-style method invocation over returned records.
Root Cause
In record_get(), the invalid positional argument-count branch sets a Python exception but continues execution:
if (nargs == 2) {
key = args[0];
defval = args[1];
} else if (nargs == 1) {
key = args[0];
} else {
PyErr_Format(PyExc_TypeError,
"Record.get() expected 1 or 2 arguments, got %zd",
nargs);
}key is not initialized in that branch. The function then reaches:
res = record_item_by_name((ApgRecordObject *)self, key, &val);As a result, an uninitialized PyObject *key is passed to record_item_by_name(), causing a native crash.
The release build also emits:
asyncpg/protocol/record/recordobj.c:702:11: warning: 'key' may be used uninitialized [-Wmaybe-uninitialized]Steps to Reproduce
Build asyncpg from source:
git submodule update --init --recursive
python setup.py build_ext --inplaceMinimal repro without requiring a PostgreSQL server, using the same internal record helper used by tests/test_record.py:
PYTHONPATH=. python -u - <<'PY'
from asyncpg.protocol.protocol import _create_record as Record
r = Record({"a": 0}, (1,))
print("before")
r.get()
print("after")
PYA three-positional-argument variant also crashes:
PYTHONPATH=. python -u - <<'PY'
from asyncpg.protocol.protocol import _create_record as Record
r = Record({"a": 0}, (1,))
print("before")
r.get("a", 2, 3)
print("after")
PYA public API variant can be reproduced by fetching any row and then calling the invalid method form:
import asyncio
import asyncpg
async def main():
conn = await asyncpg.connect()
try:
row = await conn.fetchrow("select 1 as a")
row.get()
finally:
await conn.close()
asyncio.run(main())Expected Result
Invalid positional argument counts should raise a Python exception, for example:
TypeError: Record.get() expected 1 or 2 arguments, got 0and:
TypeError: Record.get() expected 1 or 2 arguments, got 3Actual Result
On a release build, both invalid calls segfault:
before no args
Segmentation fault (core dumped)before three args
Segmentation fault (core dumped)Local verification exited with code 139 for both r.get() and r.get("a", 2, 3).
With ASAN, the invalid argument-count path produced:
AddressSanitizer:DEADLYSIGNAL
ERROR: AddressSanitizer: SEGV on unknown addressSuggested Fix
Return immediately after setting the argument-count error:
} else {
PyErr_Format(PyExc_TypeError,
"Record.get() expected 1 or 2 arguments, got %zd",
nargs);
return NULL;
}It would also be useful to add regression coverage to tests/test_record.py::test_record_get:
with self.assertRaises(TypeError):
r.get()
with self.assertRaises(TypeError):
r.get("a", 2, 3)Source: MagicStack/asyncpg