pygit2_refdb_backend_write() passes owned objects to Py_BuildValue() with the N format (which steals the reference), then decrefs the same objects again in its cleanup block — a double DECREF that can lead to a use-after-free.
File: src/refdb_backend.c
Function: pygit2_refdb_backend_write
Relevant code:
PyObject*args=NULL, *ref=NULL, *who=NULL, *old=NULL;
if ((ref=wrap_reference((git_reference*)_ref, NULL)) ==NULL)
goto euser;
if ((who=build_signature(NULL, _who, "utf-8")) ==NULL)
goto euser;
if ((old=git_oid_to_python(_old)) ==NULL)
goto euser;
if ((args=Py_BuildValue("(NNNsNs)", ref,
force ? Py_True : Py_False,
who, message, old, old_target)) ==NULL)
goto euser;
PyObject_CallObject(be->write, args);
err=git_error_for_exc();
out:
Py_DECREF(ref);
Py_DECREF(who);
Py_DECREF(old);
Py_DECREF(args);
returnerr;
euser:
err=GIT_EUSER;
goto out;Three problems:
Double DECREF. The N format steals a reference, so on success ref, who, and old are owned by the tuple args. The out: block then calls Py_DECREF(ref), Py_DECREF(who), Py_DECREF(old)andPy_DECREF(args) (which decrefs each element again during tuple deallocation). Each of the three objects is released one time too many.
Stolen singleton.force ? Py_True : Py_False is a borrowed singleton reference. Passing it with N (without Py_INCREF) transfers a reference the function never owned, underflowing the singleton's refcount.
Non-X cleanup on the error path. All four locals are initialized to NULL, but the euser path jumps to out:, which uses Py_DECREF rather than Py_XDECREF. If an early allocation fails (e.g. wrap_reference returns NULL), the cleanup dereferences a NULL pointer and crashes instead of returning GIT_EUSER.
pygit2_refdb_backend_write()passes owned objects toPy_BuildValue()with theNformat (which steals the reference), then decrefs the same objects again in its cleanup block — a double DECREF that can lead to a use-after-free.File:
src/refdb_backend.cFunction:
pygit2_refdb_backend_writeRelevant code:
Three problems:
Double DECREF. The
Nformat steals a reference, so on successref,who, andoldare owned by the tupleargs. Theout:block then callsPy_DECREF(ref),Py_DECREF(who),Py_DECREF(old)andPy_DECREF(args)(which decrefs each element again during tuple deallocation). Each of the three objects is released one time too many.Stolen singleton.
force ? Py_True : Py_Falseis a borrowed singleton reference. Passing it withN(withoutPy_INCREF) transfers a reference the function never owned, underflowing the singleton's refcount.Non-
Xcleanup on the error path. All four locals are initialized toNULL, but theeuserpath jumps toout:, which usesPy_DECREFrather thanPy_XDECREF. If an early allocation fails (e.g.wrap_referencereturnsNULL), the cleanup dereferences aNULLpointer and crashes instead of returningGIT_EUSER.