Version in base suite: 1.9.1-2 Base version: znc_1.9.1-2 Target version: znc_1.9.1-2+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/z/znc/znc_1.9.1-2.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/z/znc/znc_1.9.1-2+deb13u1.dsc changelog | 10 patches/01-Fix-crash-while-unloading-modules.diff | 497 ++++++++++ patches/02-crypt-Modernize-and-add-test-Fix-OnNumericMessage.diff | 208 ++++ patches/03-swig-functions.diff | 121 ++ patches/series | 3 5 files changed, 839 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpe_jpaxda/znc_1.9.1-2.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpe_jpaxda/znc_1.9.1-2+deb13u1.dsc: no acceptable signature found diff -Nru znc-1.9.1/debian/changelog znc-1.9.1/debian/changelog --- znc-1.9.1/debian/changelog 2024-09-21 10:39:01.000000000 +0000 +++ znc-1.9.1/debian/changelog 2026-09-11 13:00:38.000000000 +0000 @@ -1,3 +1,13 @@ +znc (1.9.1-2+deb13u1) trixie-security; urgency=high + + * Add patch 01-Fix-crash-while-unloading-modules: Use-after-free triggered by + unprivileged users. Fixes CVE-2026-82373. + * Add patch 02-crypt-Modernize-and-add-test-Fix-OnNumericMessage: Null pointer + dereference in crypt module triggered by IRC server. Fixes CVE-2026-82374. + * Add patch 03-swig-functions to update swig generated modules. + + -- Patrick Matthäi Fri, 11 Sep 2026 15:00:38 +0200 + znc (1.9.1-2) unstable; urgency=medium [ Patrick Matthäi ] diff -Nru znc-1.9.1/debian/patches/01-Fix-crash-while-unloading-modules.diff znc-1.9.1/debian/patches/01-Fix-crash-while-unloading-modules.diff --- znc-1.9.1/debian/patches/01-Fix-crash-while-unloading-modules.diff 1970-01-01 00:00:00.000000000 +0000 +++ znc-1.9.1/debian/patches/01-Fix-crash-while-unloading-modules.diff 2026-09-11 13:00:38.000000000 +0000 @@ -0,0 +1,497 @@ +From 4b88c64c6e8df29c73568ae1c8b35d797bfa509a Mon Sep 17 00:00:00 2001 +From: Alexey Sokolov +Date: Sat, 5 Sep 2026 23:09:25 +0100 +Subject: [PATCH 1/3] Fix crash while unloading modules + +The bug allowed unprivileged users to trigger use-after-free while +unloading modules + +Note that the fix requires a slight API change for OnEmbeddedWebRequest: +now the callers (webadmin is the only known caller of it) call it via +DoEmbeddedWebRequest() instead. The modules which implement this hook +need no changes. + +CVE-2026-82373 + +Found by Claude from Anthropic, reported by Ada Logics. +--- + include/znc/Modules.h | 71 ++++++++++++++++++++- + include/znc/main.h | 30 ++------- + modules/modperl/functions.in | 2 +- + modules/modpython/functions.in | 2 +- + modules/webadmin.cpp | 12 ++-- + src/Client.cpp | 29 +++------ + src/Modules.cpp | 112 +++++++++++++++------------------ + 7 files changed, 145 insertions(+), 113 deletions(-) + +diff --git a/include/znc/Modules.h b/include/znc/Modules.h +index ec8b36f8..d8423906 100644 +--- a/include/znc/Modules.h ++++ b/include/znc/Modules.h +@@ -465,6 +465,9 @@ class CModule { + void SetNetwork(CIRCNetwork* pNetwork); + void SetClient(CClient* pClient); + ++ /** True if the module can be safely unloaded. */ ++ bool IsCallStackEmpty() const; ++ + /** This function throws CModule::UNLOAD which causes this module to be unloaded. + */ + void Unload() { throw UNLOAD; } +@@ -535,9 +538,11 @@ class CModule { + * @return The List. + */ + virtual VWebSubPages& GetSubPages() { return m_vSubPages; } ++ private: + /** Using this hook, module can embed web stuff directly to different places. +- * This method is called whenever embededded modules I/O happens. ++ * This method is called whenever embedded modules I/O happens. + * Name of used .tmpl file (if any) is up to caller. ++ * Callers call it via `DoEmbeddedWebRequest()`. + * @param WebSock Socket for web connection, don't do bad things with it. + * @param sPageName Describes the place where web stuff is embedded to. + * @param Tmpl Template. Depending on context, you can do various stuff with it. +@@ -547,6 +552,9 @@ class CModule { + virtual bool OnEmbeddedWebRequest(CWebSock& WebSock, + const CString& sPageName, + CTemplate& Tmpl); ++ public: ++ bool DoEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, ++ CTemplate& Tmpl); + + /** Called just before znc.conf is rehashed */ + virtual void OnPreRehash(); +@@ -1528,6 +1536,9 @@ class CModule { + m_mssRegistry; //!< way to save name/value pairs. Note there is no encryption involved in this + VWebSubPages m_vSubPages; + std::map m_mCommands; ++ int m_iCallStackDepth = 0; ++ ++ friend struct CModCallProtector; + }; + + class CModules : public std::vector, private CCoreTranslationMixin { +@@ -1766,4 +1777,62 @@ class CModules : public std::vector, private CCoreTranslationMixin { + CClient* m_pClient; + }; + ++struct CModCallProtector { ++ explicit CModCallProtector(CModule& pMod) : m_pMod(&pMod) { ++ pMod.m_iCallStackDepth++; ++ } ++ ~CModCallProtector() { m_pMod->m_iCallStackDepth--; } ++ ++ CModule* m_pMod; ++}; ++ ++#ifndef SWIG ++template ++struct CTemporaryModField { ++ CTemporaryModField(CModule& pMod, T* pValue) { ++ m_pMod = &pMod; ++ m_pOldT = (pMod.*Getter)(); ++ if (pValue) { ++ (pMod.*Setter)(pValue); ++ } ++ } ++ ++ ~CTemporaryModField() { (m_pMod->*Setter)(m_pOldT); } ++ ++ CModule* m_pMod; ++ T* m_pOldT; ++}; ++ ++using CTemporaryModClient = ++ CTemporaryModField; ++using CTemporaryModNetwork = ++ CTemporaryModField; ++using CTemporaryModUser = ++ CTemporaryModField; ++ ++// Same as above, but for plural CModules ++template ++struct CTemporaryModsField { ++ CTemporaryModsField(CModules& pMods, T* pValue) { ++ m_pMods = &pMods; ++ m_pOldT = (pMods.*Getter)(); ++ if (pValue) { ++ (pMods.*Setter)(pValue); ++ } ++ } ++ ++ ~CTemporaryModsField() { (m_pMods->*Setter)(m_pOldT); } ++ ++ CModules* m_pMods; ++ T* m_pOldT; ++}; ++ ++using CTemporaryModsClient = ++ CTemporaryModsField; ++using CTemporaryModsNetwork = ++ CTemporaryModsField; ++using CTemporaryModsUser = ++ CTemporaryModsField; ++#endif ++ + #endif // !ZNC_MODULES_H +diff --git a/include/znc/main.h b/include/znc/main.h +index 7d22fd43..28924d2c 100644 +--- a/include/znc/main.h ++++ b/include/znc/main.h +@@ -56,21 +56,12 @@ extern bool ZNC_NO_NEED_TO_DO_ANYTHING_ON_MODULE_CALL_EXITER; + #define _GLOBALMODULECALL(macFUNC, macUSER, macNETWORK, macCLIENT, macEXITER) \ + do { \ + CModules& GMods = CZNC::Get().GetModules(); \ +- CUser* pOldGUser = GMods.GetUser(); \ +- CIRCNetwork* pOldGNetwork = GMods.GetNetwork(); \ +- CClient* pOldGClient = GMods.GetClient(); \ +- GMods.SetUser(macUSER); \ +- GMods.SetNetwork(macNETWORK); \ +- GMods.SetClient(macCLIENT); \ ++ CTemporaryModsUser TempUser(GMods, macUSER); \ ++ CTemporaryModsNetwork TempNetwork(GMods, macNETWORK); \ ++ CTemporaryModsClient TempClient(GMods, macCLIENT); \ + if (GMods.macFUNC) { \ +- GMods.SetUser(pOldGUser); \ +- GMods.SetNetwork(pOldGNetwork); \ +- GMods.SetClient(pOldGClient); \ + *macEXITER = true; \ + } \ +- GMods.SetUser(pOldGUser); \ +- GMods.SetNetwork(pOldGNetwork); \ +- GMods.SetClient(pOldGClient); \ + } while (false) + + #define _USERMODULECALL(macFUNC, macUSER, macNETWORK, macCLIENT, macEXITER) \ +@@ -84,17 +75,11 @@ extern bool ZNC_NO_NEED_TO_DO_ANYTHING_ON_MODULE_CALL_EXITER; + } \ + if (macUSER != nullptr) { \ + CModules& UMods = macUSER->GetModules(); \ +- CIRCNetwork* pOldUNetwork = UMods.GetNetwork(); \ +- CClient* pOldUClient = UMods.GetClient(); \ +- UMods.SetNetwork(macNETWORK); \ +- UMods.SetClient(macCLIENT); \ ++ CTemporaryModsNetwork TempNetwork(UMods, macNETWORK); \ ++ CTemporaryModsClient TempClient(UMods, macCLIENT); \ + if (UMods.macFUNC) { \ +- UMods.SetNetwork(pOldUNetwork); \ +- UMods.SetClient(pOldUClient); \ + *macEXITER = true; \ + } \ +- UMods.SetNetwork(pOldUNetwork); \ +- UMods.SetClient(pOldUClient); \ + } \ + } while (false) + +@@ -109,13 +94,10 @@ extern bool ZNC_NO_NEED_TO_DO_ANYTHING_ON_MODULE_CALL_EXITER; + } \ + if (macNETWORK != nullptr) { \ + CModules& NMods = macNETWORK->GetModules(); \ +- CClient* pOldNClient = NMods.GetClient(); \ +- NMods.SetClient(macCLIENT); \ ++ CTemporaryModsClient TempClient(NMods, macCLIENT); \ + if (NMods.macFUNC) { \ +- NMods.SetClient(pOldNClient); \ + *macEXITER = true; \ + } \ +- NMods.SetClient(pOldNClient); \ + } \ + } while (false) + +diff --git a/modules/modperl/functions.in b/modules/modperl/functions.in +index a44c818a..bfa67119 100644 +--- a/modules/modperl/functions.in ++++ b/modules/modperl/functions.in +@@ -66,7 +66,7 @@ void OnServerCapResult(const CString& sCap, bool bSuccess) + void OnClientAttached() + void OnClientDetached() + EModRet OnTimerAutoJoin(CChan& Channel) +-bool OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) ++bool OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl)=false + EModRet OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet) + EModRet OnDeleteNetwork(CIRCNetwork& Network) + EModRet OnSendToClient(CString& sLine, CClient& Client) +diff --git a/modules/modpython/functions.in b/modules/modpython/functions.in +index 76ed437a..d2914f9d 100644 +--- a/modules/modpython/functions.in ++++ b/modules/modpython/functions.in +@@ -66,7 +66,7 @@ void OnServerCapResult(const CString& sCap, bool bSuccess) + void OnClientAttached() + void OnClientDetached() + EModRet OnTimerAutoJoin(CChan& Channel) +-bool OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) ++bool OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl)=false + EModRet OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet) + EModRet OnDeleteNetwork(CIRCNetwork& Network) + EModRet OnSendToClient(CString& sLine, CClient& Client) +diff --git a/modules/webadmin.cpp b/modules/webadmin.cpp +index 0b724138..ac77517c 100644 +--- a/modules/webadmin.cpp ++++ b/modules/webadmin.cpp +@@ -742,7 +742,7 @@ class CWebAdminMod : public CModule { + CTemplate& mod = Tmpl.AddRow("EmbeddedModuleLoop"); + mod.insert(Tmpl.begin(), Tmpl.end()); + mod["WebadminAction"] = "display"; +- if ((*i)->OnEmbeddedWebRequest(WebSock, "webadmin/channel", ++ if ((*i)->DoEmbeddedWebRequest(WebSock, "webadmin/channel", + mod)) { + mod["Embed"] = WebSock.FindTmpl(*i, "WebadminChan.tmpl"); + mod["ModName"] = (*i)->GetModName(); +@@ -816,7 +816,7 @@ class CWebAdminMod : public CModule { + TmplMod["ChanName"] = pChan->GetName(); + TmplMod["WebadminAction"] = "change"; + FOR_EACH_MODULE(it, pNetwork) { +- (*it)->OnEmbeddedWebRequest(WebSock, "webadmin/channel", TmplMod); ++ (*it)->DoEmbeddedWebRequest(WebSock, "webadmin/channel", TmplMod); + } + + if (!CZNC::Get().WriteConfig()) { +@@ -1027,7 +1027,7 @@ class CWebAdminMod : public CModule { + CTemplate& mod = Tmpl.AddRow("EmbeddedModuleLoop"); + mod.insert(Tmpl.begin(), Tmpl.end()); + mod["WebadminAction"] = "display"; +- if ((*i)->OnEmbeddedWebRequest(WebSock, "webadmin/network", ++ if ((*i)->DoEmbeddedWebRequest(WebSock, "webadmin/network", + mod)) { + mod["Embed"] = WebSock.FindTmpl(*i, "WebadminNetwork.tmpl"); + mod["ModName"] = (*i)->GetModName(); +@@ -1246,7 +1246,7 @@ class CWebAdminMod : public CModule { + TmplMod["Name"] = pNetwork->GetName(); + TmplMod["WebadminAction"] = "change"; + FOR_EACH_MODULE(it, make_pair(pUser, pNetwork)) { +- (*it)->OnEmbeddedWebRequest(WebSock, "webadmin/network", TmplMod); ++ (*it)->DoEmbeddedWebRequest(WebSock, "webadmin/network", TmplMod); + } + + if (!CZNC::Get().WriteConfig()) { +@@ -1670,7 +1670,7 @@ class CWebAdminMod : public CModule { + CTemplate& mod = Tmpl.AddRow("EmbeddedModuleLoop"); + mod.insert(Tmpl.begin(), Tmpl.end()); + mod["WebadminAction"] = "display"; +- if ((*i)->OnEmbeddedWebRequest(WebSock, "webadmin/user", mod)) { ++ if ((*i)->DoEmbeddedWebRequest(WebSock, "webadmin/user", mod)) { + mod["Embed"] = WebSock.FindTmpl(*i, "WebadminUser.tmpl"); + mod["ModName"] = (*i)->GetModName(); + } +@@ -1731,7 +1731,7 @@ class CWebAdminMod : public CModule { + TmplMod["Username"] = sUsername; + TmplMod["WebadminAction"] = "change"; + FOR_EACH_MODULE(it, pUser) { +- (*it)->OnEmbeddedWebRequest(WebSock, "webadmin/user", TmplMod); ++ (*it)->DoEmbeddedWebRequest(WebSock, "webadmin/user", TmplMod); + } + + if (!CZNC::Get().WriteConfig()) { +diff --git a/src/Client.cpp b/src/Client.cpp +index 0f60f9a1..db1ec7a6 100644 +--- a/src/Client.cpp ++++ b/src/Client.cpp +@@ -21,8 +21,8 @@ + #include + #include + +-using std::set; + using std::map; ++using std::set; + using std::vector; + + #define CALLMOD(MOD, CLIENT, USER, NETWORK, FUNC) \ +@@ -30,10 +30,9 @@ using std::vector; + CModule* pModule = nullptr; \ + if (NETWORK && (pModule = (NETWORK)->GetModules().FindModule(MOD))) { \ + try { \ +- CClient* pOldClient = pModule->GetClient(); \ +- pModule->SetClient(CLIENT); \ ++ CModCallProtector Inside(*pModule); \ ++ CTemporaryModClient TempClient(*pModule, CLIENT); \ + pModule->FUNC; \ +- pModule->SetClient(pOldClient); \ + } catch (const CModule::EModException& e) { \ + if (e == CModule::UNLOAD) { \ + (NETWORK)->GetModules().UnloadModule(MOD); \ +@@ -41,13 +40,10 @@ using std::vector; + } \ + } else if ((pModule = (USER)->GetModules().FindModule(MOD))) { \ + try { \ +- CClient* pOldClient = pModule->GetClient(); \ +- CIRCNetwork* pOldNetwork = pModule->GetNetwork(); \ +- pModule->SetClient(CLIENT); \ +- pModule->SetNetwork(NETWORK); \ ++ CModCallProtector Inside(*pModule); \ ++ CTemporaryModClient TempClient(*pModule, CLIENT); \ ++ CTemporaryModNetwork TempNetwork(*pModule, NETWORK); \ + pModule->FUNC; \ +- pModule->SetClient(pOldClient); \ +- pModule->SetNetwork(pOldNetwork); \ + } catch (const CModule::EModException& e) { \ + if (e == CModule::UNLOAD) { \ + (USER)->GetModules().UnloadModule(MOD); \ +@@ -55,16 +51,11 @@ using std::vector; + } \ + } else if ((pModule = CZNC::Get().GetModules().FindModule(MOD))) { \ + try { \ +- CClient* pOldClient = pModule->GetClient(); \ +- CIRCNetwork* pOldNetwork = pModule->GetNetwork(); \ +- CUser* pOldUser = pModule->GetUser(); \ +- pModule->SetClient(CLIENT); \ +- pModule->SetNetwork(NETWORK); \ +- pModule->SetUser(USER); \ ++ CModCallProtector Inside(*pModule); \ ++ CTemporaryModClient TempClient(*pModule, CLIENT); \ ++ CTemporaryModNetwork TempNetwork(*pModule, NETWORK); \ ++ CTemporaryModUser TempUser(*pModule, USER); \ + pModule->FUNC; \ +- pModule->SetClient(pOldClient); \ +- pModule->SetNetwork(pOldNetwork); \ +- pModule->SetUser(pOldUser); \ + } catch (const CModule::EModException& e) { \ + if (e == CModule::UNLOAD) { \ + CZNC::Get().GetModules().UnloadModule(MOD); \ +diff --git a/src/Modules.cpp b/src/Modules.cpp +index d553cbb7..2c4c056f 100644 +--- a/src/Modules.cpp ++++ b/src/Modules.cpp +@@ -36,67 +36,45 @@ bool ZNC_NO_NEED_TO_DO_ANYTHING_ON_MODULE_CALL_EXITER; + #warning "your crap box doesn't define RTLD_LOCAL !?" + #endif + +-#define MODUNLOADCHK(func) \ +- for (CModule * pMod : *this) { \ +- try { \ +- CClient* pOldClient = pMod->GetClient(); \ +- pMod->SetClient(m_pClient); \ +- CUser* pOldUser = nullptr; \ +- if (m_pUser) { \ +- pOldUser = pMod->GetUser(); \ +- pMod->SetUser(m_pUser); \ +- } \ +- CIRCNetwork* pNetwork = nullptr; \ +- if (m_pNetwork) { \ +- pNetwork = pMod->GetNetwork(); \ +- pMod->SetNetwork(m_pNetwork); \ +- } \ +- pMod->func; \ +- if (m_pUser) pMod->SetUser(pOldUser); \ +- if (m_pNetwork) pMod->SetNetwork(pNetwork); \ +- pMod->SetClient(pOldClient); \ +- } catch (const CModule::EModException& e) { \ +- if (e == CModule::UNLOAD) { \ +- UnloadModule(pMod->GetModName()); \ +- } \ +- } \ +- } +- +-#define MODHALTCHK(func) \ +- bool bHaltCore = false; \ +- for (CModule * pMod : *this) { \ +- try { \ +- CModule::EModRet e = CModule::CONTINUE; \ +- CClient* pOldClient = pMod->GetClient(); \ +- pMod->SetClient(m_pClient); \ +- CUser* pOldUser = nullptr; \ +- if (m_pUser) { \ +- pOldUser = pMod->GetUser(); \ +- pMod->SetUser(m_pUser); \ +- } \ +- CIRCNetwork* pNetwork = nullptr; \ +- if (m_pNetwork) { \ +- pNetwork = pMod->GetNetwork(); \ +- pMod->SetNetwork(m_pNetwork); \ +- } \ +- e = pMod->func; \ +- if (m_pUser) pMod->SetUser(pOldUser); \ +- if (m_pNetwork) pMod->SetNetwork(pNetwork); \ +- pMod->SetClient(pOldClient); \ +- if (e == CModule::HALTMODS) { \ +- break; \ +- } else if (e == CModule::HALTCORE) { \ +- bHaltCore = true; \ +- } else if (e == CModule::HALT) { \ +- bHaltCore = true; \ +- break; \ +- } \ +- } catch (const CModule::EModException& e) { \ +- if (e == CModule::UNLOAD) { \ +- UnloadModule(pMod->GetModName()); \ +- } \ +- } \ +- } \ ++#define MODUNLOADCHK(func) \ ++ for (CModule* pMod : *this) { \ ++ try { \ ++ CModCallProtector inside(*pMod); \ ++ CTemporaryModClient TempClient(*pMod, m_pClient); \ ++ CTemporaryModUser TempUser(*pMod, m_pUser); \ ++ CTemporaryModNetwork TempNetwork(*pMod, m_pNetwork); \ ++ pMod->func; \ ++ } catch (const CModule::EModException& e) { \ ++ if (e == CModule::UNLOAD) { \ ++ UnloadModule(pMod->GetModName()); \ ++ } \ ++ } \ ++ } ++ ++#define MODHALTCHK(func) \ ++ bool bHaltCore = false; \ ++ for (CModule* pMod : *this) { \ ++ try { \ ++ CModCallProtector inside(*pMod); \ ++ CModule::EModRet e = CModule::CONTINUE; \ ++ CTemporaryModClient TempClient(*pMod, m_pClient); \ ++ CTemporaryModUser TempUser(*pMod, m_pUser); \ ++ CTemporaryModNetwork TempNetwork(*pMod, m_pNetwork); \ ++ e = pMod->func; \ ++ if (e == CModule::HALTMODS) { \ ++ break; \ ++ } else if (e == CModule::HALTCORE) { \ ++ bHaltCore = true; \ ++ } else if (e == CModule::HALT) { \ ++ bHaltCore = true; \ ++ break; \ ++ } \ ++ } catch (const CModule::EModException& e) { \ ++ if (e == CModule::UNLOAD) { \ ++ UnloadModule(pMod->GetModName()); \ ++ } \ ++ } \ ++ } \ + return bHaltCore; + + /////////////////// Timer /////////////////// +@@ -205,6 +183,8 @@ void CModule::SetUser(CUser* pUser) { m_pUser = pUser; } + void CModule::SetNetwork(CIRCNetwork* pNetwork) { m_pNetwork = pNetwork; } + void CModule::SetClient(CClient* pClient) { m_pClient = pClient; } + ++bool CModule::IsCallStackEmpty() const { return m_iCallStackDepth == 0; } ++ + CString CModule::ExpandString(const CString& sStr) const { + CString sRet; + return ExpandString(sStr, sRet); +@@ -622,6 +602,11 @@ bool CModule::OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, + CTemplate& Tmpl) { + return false; + } ++bool CModule::DoEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, ++ CTemplate& Tmpl) { ++ CModCallProtector inside(*this); ++ return OnEmbeddedWebRequest(WebSock, sPageName, Tmpl); ++} + // !Webmods + + bool CModule::OnLoad(const CString& sArgs, CString& sMessage) { +@@ -1953,6 +1938,11 @@ bool CModules::UnloadModule(const CString& sModule, CString& sRetMsg) { + return false; + } + ++ if (!pModule->IsCallStackEmpty()) { ++ sRetMsg = t_f("Module [{1}] is being called, cannot unload.")(sMod); ++ return false; ++ } ++ + bool bSuccess; + bool bHandled = false; + _GLOBALMODULECALL(OnModuleUnloading(pModule, bSuccess, sRetMsg), +-- +2.54.0 + + diff -Nru znc-1.9.1/debian/patches/02-crypt-Modernize-and-add-test-Fix-OnNumericMessage.diff znc-1.9.1/debian/patches/02-crypt-Modernize-and-add-test-Fix-OnNumericMessage.diff --- znc-1.9.1/debian/patches/02-crypt-Modernize-and-add-test-Fix-OnNumericMessage.diff 1970-01-01 00:00:00.000000000 +0000 +++ znc-1.9.1/debian/patches/02-crypt-Modernize-and-add-test-Fix-OnNumericMessage.diff 2026-09-11 13:00:38.000000000 +0000 @@ -0,0 +1,208 @@ +From 3e0ea6a3b8cbeef3b30b203d836458dea61aaf35 Mon Sep 17 00:00:00 2001 +From: RealKindOne +Date: Sun, 23 Aug 2026 15:26:03 -0400 +Subject: [PATCH 2/3] crypt: Modernize and add test. Fix OnNumericMessage 332 + issue. + +It was possible to trigger null pointer dereference during +OnNumericMessage + +CVE-2026-82374 + +diff -Naur znc-1.9.1.orig/modules/crypt.cpp znc-1.9.1/modules/crypt.cpp +--- znc-1.9.1.orig/modules/crypt.cpp 2024-07-03 12:20:11.000000000 +0200 ++++ znc-1.9.1/modules/crypt.cpp 2026-09-08 14:56:26.009602059 +0200 +@@ -248,13 +248,19 @@ + FilterOutgoing(Message); + return CONTINUE; + } ++ EModRet OnPrivTextMessage(CTextMessage& Message) override { ++ CNick& Nick = Message.GetNick(); ++ CString sMessage = Message.GetText(); ++ CString sHostmask = Nick.GetHostMask(); + +- EModRet OnPrivMsg(CNick& Nick, CString& sMessage) override { + FilterIncoming(Nick.GetNick(), Nick, sMessage); ++ Message.SetText(sMessage); + return CONTINUE; + } + +- EModRet OnPrivNotice(CNick& Nick, CString& sMessage) override { ++ EModRet OnPrivNoticeMessage(CNoticeMessage& Message) override { ++ CNick& Nick = Message.GetNick(); ++ CString sMessage = Message.GetText(); + CString sCommand = sMessage.Token(0); + CString sOtherPubKey = sMessage.Token(1); + +@@ -305,33 +311,42 @@ + } + + FilterIncoming(Nick.GetNick(), Nick, sMessage); ++ Message.SetText(sMessage); + return CONTINUE; + } + +- EModRet OnPrivAction(CNick& Nick, CString& sMessage) override { +- FilterIncoming(Nick.GetNick(), Nick, sMessage); ++ EModRet OnPrivActionMessage(CActionMessage& Message) override { ++ CString sText = Message.GetText(); ++ FilterIncoming(Message.GetNick().GetNick(), Message.GetNick(), sText); ++ Message.SetText(sText); + return CONTINUE; + } + +- EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) override { +- FilterIncoming(Channel.GetName(), Nick, sMessage); ++ EModRet OnChanTextMessage(CTextMessage& Message) override { ++ CString sText = Message.GetText(); ++ FilterIncoming(Message.GetTarget(), Message.GetNick(), sText); ++ Message.SetText(sText); + return CONTINUE; + } + +- EModRet OnChanNotice(CNick& Nick, CChan& Channel, +- CString& sMessage) override { +- FilterIncoming(Channel.GetName(), Nick, sMessage); ++ EModRet OnChanNoticeMessage(CNoticeMessage& Message) override { ++ CString sText = Message.GetText(); ++ FilterIncoming(Message.GetTarget(), Message.GetNick(), sText); ++ Message.SetText(sText); + return CONTINUE; + } + +- EModRet OnChanAction(CNick& Nick, CChan& Channel, +- CString& sMessage) override { +- FilterIncoming(Channel.GetName(), Nick, sMessage); ++ EModRet OnChanActionMessage(CActionMessage& Message) override { ++ CString sText = Message.GetText(); ++ FilterIncoming(Message.GetTarget(), Message.GetNick(), sText); ++ Message.SetText(sText); + return CONTINUE; + } + +- EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sMessage) override { +- FilterIncoming(Channel.GetName(), Nick, sMessage); ++ EModRet OnTopicMessage(CTopicMessage& Message) override { ++ CString sText = Message.GetText(); ++ FilterIncoming(Message.GetTarget(), Message.GetNick(), sText); ++ Message.SetText(sText); + return CONTINUE; + } + +@@ -342,10 +357,16 @@ + + CChan* pChan = GetNetwork()->FindChan(Message.GetParam(1)); + if (pChan) { +- CNick* Nick = pChan->FindNick(Message.GetParam(0)); ++ CNick* pNick = pChan->FindNick(Message.GetParam(0)); + CString sTopic = Message.GetParam(2); + +- FilterIncoming(pChan->GetName(), *Nick, sTopic); ++ if (pNick) { ++ FilterIncoming(pChan->GetName(), *pNick, sTopic); ++ } else { ++ // 332 can arrive before ZNC has this nick in the nicklist. ++ CNick TempNick(Message.GetParam(0)); ++ FilterIncoming(pChan->GetName(), TempNick, sTopic); ++ } + Message.SetParam(2, sTopic); + } + +diff -Naur znc-1.9.1.orig/test/integration/tests/modules.cpp znc-1.9.1/test/integration/tests/modules.cpp +--- znc-1.9.1.orig/test/integration/tests/modules.cpp 2024-07-03 12:20:11.000000000 +0200 ++++ znc-1.9.1/test/integration/tests/modules.cpp 2026-09-08 14:56:26.009602059 +0200 +@@ -130,7 +130,11 @@ + ":*spaces!watch@znc.in PRIVMSG nick : SOMETHING word1 word2 SOMETHING"); + } + +-TEST_F(ZNCTest, ModuleCrypt) { ++TEST_F(ZNCTest, CryptModule) { ++#ifndef HAVE_LIBSSL ++ GTEST_SKIP() << "SSL is disabled"; ++#endif ++ + QFile conf(m_dir.path() + "/configs/znc.conf"); + ASSERT_TRUE(conf.open(QIODevice::Append | QIODevice::Text)); + QTextStream(&conf) << "ServerThrottle = 1\n"; +@@ -176,6 +180,8 @@ + QByteArray key2(""); + client2.ReadUntilAndGet("\002user\017: ", key2); + ASSERT_EQ(key1.mid(9), key2.mid(8)); ++ ++ // OnPrivTextMessage + client1.Write("CAP REQ :echo-message"); + client1.Write("PRIVMSG .nick2 :Hello"); + QByteArray secretmsg; +@@ -183,6 +189,72 @@ + ircd2.Write(":user!user@user/test " + secretmsg); + client2.ReadUntil("Hello"); + client1.ReadUntil(secretmsg); // by echo-message ++ ++ client1.Write("PRIVMSG *crypt :SetNickPrefix ."); ++ client1.ReadUntil("Setting Nick Prefix to ."); ++ client2.Write("PRIVMSG *crypt :SetNickPrefix ."); ++ client2.ReadUntil("Setting Nick Prefix to ."); ++ ++ // OnPrivNoticeMessage ++ client1.Write("NOTICE .nick2 :secret notice"); ++ QByteArray noticeMsg; ++ ircd1.ReadUntilAndGet("NOTICE nick2 :+OK ", noticeMsg); ++ ircd2.Write(":user!user@user/test " + noticeMsg); ++ QByteArray noticeLine; ++ client2.ReadUntilAndGet("NOTICE nick2 :", noticeLine); ++ QByteArray noticemessage = noticeLine.mid(noticeLine.lastIndexOf(':') + 1); ++ EXPECT_EQ(noticemessage, "secret notice"); ++ ++ // OnUserActionMessage ++ client1.Write("PRIVMSG .nick2 :\001ACTION waves\001"); ++ QByteArray actionMsg; ++ ircd1.ReadUntilAndGet("PRIVMSG nick2 :\001ACTION +OK ", actionMsg); ++ ircd2.Write(":user!user@user/test " + actionMsg); ++ QByteArray actionLine; ++ client2.ReadUntilAndGet("PRIVMSG nick2 :", actionLine); ++ QByteArray actionMessage = actionLine.mid(actionLine.lastIndexOf(':') + 1); ++ EXPECT_EQ(actionMessage, "\001ACTION waves\001"); ++ ++ client1.Write("JOIN #test"); ++ client2.Write("JOIN #test"); ++ ++ QByteArray chanKey = "channelKey123"; ++ client1.Write(QByteArray("PRIVMSG *crypt :SetKey #test ") + chanKey); ++ client1.ReadUntil(QByteArray("Set encryption key for [#test] to [") + ++ chanKey + "]"); ++ client2.Write(QByteArray("PRIVMSG *crypt :SetKey #test ") + chanKey); ++ client2.ReadUntil(QByteArray("Set encryption key for [#test] to [") + ++ chanKey + "]"); ++ ++ // OnChanTextMessage ++ client1.Write("PRIVMSG #test :channel secret"); ++ QByteArray chanMsg; ++ ircd1.ReadUntilAndGet("PRIVMSG #test :+OK ", chanMsg); ++ ircd2.Write(":user!user@user/test " + chanMsg); ++ client2.ReadUntil("channel secret"); ++ ++ // OnChanNoticeMessage ++ client1.Write("NOTICE #test :chan notice"); ++ QByteArray chanNoticeMsg; ++ ircd1.ReadUntilAndGet("NOTICE #test :+OK ", chanNoticeMsg); ++ ircd2.Write(":user!user@user/test " + chanNoticeMsg); ++ client2.ReadUntil("chan notice"); ++ ++ // OnChanActionMessage ++ client1.Write("PRIVMSG #test :\001ACTION dances\001"); ++ QByteArray chanActionMsg; ++ ircd1.ReadUntilAndGet("PRIVMSG #test :\001ACTION +OK ", chanActionMsg); ++ ircd2.Write(":user!user@user/test " + chanActionMsg); ++ client2.ReadUntil("dances"); ++ ++ // OnTopicMessage / OnNumericMessage ++ client1.Write("TOPIC #test :new chan topic"); ++ QByteArray chanTopicEncrypted; ++ ircd1.ReadUntilAndGet("TOPIC #test :+OK ", chanTopicEncrypted); ++ chanTopicEncrypted = "+OK " + chanTopicEncrypted.mid( ++ chanTopicEncrypted.lastIndexOf(' ') + 1); ++ ircd2.Write(":ircd2 332 nick2 #test :" + chanTopicEncrypted); ++ client2.ReadUntil("new chan topic"); + } + + TEST_F(ZNCTest, AutoAttachModule) { diff -Nru znc-1.9.1/debian/patches/03-swig-functions.diff znc-1.9.1/debian/patches/03-swig-functions.diff --- znc-1.9.1/debian/patches/03-swig-functions.diff 1970-01-01 00:00:00.000000000 +0000 +++ znc-1.9.1/debian/patches/03-swig-functions.diff 2026-09-11 13:00:38.000000000 +0000 @@ -0,0 +1,121 @@ +Description: Build modperl/modpython against the patched hook tables + The pregenerated files shipped in modules/mod{perl,python}/generated.tar.gz + predate the fix for CVE-2026-82373 (01-Fix-crash-while-unloading-modules), + which changes codegen.pl/functions.in and makes + CModule::OnEmbeddedWebRequest() private. Without SWIG the stale + pyfunctions.cpp/perlfunctions.cpp and *_biglib.cpp are used and the build + fails with "OnEmbeddedWebRequest ... is private within this context". + . + This patch: + - always generates pyfunctions.cpp/perlfunctions.cpp with codegen.pl + (Perl only, no SWIG needed); only the SWIG output is still taken from + generated.tar.gz. With the unpatched codegen.pl/functions.in the result + is byte-identical to the tarball copy. + - makes the single CModule_OnEmbeddedWebRequest wrapper in the pregenerated + *_biglib.cpp return false instead of calling the now private method. That + is what CModule::OnEmbeddedWebRequest() itself returns, and it keeps + scripts from re-entering the hook without the new call-stack protection. +Author: Patrick Matthäi +Forwarded: not-needed +Last-Update: 2026-09-11 +--- +--- znc-1.9.1.orig/modules/modperl/CMakeLists.txt ++++ znc-1.9.1/modules/modperl/CMakeLists.txt +@@ -30,16 +30,19 @@ if(APPLE) + "${CMAKE_SHARED_MODULE_CREATE_CXX_FLAGS} -undefined dynamic_lookup") + endif() + +-if(SWIG_FOUND) +- add_custom_command( +- OUTPUT perlfunctions.cpp +- COMMAND "${PERL_EXECUTABLE}" +- "${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl" +- "${CMAKE_CURRENT_SOURCE_DIR}/functions.in" +- "perlfunctions.cpp" +- VERBATIM +- DEPENDS codegen.pl functions.in) ++# Always generate perlfunctions.cpp with codegen.pl, even without SWIG: ++# it only needs Perl, and the copy in generated.tar.gz is stale once ++# codegen.pl and functions.in are patched. ++add_custom_command( ++ OUTPUT perlfunctions.cpp ++ COMMAND "${PERL_EXECUTABLE}" ++ "${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl" ++ "${CMAKE_CURRENT_SOURCE_DIR}/functions.in" ++ "perlfunctions.cpp" ++ VERBATIM ++ DEPENDS codegen.pl functions.in) + ++if(SWIG_FOUND) + add_custom_command( + OUTPUT swigperlrun.h + COMMAND "${SWIG_EXECUTABLE}" -perl -c++ -shadow -external-runtime +@@ -60,10 +63,17 @@ if(SWIG_FOUND) + IMPLICIT_DEPENDS CXX "${CMAKE_CURRENT_SOURCE_DIR}/modperl.i" + VERBATIM) + else() ++ # The pregenerated SWIG wrapper predates the CVE-2026-82373 fix, which made ++ # CModule::OnEmbeddedWebRequest() private; make the CModule wrapper ++ # return false, which is what the base implementation does. + add_custom_command( +- OUTPUT swigperlrun.h ZNC.pm modperl_biglib.cpp perlfunctions.cpp ++ OUTPUT swigperlrun.h ZNC.pm modperl_biglib.cpp + COMMAND "${CMAKE_COMMAND}" -E tar xz + "${CMAKE_CURRENT_SOURCE_DIR}/generated.tar.gz" ++ swigperlrun.h ZNC.pm modperl_biglib.cpp ++ COMMAND "${PERL_EXECUTABLE}" -0777 -pi -e ++ "s/(_wrap_CModule_OnEmbeddedWebRequest[^A-Za-z0-9_].*?)result = [(]bool[)][(]arg1[)]->OnEmbeddedWebRequest[(][^;]*;/$1result = false;/s or die qq(CModule_OnEmbeddedWebRequest wrapper not found)" ++ modperl_biglib.cpp + VERBATIM) + endif() + add_custom_target(modperl_functions DEPENDS "perlfunctions.cpp") +--- znc-1.9.1.orig/modules/modpython/CMakeLists.txt ++++ znc-1.9.1/modules/modpython/CMakeLists.txt +@@ -28,16 +28,19 @@ if(APPLE) + "${CMAKE_SHARED_MODULE_CREATE_CXX_FLAGS} -undefined dynamic_lookup") + endif() + +-if(SWIG_FOUND) +- add_custom_command( +- OUTPUT "pyfunctions.cpp" +- COMMAND "${PERL_EXECUTABLE}" +- "${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl" +- "${CMAKE_CURRENT_SOURCE_DIR}/functions.in" +- "pyfunctions.cpp" +- VERBATIM +- DEPENDS codegen.pl functions.in) ++# Always generate pyfunctions.cpp with codegen.pl, even without SWIG: ++# it only needs Perl, and the copy in generated.tar.gz is stale once ++# codegen.pl and functions.in are patched. ++add_custom_command( ++ OUTPUT "pyfunctions.cpp" ++ COMMAND "${PERL_EXECUTABLE}" ++ "${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl" ++ "${CMAKE_CURRENT_SOURCE_DIR}/functions.in" ++ "pyfunctions.cpp" ++ VERBATIM ++ DEPENDS codegen.pl functions.in) + ++if(SWIG_FOUND) + add_custom_command( + OUTPUT "swigpyrun.h" + COMMAND "${SWIG_EXECUTABLE}" -python -py3 -c++ -shadow -external-runtime +@@ -57,10 +60,17 @@ if(SWIG_FOUND) + IMPLICIT_DEPENDS CXX "${CMAKE_CURRENT_SOURCE_DIR}/modpython.i" + VERBATIM) + else() ++ # The pregenerated SWIG wrapper predates the CVE-2026-82373 fix, which made ++ # CModule::OnEmbeddedWebRequest() private; make the CModule wrapper ++ # return false, which is what the base implementation does. + add_custom_command( +- OUTPUT swigpyrun.h znc_core.py modpython_biglib.cpp pyfunctions.cpp ++ OUTPUT swigpyrun.h znc_core.py modpython_biglib.cpp + COMMAND "${CMAKE_COMMAND}" -E tar xz + "${CMAKE_CURRENT_SOURCE_DIR}/generated.tar.gz" ++ swigpyrun.h znc_core.py modpython_biglib.cpp ++ COMMAND "${PERL_EXECUTABLE}" -0777 -pi -e ++ "s/(_wrap_CModule_OnEmbeddedWebRequest[^A-Za-z0-9_].*?)result = [(]bool[)][(]arg1[)]->OnEmbeddedWebRequest[(][^;]*;/$1result = false;/s or die qq(CModule_OnEmbeddedWebRequest wrapper not found)" ++ modpython_biglib.cpp + VERBATIM) + endif() + add_custom_target(modpython_functions DEPENDS "pyfunctions.cpp") diff -Nru znc-1.9.1/debian/patches/series znc-1.9.1/debian/patches/series --- znc-1.9.1/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 +++ znc-1.9.1/debian/patches/series 2026-09-11 13:00:38.000000000 +0000 @@ -0,0 +1,3 @@ +01-Fix-crash-while-unloading-modules.diff +02-crypt-Modernize-and-add-test-Fix-OnNumericMessage.diff +03-swig-functions.diff