modpython lets Python register capability callbacks on the native corecaps module through CModule. If a callback raises, the error handler casts that CCoreCaps object to CPyModule. In a local ZNC 1.11.x test, UBSan reported invalid downcasts on both server and authenticated-client CAP paths; a separate uninstrumented server-path run ended with SIGSEGV.
Affected code
In modpython.i:234–247 exposes the method, and Modules.cpp:1233–1236 stores the receiving module via pCap->SetModule(this). On a callback exception, modpython.cpp:499 (server) or :515 (client) calls ((CPyModule*)GetModule())->GetPyExceptionStr() even when the stored owner is CCoreCaps.
Reproduction
Use a Python-enabled ZNC test instance with corecaps, modpython, and this operator-written global module loaded in that order. A local fake IRC server listens on one Unix socket, which ZNC connects to; an authenticated test client connects to ZNC's separate Unix listener. Use a disposable datadir and credentials generated with the matching ZNC binary's --makepass.
ownerprobe.py (the code used for the server and client runs):
importzncclassownerprobe(znc.Module):description="Local capability owner test"module_types=[znc.CModInfo.GlobalModule]defOnLoad(self,args,ret):defserver_change(network,state):raiseRuntimeError("server callback")defclient_change(client,state):raiseRuntimeError("client callback")modules=znc.CZNC.Get().GetModules()try:count=len(modules)exceptException:count=modules.size()foriinrange(count):owner=modules[i]ifstr(owner.GetModName())=="corecaps":owner.AddServerDependentCapability("testcapx",server_change,client_change)returnTruereturnTrue# no target: skip registration
Server path: After ZNC sends CAP LS 302, the fake server sends 001/376, then :ircd.test CAP <nick> NEW testcapx=value. ZNC requests CAP REQ :testcapx; the server answers :ircd.test CAP <nick> ACK :testcapx. The raising callback reaches OnServerChangedSupport.
Client path: An authenticated local client sends CAP LS 302, NICK, USER, CAP END, then CAP REQ :-testcapx. The raising callback reaches OnClientChangedSupport.
Observed results
Both sanitized ZNC children exited 1. Distinct report exceptions:
modules/modpython.cpp:499:27: runtime error: downcast ... does not point to an object of type 'CPyModule'
note: object is of type 'CCoreCaps'
#0 CPyCapability::OnServerChangedSupport(CIRCNetwork*, bool)
#3 CIRCSock::OnCapabilityMessage(CMessage&)
modules/modpython.cpp:515:27: runtime error: downcast ... does not point to an object of type 'CPyModule'
note: object is of type 'CCoreCaps'
#0 CPyCapability::OnClientChangedSupport(CClient*, bool)
#3 CClient::HandleCap(CMessage const&)
An earlier direct uninstrumented Release server-path run recorded ZNC child exit -11 (SIGSEGV). A separate GDB run stopped at:
The runs used a local 1.11.x-git-unknown ZNC build; its complete upstream revision is unknown. The sanitized build used x86_64 Clang 18.1.3, Python 3.12, SWIG 4.2.0, and -O1 -g -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address,undefined -fno-sanitize-recover=bounds,object-size,vptr -fPIE -Wno-everything.
## Summary
`modpython` lets Python register capability callbacks on the native `corecaps` module through `CModule`. If a callback raises, the error handler casts that `CCoreCaps` object to `CPyModule`. In a local ZNC 1.11.x test, UBSan reported invalid downcasts on both server and authenticated-client CAP paths; a separate uninstrumented server-path run ended with SIGSEGV.
## Affected code
In [`modpython.i:234–247`](https://github.com/znc/znc/blob/bfba882ccc5a1672897941e3dcdf9bdcb451b425/modules/modpython/modpython.i#L234-L247) exposes the method, and [`Modules.cpp:1233–1236`](https://github.com/znc/znc/blob/bfba882ccc5a1672897941e3dcdf9bdcb451b425/src/Modules.cpp#L1233-L1236) stores the receiving module via `pCap->SetModule(this)`. On a callback exception, [`modpython.cpp:499`](https://github.com/znc/znc/blob/bfba882ccc5a1672897941e3dcdf9bdcb451b425/modules/modpython.cpp#L492-L502) (server) or [`:515`](https://github.com/znc/znc/blob/bfba882ccc5a1672897941e3dcdf9bdcb451b425/modules/modpython.cpp#L508-L518) (client) calls `((CPyModule*)GetModule())->GetPyExceptionStr()` even when the stored owner is `CCoreCaps`.
## Reproduction
Use a Python-enabled ZNC test instance with `corecaps`, `modpython`, and this operator-written global module loaded in that order. A local fake IRC server listens on one Unix socket, which ZNC connects to; an authenticated test client connects to ZNC's separate Unix listener. Use a disposable datadir and credentials generated with the matching ZNC binary's `--makepass`.
`ownerprobe.py` (the code used for the server and client runs):
```python
import znc
class ownerprobe(znc.Module):
description = "Local capability owner test"
module_types = [znc.CModInfo.GlobalModule]
def OnLoad(self, args, ret):
def server_change(network, state):
raise RuntimeError("server callback")
def client_change(client, state):
raise RuntimeError("client callback")
modules = znc.CZNC.Get().GetModules()
try:
count = len(modules)
except Exception:
count = modules.size()
for i in range(count):
owner = modules[i]
if str(owner.GetModName()) == "corecaps":
owner.AddServerDependentCapability(
"testcapx", server_change, client_change)
return True
return True # no target: skip registration
```
- **Server path:** After ZNC sends `CAP LS 302`, the fake server sends `001`/`376`, then `:ircd.test CAP <nick> NEW testcapx=value`. ZNC requests `CAP REQ :testcapx`; the server answers `:ircd.test CAP <nick> ACK :testcapx`. The raising callback reaches `OnServerChangedSupport`.
- **Client path:** An authenticated local client sends `CAP LS 302`, `NICK`, `USER`, `CAP END`, then `CAP REQ :-testcapx`. The raising callback reaches `OnClientChangedSupport`.
## Observed results
Both sanitized ZNC children exited **1**. Distinct report exceptions:
```text
modules/modpython.cpp:499:27: runtime error: downcast ... does not point to an object of type 'CPyModule'
note: object is of type 'CCoreCaps'
#0 CPyCapability::OnServerChangedSupport(CIRCNetwork*, bool)
#3 CIRCSock::OnCapabilityMessage(CMessage&)
modules/modpython.cpp:515:27: runtime error: downcast ... does not point to an object of type 'CPyModule'
note: object is of type 'CCoreCaps'
#0 CPyCapability::OnClientChangedSupport(CClient*, bool)
#3 CClient::HandleCap(CMessage const&)
```
An earlier direct uninstrumented Release server-path run recorded ZNC child exit **-11** (SIGSEGV). A separate GDB run stopped at:
```text
#0 CModPython::GetPyExceptionStr()
#1 CPyCapability::OnServerChangedSupport(CIRCNetwork*, bool)
#2 CModule::InternalServerDependentCapsOnServerCapResult(CString const&, bool)
#3 CModules::OnServerCapResult(CString const&, bool)
#4 CIRCSock::OnCapabilityMessage(CMessage&)
#5 CIRCSock::ReadLine(CString const&)
```
## Environment and scope
The runs used a local `1.11.x-git-unknown` ZNC build; its complete upstream revision is unknown. The sanitized build used x86_64 Clang 18.1.3, Python 3.12, SWIG 4.2.0, and `-O1 -g -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address,undefined -fno-sanitize-recover=bounds,object-size,vptr -fPIE -Wno-everything`.
<!-- github-sync-id:5975864981 -->
Yes, modpython doesn't prevent the module author from shooting themselves in foot.
ZNC API gives you access to other modules. What's the point of calling AddServerDependentCapability not on "self", but on some other module?
Yes, modpython doesn't prevent the module author from shooting themselves in foot.
ZNC API gives you access to other modules. What's the point of calling AddServerDependentCapability not on "self", but on some other module?
<!-- github-comment-id:6336635203 -->
Btw, if you do something like that from C++ module, and attach callback to some other module instead of "this", it can also cause crash in certain cases.
Btw, if you do something like that from C++ module, and attach callback to some other module instead of "this", it can also cause crash in certain cases.
<!-- github-comment-id:6350898788 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
modpythonlets Python register capability callbacks on the nativecorecapsmodule throughCModule. If a callback raises, the error handler casts thatCCoreCapsobject toCPyModule. In a local ZNC 1.11.x test, UBSan reported invalid downcasts on both server and authenticated-client CAP paths; a separate uninstrumented server-path run ended with SIGSEGV.Affected code
In
modpython.i:234–247exposes the method, andModules.cpp:1233–1236stores the receiving module viapCap->SetModule(this). On a callback exception,modpython.cpp:499(server) or:515(client) calls((CPyModule*)GetModule())->GetPyExceptionStr()even when the stored owner isCCoreCaps.Reproduction
Use a Python-enabled ZNC test instance with
corecaps,modpython, and this operator-written global module loaded in that order. A local fake IRC server listens on one Unix socket, which ZNC connects to; an authenticated test client connects to ZNC's separate Unix listener. Use a disposable datadir and credentials generated with the matching ZNC binary's--makepass.ownerprobe.py(the code used for the server and client runs):CAP LS 302, the fake server sends001/376, then:ircd.test CAP <nick> NEW testcapx=value. ZNC requestsCAP REQ :testcapx; the server answers:ircd.test CAP <nick> ACK :testcapx. The raising callback reachesOnServerChangedSupport.CAP LS 302,NICK,USER,CAP END, thenCAP REQ :-testcapx. The raising callback reachesOnClientChangedSupport.Observed results
Both sanitized ZNC children exited 1. Distinct report exceptions:
An earlier direct uninstrumented Release server-path run recorded ZNC child exit -11 (SIGSEGV). A separate GDB run stopped at:
Environment and scope
The runs used a local
1.11.x-git-unknownZNC build; its complete upstream revision is unknown. The sanitized build used x86_64 Clang 18.1.3, Python 3.12, SWIG 4.2.0, and-O1 -g -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address,undefined -fno-sanitize-recover=bounds,object-size,vptr -fPIE -Wno-everything.Yes, modpython doesn't prevent the module author from shooting themselves in foot.
ZNC API gives you access to other modules. What's the point of calling AddServerDependentCapability not on "self", but on some other module?
Btw, if you do something like that from C++ module, and attach callback to some other module instead of "this", it can also cause crash in certain cases.