CVE-2019-9917: Improper Input Validation (CWE-20)
value: ZNC before 1.7.3-rc1 allows an existing remote user to cause a Denial of Service (crash) via invalid encoding.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 6.5
Published: 2019-03-27T06:29Z
Repository: https://github.com/znc/znc
Language: C++
Files changed: modules/controlpanel.cpp, src/IRCNetwork.cpp, src/User.cpp, src/znc.cpp, test/integration/tests/scripting.cpp
Commit date: 2019-03-22 00:22:00 +0000
Commit message: Don't crash if user specified invalid encoding.

This is CVE-2019-9917

Diff: diff --git a/modules/controlpanel.cpp b/modules/controlpanel.cpp
index 139c2aefa..109f8c6b0 100644
--- a/modules/controlpanel.cpp
+++ b/modules/controlpanel.cpp
@@ -493,11 +493,11 @@ class CAdminMod : public CModule {
         }
 #endif
 #ifdef HAVE_ICU
         else if (sVar == "clientencoding") {
             pUser->SetClientEncoding(sValue);
-            PutModule("ClientEncoding = " + sValue);
+            PutModule("ClientEncoding = " + pUser->GetClientEncoding());
         }
 #endif
         else
             PutModule(t_s("Error: Unknown variable"));
     }
diff --git a/src/IRCNetwork.cpp b/src/IRCNetwork.cpp
index 0284dc53e..0e1d6e2a3 100644
--- a/src/IRCNetwork.cpp
+++ b/src/IRCNetwork.cpp
@@ -1480,13 +1480,13 @@ void CIRCNetwork::SetBindHost(const CString& s) {
         m_sBindHost = s;
     }
 }
 
 void CIRCNetwork::SetEncoding(const CString& s) {
-    m_sEncoding = s;
+    m_sEncoding = CZNC::Get().FixupEncoding(s);
     if (GetIRCSock()) {
-        GetIRCSock()->SetEncoding(s);
+        GetIRCSock()->SetEncoding(m_sEncoding);
     }
 }
 
 void CIRCNetwork::SetQuitMsg(const CString& s) {
     if (m_pUser->GetQuitMsg().Equals(s)) {
diff --git a/src/User.cpp b/src/User.cpp
index 3fd532a7c..c44cf6070 100644
--- a/src/User.cpp
+++ b/src/User.cpp
@@ -1251,13 +1251,13 @@ void CUser::SetMultiClients(bool b) { m_bMultiClients = b; }
 void CUser::SetDenyLoadMod(bool b) { m_bDenyLoadMod = b; }
 void CUser::SetAdmin(bool b) { m_bAdmin = b; }
 void CUser::SetDenySetBindHost(bool b) { m_bDenySetBindHost = b; }
 void CUser::SetDefaultChanModes(const CString& s) { m_sDefaultChanModes = s; }
 void CUser::SetClientEncoding(const CString& s) {
-    m_sClientEncoding = s;
+    m_sClientEncoding = CZNC::Get().FixupEncoding(s);
     for (CClient* pClient : GetAllClients()) {
-        pClient->SetEncoding(s);
+        pClient->SetEncoding(m_sClientEncoding);
     }
 }
 void CUser::SetQuitMsg(const CString& s) { m_sQuitMsg = s; }
 void CUser::SetAutoClearChanBuffer(bool b) {
     for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
diff --git a/src/znc.cpp b/src/znc.cpp
index 4e7216ee1..3f4dd2e07 100644
--- a/src/znc.cpp
+++ b/src/znc.cpp
@@ -2090,22 +2090,40 @@ void CZNC::ResumeConnectQueue() {
 
 void CZNC::ForceEncoding() {
     m_uiForceEncoding++;
 #ifdef HAVE_ICU
     for (Csock* pSock : GetManager()) {
-        if (pSock->GetEncoding().empty()) {
-            pSock->SetEncoding("UTF-8");
-        }
+        pSock->SetEncoding(FixupEncoding(pSock->GetEncoding()));
     }
 #endif
 }
 void CZNC::UnforceEncoding() { m_uiForceEncoding--; }
 bool CZNC::IsForcingEncoding() const { return m_uiForceEncoding; }
 CString CZNC::FixupEncoding(const CString& sEncoding) const {
-    if (sEncoding.empty() && m_uiForceEncoding) {
+    if (!m_uiForceEncoding) {
+        return sEncoding;
+    }
+    if (sEncoding.empty()) {
+        return "UTF-8";
+    }
+    const char* sRealEncoding = sEncoding.c_str();
+    if (sEncoding[0] == '*' || sEncoding[0] == '^') {
+        sRealEncoding++;
+    }
+    if (!*sRealEncoding) {
         return "UTF-8";
     }
+#ifdef HAVE_ICU
+    UErrorCode e = U_ZERO_ERROR;
+    UConverter* cnv = ucnv_open(sRealEncoding, &e);
+    if (cnv) {
+        ucnv_close(cnv);
+    }
+    if (U_FAILURE(e)) {
+        return "UTF-8";
+    }
+#endif
     return sEncoding;
 }
 
 void CZNC::AddNetworkToQueue(CIRCNetwork* pNetwork) {
     // Make sure we are not already in the queue
diff --git a/test/integration/tests/scripting.cpp b/test/integration/tests/scripting.cpp
index 9dd68d8fa..8f809f50c 100644
--- a/test/integration/tests/scripting.cpp
+++ b/test/integration/tests/scripting.cpp
@@ -53,10 +53,17 @@ TEST_F(ZNCTest, Modpython) {
     client.ReadUntil("nick :'user'");
     ircd.Write(":server 001 nick :Hello");
     ircd.Write(":n!u@h PRIVMSG nick :Hi\xF0, github issue #1229");
     // "replacement character"
     client.ReadUntil("Hi\xEF\xBF\xBD, github issue");
+
+    // Non-existing encoding
+    client.Write("PRIVMSG *controlpanel :Set ClientEncoding $me Western");
+    client.Write("JOIN #a\342");
+    client.ReadUntil(
+        ":*controlpanel!znc@znc.in PRIVMSG nick :ClientEncoding = UTF-8");
+    ircd.ReadUntil("JOIN #a\xEF\xBF\xBD");
 }
 
 TEST_F(ZNCTest, ModpythonSocket) {
     if (QProcessEnvironment::systemEnvironment().value(
             "DISABLED_ZNC_PERL_PYTHON_TEST") == "1") {

Vulnerable code:             PutModule("ClientEncoding = " + sValue);
    m_sEncoding = s;
        GetIRCSock()->SetEncoding(s);
    m_sClientEncoding = s;
        pClient->SetEncoding(s);
        if (pSock->GetEncoding().empty()) {
            pSock->SetEncoding("UTF-8");
        }
    if (sEncoding.empty() && m_uiForceEncoding) {
Fixed code:             PutModule("ClientEncoding = " + pUser->GetClientEncoding());
    m_sEncoding = CZNC::Get().FixupEncoding(s);
        GetIRCSock()->SetEncoding(m_sEncoding);
    m_sClientEncoding = CZNC::Get().FixupEncoding(s);
        pClient->SetEncoding(m_sClientEncoding);
        pSock->SetEncoding(FixupEncoding(pSock->GetEncoding()));
    if (!m_uiForceEncoding) {
        return sEncoding;
    }
    if (sEncoding.empty()) {
        return "UTF-8";
    }
    const char* sRealEncoding = sEncoding.c_str();
    if (sEncoding[0] == '*' || sEncoding[0] == '^') {
        sRealEncoding++;
    }
    if (!*sRealEncoding) {
#ifdef HAVE_ICU
    UErrorCode e = U_ZERO_ERROR;
    UConverter* cnv = ucnv_open(sRealEncoding, &e);
    if (cnv) {
        ucnv_close(cnv);
    }
    if (U_FAILURE(e)) {
        return "UTF-8";
    }
#endif

    // Non-existing encoding
    client.Write("PRIVMSG *controlpanel :Set ClientEncoding $me Western");
    client.Write("JOIN #a\342");
    client.ReadUntil(
        ":*controlpanel!znc@znc.in PRIVMSG nick :ClientEncoding = UTF-8");
    ircd.ReadUntil("JOIN #a\xEF\xBF\xBD");