From: Kefu Chai Date: Fri, 26 Jun 2026 00:44:35 +0000 (+0800) Subject: mgr: fix PyObject leak in the remote() pickle calls X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=refs%2Fpull%2F69755%2Fhead;p=ceph.git mgr: fix PyObject leak in the remote() pickle calls dispatch_remote() and ceph_dispatch_remote() invoke pickle.loads/dumps via PyObject_CallMethodObjArgs(pmodule, PyUnicode_FromString("loads"), ...). The method-name string created by PyUnicode_FromString is a new reference that CallMethodObjArgs does not steal, and it was never released, so every cross -module remote() call leaked three PyUnicode objects on the active mgr. Use PyObject_CallMethod(pmodule, "loads", "(O)", arg), which takes the method name as a C string and manages it internally, so there is nothing to leak. The "(O)" format wraps the argument in a one-tuple so it reaches the callee as a single object. A bare "O" passes the argument through directly, and when it is a tuple (as remote()'s *args always is, including the empty tuple from a no-argument call) PyObject_CallMethod unpacks it and calls dumps() with the wrong arguments, e.g. dumps() with none. Fixes: https://tracker.ceph.com/issues/77724 Signed-off-by: Kefu Chai --- diff --git a/src/mgr/ActivePyModule.cc b/src/mgr/ActivePyModule.cc index a01233ac5dc..24e91082278 100644 --- a/src/mgr/ActivePyModule.cc +++ b/src/mgr/ActivePyModule.cc @@ -155,11 +155,7 @@ std::optional> ActivePyModule::dispatch_remote( auto pmodule = py_module->pPickleModule; auto pickled_args_bytes = py_bytes_from_span(pickled_args); - auto args = PyObject_CallMethodObjArgs( - pmodule, - PyUnicode_FromString("loads"), - pickled_args_bytes, - nullptr); + auto args = PyObject_CallMethod(pmodule, "loads", "(O)", pickled_args_bytes); Py_DECREF(pickled_args_bytes); if (args == nullptr) { std::string caller = "ActivePyModule::dispatch_remote "s + method; @@ -169,11 +165,7 @@ std::optional> ActivePyModule::dispatch_remote( } auto pickled_kwargs_bytes = py_bytes_from_span(pickled_kwargs); - auto kwargs = PyObject_CallMethodObjArgs( - pmodule, - PyUnicode_FromString("loads"), - pickled_kwargs_bytes, - nullptr); + auto kwargs = PyObject_CallMethod(pmodule, "loads", "(O)", pickled_kwargs_bytes); Py_DECREF(pickled_kwargs_bytes); if (kwargs == nullptr) { std::string caller = "ActivePyModule::dispatch_remote "s + method; @@ -208,11 +200,7 @@ std::optional> ActivePyModule::dispatch_remote( } dout(20) << "Success calling '" << method << "'" << dendl; - auto pickled_ret = PyObject_CallMethodObjArgs( - pmodule, - PyUnicode_FromString("dumps"), - ret, - nullptr); + auto pickled_ret = PyObject_CallMethod(pmodule, "dumps", "(O)", ret); Py_DECREF(ret); if (pickled_ret == nullptr) { std::string caller = "ActivePyModule::dispatch_remote "s + method; diff --git a/src/mgr/BaseMgrModule.cc b/src/mgr/BaseMgrModule.cc index 8e34159aec7..11459d87938 100644 --- a/src/mgr/BaseMgrModule.cc +++ b/src/mgr/BaseMgrModule.cc @@ -871,11 +871,7 @@ ceph_dispatch_remote(BaseMgrModule *self, PyObject *args) } auto pmodule = self->this_module->py_module->pPickleModule; - auto pickled_args = PyObject_CallMethodObjArgs( - pmodule, - PyUnicode_FromString("dumps"), - remote_args, - nullptr); + auto pickled_args = PyObject_CallMethod(pmodule, "dumps", "(O)", remote_args); if (pickled_args == nullptr) { std::string caller = "ceph_dispatch_remote "s + " " + method; std::string err = handle_pyerror(true, other_module, caller); @@ -885,11 +881,7 @@ ceph_dispatch_remote(BaseMgrModule *self, PyObject *args) } std::span pickled_args_span = py_bytes_as_span(pickled_args); - auto pickled_kwargs = PyObject_CallMethodObjArgs( - pmodule, - PyUnicode_FromString("dumps"), - remote_kwargs, - nullptr); + auto pickled_kwargs = PyObject_CallMethod(pmodule, "dumps", "(O)", remote_kwargs); if (pickled_kwargs == nullptr) { std::string caller = "ceph_dispatch_remote "s + " " + method; std::string err = handle_pyerror(true, other_module, caller); @@ -940,11 +932,7 @@ ceph_dispatch_remote(BaseMgrModule *self, PyObject *args) } auto pickled_ret_bytes = py_bytes_from_vec(*maybe_pickled_ret); - auto ret = PyObject_CallMethodObjArgs( - pmodule, - PyUnicode_FromString("loads"), - pickled_ret_bytes, - nullptr); + auto ret = PyObject_CallMethod(pmodule, "loads", "(O)", pickled_ret_bytes); if (ret == nullptr) { std::string caller = "ceph_dispatch_remote "s + " " + method; std::string err = handle_pyerror(true, other_module, caller);