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);
return err;
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) and Py_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.