Skip to content

Inverted signature check (plus double DECREF) in pygit2_refdb_backend_rename #1474

Description

@K-ANOY

pygit2_refdb_backend_rename() has an inverted success check on build_signature(): it returns an error precisely when the signature is built successfully, and falls through with a NULL signature when it fails. The same function also double-decrefs who and leaks the callback result on one path.

File: src/refdb_backend.c

Function: pygit2_refdb_backend_rename

Relevant code:

PyObject *args, *who;

if ((who = build_signature(NULL, _who, "utf-8")) != NULL)
    return GIT_EUSER;
if ((args = Py_BuildValue("(ssNNs)", old_name, new_name,
        force ? Py_True : Py_False, who, message)) == NULL) {
    Py_DECREF(who);
    return GIT_EUSER;
}
Reference *ref = (Reference *)PyObject_CallObject(be->rename, args);
Py_DECREF(who);
Py_DECREF(args);

if ((err = git_error_for_exc()) != 0)
    return err;

if (!PyObject_IsInstance((PyObject *)ref, (PyObject *)&ReferenceType)) {
    PyErr_SetString(PyExc_TypeError, "Expected object of type pygit2.Reference");
    return GIT_EUSER;
}

git_reference_dup(out, ref->reference);
Py_DECREF(ref);
return 0;

Problems:

  1. Inverted check. if ((who = build_signature(...)) != NULL) return GIT_EUSER;
    returns an error whenever build_signature() succeeds (and leaks who on
    that return). When it fails and returns NULL, execution continues and
    passes who == NULL into Py_BuildValue. Compare pygit2_refdb_backend_write(),
    which correctly uses == NULL. As written, rename cannot work.

  2. Double DECREF of who. who is passed with N (stolen into args), then
    Py_DECREF(who) is called again at line 259 after PyObject_CallObject, in
    addition to Py_DECREF(args).

  3. Stolen singleton. force ? Py_True : Py_False is passed with N, stealing
    a reference to a borrowed singleton.

  4. Leaked callback result. If be->rename returns a non-Reference object,
    the function returns GIT_EUSER without decrefing ref.

Suggested fix:

who = build_signature(NULL, _who, "utf-8");
if (who == NULL)
    return GIT_EUSER;

args = Py_BuildValue("(ssOOs)", old_name, new_name,
                     force ? Py_True : Py_False, who, message);
Py_DECREF(who);
if (args == NULL)
    return GIT_EUSER;

Reference *ref = (Reference *)PyObject_CallObject(be->rename, args);
Py_DECREF(args);

if ((err = git_error_for_exc()) != 0)
    return err;

if (!PyObject_IsInstance((PyObject *)ref, (PyObject *)&ReferenceType)) {
    PyErr_SetString(PyExc_TypeError, "Expected object of type pygit2.Reference");
    Py_DECREF(ref);
    return GIT_EUSER;
}

git_reference_dup(out, ref->reference);
Py_DECREF(ref);
return 0;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions