diff -Nru quassel-0.10.1/debian/changelog quassel-0.10.1/debian/changelog --- quassel-0.10.1/debian/changelog 2014-11-17 14:32:36.000000000 +0100 +++ quassel-0.10.1/debian/changelog 2015-05-01 19:07:04.000000000 +0200 @@ -1,3 +1,17 @@ +quassel (0.10.1-0ubuntu1.2) utopic-security; urgency=medium + + * SECURITY UPDATE: stack consumption vulnerability in message splitting code + - debian/patches/CVE-2015-2778.patch: original patch from Michael Marley, + backported by Steinar H. Gunderson + - CVE-2015-2778 and CVE-2015-2779 + * SECURITY UPDATE: SQL injection vulnerability in PostgreSQL backend + - debian/patches/CVE-2015-3427.patch: upstream patch + - CVE-2015-3427 + - original issue was CVE-2013-4422 which had an incomplete fix + - LP: #1448911 + + -- Felix Geyer Fri, 01 May 2015 18:46:52 +0200 + quassel (0.10.1-0ubuntu1.1) utopic-security; urgency=medium * SECURITY UPDATE: out-of-bounds read in ECB Blowfish decryption diff -Nru quassel-0.10.1/debian/patches/CVE-2015-2778.patch quassel-0.10.1/debian/patches/CVE-2015-2778.patch --- quassel-0.10.1/debian/patches/CVE-2015-2778.patch 1970-01-01 01:00:00.000000000 +0100 +++ quassel-0.10.1/debian/patches/CVE-2015-2778.patch 2015-05-01 18:54:41.000000000 +0200 @@ -0,0 +1,432 @@ +>From b5e38970ffd55e2dd9f706ce75af9a8d7730b1b8 Mon Sep 17 00:00:00 2001 +From: Michael Marley +Date: Sat, 21 Feb 2015 07:33:57 -0500 +Subject: [PATCH] Improve the message-splitting algorithm for PRIVMSG and CTCP + +This introduces a new message splitting algorithm based on +QTextBoundaryFinder. It works by first starting with the entire +message to be sent, encoding it, and checking to see if it is over +the maximum message length. If it is, it uses QTBF to find the +word boundary most immediately preceding the maximum length. If no +suitable boundary can be found, it falls back to searching for +grapheme boundaries. It repeats this process until the entire +message has been sent. + +Unlike what it replaces, the new splitting code is not recursive +and cannot cause stack overflows. Additionally, if it is unable +to split a string, it will give up gracefully and not crash the +core or cause a thread to run away. + +This patch fixes two bugs. The first is garbage characters caused +by accidentally splitting the string in the middle of a multibyte +character. Since the new code splits at a character level instead +of a byte level, this will no longer be an issue. The second is +the core crash caused by sending an overlength CTCP query ("/me") +containing only multibyte characters. This bug was caused by the +old CTCP splitter using the byte index from lastParamOverrun() as +a character index for a QString. +--- + src/core/corebasichandler.cpp | 3 ++ + src/core/corebasichandler.h | 1 + + src/core/corenetwork.cpp | 86 +++++++++++++++++++++++++++++++++++++++ + src/core/corenetwork.h | 5 +++ + src/core/coreuserinputhandler.cpp | 72 +++++++++++--------------------- + src/core/coreuserinputhandler.h | 2 +- + src/core/ctcpparser.cpp | 26 +++--------- + 7 files changed, 124 insertions(+), 71 deletions(-) + +Index: quassel-0.10.0/src/core/corebasichandler.cpp +=================================================================== +--- quassel-0.10.0.orig/src/core/corebasichandler.cpp ++++ quassel-0.10.0/src/core/corebasichandler.cpp +@@ -33,6 +33,9 @@ CoreBasicHandler::CoreBasicHandler(CoreN + connect(this, SIGNAL(putCmd(QString, const QList &, const QByteArray &)), + network(), SLOT(putCmd(QString, const QList &, const QByteArray &))); + ++ connect(this, SIGNAL(putCmd(QString, const QList > &, const QByteArray &)), ++ network(), SLOT(putCmd(QString, const QList > &, const QByteArray &))); ++ + connect(this, SIGNAL(putRawLine(const QByteArray &)), + network(), SLOT(putRawLine(const QByteArray &))); + } +Index: quassel-0.10.0/src/core/corebasichandler.h +=================================================================== +--- quassel-0.10.0.orig/src/core/corebasichandler.h ++++ quassel-0.10.0/src/core/corebasichandler.h +@@ -55,6 +55,7 @@ public: + signals: + void displayMsg(Message::Type, BufferInfo::Type, const QString &target, const QString &text, const QString &sender = "", Message::Flags flags = Message::None); + void putCmd(const QString &cmd, const QList ¶ms, const QByteArray &prefix = QByteArray()); ++ void putCmd(const QString &cmd, const QList > ¶ms, const QByteArray &prefix = QByteArray()); + void putRawLine(const QByteArray &msg); + + protected: +Index: quassel-0.10.0/src/core/corenetwork.cpp +=================================================================== +--- quassel-0.10.0.orig/src/core/corenetwork.cpp ++++ quassel-0.10.0/src/core/corenetwork.cpp +@@ -283,6 +283,16 @@ void CoreNetwork::putCmd(const QString & + } + + ++void CoreNetwork::putCmd(const QString &cmd, const QList > ¶ms, const QByteArray &prefix) ++{ ++ QListIterator > i(params); ++ while (i.hasNext()) { ++ QList msg = i.next(); ++ putCmd(cmd, msg, prefix); ++ } ++} ++ ++ + void CoreNetwork::setChannelJoined(const QString &channel) + { + _autoWhoQueue.prepend(channel.toLower()); // prepend so this new chan is the first to be checked +@@ -979,3 +989,82 @@ void CoreNetwork::requestSetNetworkInfo( + } + } + } ++ ++ ++CoreNetwork::SplitGenerator::~SplitGenerator() {} ++ ++ ++QList > CoreNetwork::splitMessage(const QString &cmd, const QString &message, SplitGenerator *cmdGenerator) ++{ ++ QString wrkMsg(message); ++ QList > msgsToSend; ++ ++ // do while (wrkMsg.size() > 0) ++ do { ++ // First, check to see if the whole message can be sent at once. The ++ // cmdGenerator function is passed in by the caller and is used to encode ++ // and encrypt (if applicable) the message, since different callers might ++ // want to use different encoding or encode different values. ++ int splitPos = wrkMsg.size(); ++ QList initialSplitMsgEnc = (*cmdGenerator)(wrkMsg); ++ int initialOverrun = userInputHandler()->lastParamOverrun(cmd, initialSplitMsgEnc); ++ ++ if (initialOverrun) { ++ // If the message was too long to be sent, first try splitting it along ++ // word boundaries with QTextBoundaryFinder. ++ QString splitMsg(wrkMsg); ++ QTextBoundaryFinder qtbf(QTextBoundaryFinder::Word, splitMsg); ++ qtbf.setPosition(initialSplitMsgEnc[1].size() - initialOverrun); ++ QList splitMsgEnc; ++ int overrun = initialOverrun; ++ ++ while (overrun) { ++ splitPos = qtbf.toPreviousBoundary(); ++ ++ // splitPos==-1 means the QTBF couldn't find a split point at all and ++ // splitPos==0 means the QTBF could only find a boundary at the beginning of ++ // the string. Neither one of these works for us. ++ if (splitPos > 0) { ++ // If a split point could be found, split the message there, calculate the ++ // overrun, and continue with the loop. ++ splitMsg = splitMsg.left(splitPos); ++ splitMsgEnc = (*cmdGenerator)(splitMsg); ++ overrun = userInputHandler()->lastParamOverrun(cmd, splitMsgEnc); ++ } ++ else { ++ // If a split point could not be found (the beginning of the message ++ // is reached without finding a split point short enough to send) and we ++ // are still in Word mode, switch to Grapheme mode. We also need to restore ++ // the full wrkMsg to splitMsg, since splitMsg may have been cut down during ++ // the previous attempt to find a split point. ++ if (qtbf.type() == QTextBoundaryFinder::Word) { ++ splitMsg = wrkMsg; ++ splitPos = splitMsg.size(); ++ QTextBoundaryFinder graphemeQtbf(QTextBoundaryFinder::Grapheme, splitMsg); ++ graphemeQtbf.setPosition(initialSplitMsgEnc[1].size() - initialOverrun); ++ qtbf = graphemeQtbf; ++ } ++ else { ++ // If the QTBF fails to find a split point in Grapheme mode, we give up. ++ // This should never happen, but it should be handled anyway. ++ qWarning() << "Unexpected failure to split message!"; ++ return msgsToSend; ++ } ++ } ++ } ++ ++ // Once a message of sendable length has been found, remove it from the wrkMsg and ++ // add it to the list of messages to be sent. ++ wrkMsg.remove(0, splitPos); ++ msgsToSend.append(splitMsgEnc); ++ } ++ else{ ++ // If the entire remaining message is short enough to be sent all at once, remove ++ // it from the wrkMsg and add it to the list of messages to be sent. ++ wrkMsg.remove(0, splitPos); ++ msgsToSend.append(initialSplitMsgEnc); ++ } ++ } while (wrkMsg.size() > 0); ++ ++ return msgsToSend; ++} +Index: quassel-0.10.0/src/core/corenetwork.h +=================================================================== +--- quassel-0.10.0.orig/src/core/corenetwork.h ++++ quassel-0.10.0/src/core/corenetwork.h +@@ -93,6 +93,13 @@ public: + inline quint16 localPort() const { return socket.localPort(); } + inline quint16 peerPort() const { return socket.peerPort(); } + ++ // Interface equivalent to a std::function(QString &)>. ++ struct SplitGenerator { ++ virtual ~SplitGenerator() = 0; ++ virtual QList operator() (QString &) = 0; ++ }; ++ QList > splitMessage(const QString &cmd, const QString &message, SplitGenerator *cmdGenerator); ++ + public slots: + virtual void setMyNick(const QString &mynick); + +@@ -112,6 +119,7 @@ public slots: + void userInput(BufferInfo bufferInfo, QString msg); + void putRawLine(QByteArray input); + void putCmd(const QString &cmd, const QList ¶ms, const QByteArray &prefix = QByteArray()); ++ void putCmd(const QString &cmd, const QList > ¶ms, const QByteArray &prefix = QByteArray()); + + void setChannelJoined(const QString &channel); + void setChannelParted(const QString &channel); +Index: quassel-0.10.0/src/core/coreuserinputhandler.cpp +=================================================================== +--- quassel-0.10.0.orig/src/core/coreuserinputhandler.cpp ++++ quassel-0.10.0/src/core/coreuserinputhandler.cpp +@@ -468,7 +468,6 @@ void CoreUserInputHandler::handleMode(co + emit putCmd("MODE", serverEncode(params)); + } + +- + // TODO: show privmsgs + void CoreUserInputHandler::handleMsg(const BufferInfo &bufferInfo, const QString &msg) + { +@@ -477,16 +476,15 @@ void CoreUserInputHandler::handleMsg(con + return; + + QString target = msg.section(' ', 0, 0); +- QByteArray encMsg = userEncode(target, msg.section(' ', 1)); ++ QString msgSection = msg.section(' ', 1); + + #ifdef HAVE_QCA2 +- putPrivmsg(serverEncode(target), encMsg, network()->cipher(target)); ++ putPrivmsg(target, msgSection, /*encodeForChannel=*/false, network()->cipher(target)); + #else +- putPrivmsg(serverEncode(target), encMsg); ++ putPrivmsg(target, msgSection, /*encodeForChannel=*/false); + #endif + } + +- + void CoreUserInputHandler::handleNick(const BufferInfo &bufferInfo, const QString &msg) + { + Q_UNUSED(bufferInfo) +@@ -588,11 +586,10 @@ void CoreUserInputHandler::handleSay(con + if (bufferInfo.bufferName().isEmpty() || !bufferInfo.acceptsRegularMessages()) + return; // server buffer + +- QByteArray encMsg = channelEncode(bufferInfo.bufferName(), msg); + #ifdef HAVE_QCA2 +- putPrivmsg(serverEncode(bufferInfo.bufferName()), encMsg, network()->cipher(bufferInfo.bufferName())); ++ putPrivmsg(bufferInfo.bufferName(), msg, /*encodeForChannel=*/true, network()->cipher(bufferInfo.bufferName())); + #else +- putPrivmsg(serverEncode(bufferInfo.bufferName()), encMsg); ++ putPrivmsg(bufferInfo.bufferName(), msg, /*encodeForChannel=*/true); + #endif + emit displayMsg(Message::Plain, bufferInfo.type(), bufferInfo.bufferName(), msg, network()->myNick(), Message::Self); + } +@@ -757,56 +754,37 @@ void CoreUserInputHandler::defaultHandle + } + + +-void CoreUserInputHandler::putPrivmsg(const QByteArray &target, const QByteArray &message, Cipher *cipher) +-{ +- // Encrypted messages need special care. There's no clear relation between cleartext and encrypted message length, +- // so we can't just compute the maxSplitPos. Instead, we need to loop through the splitpoints until the crypted +- // version is short enough... +- // TODO: check out how the various possible encryption methods behave length-wise and make +- // this clean by predicting the length of the crypted msg. +- // For example, blowfish-ebc seems to create 8-char chunks. +- +- static const char *cmd = "PRIVMSG"; +- static const char *splitter = " .,-!?"; +- +- int maxSplitPos = message.count(); +- int splitPos = maxSplitPos; +- forever { +- QByteArray crypted = message.left(splitPos); +- bool isEncrypted = false; ++CoreUserInputHandler::MsgSplitGenerator::MsgSplitGenerator(CoreUserInputHandler *parent, const QString &target, bool encodeForChannel, Cipher *cipher) ++ : _parent(parent), _target(target), _encodeForChannel(encodeForChannel), _cipher(cipher) {} ++ ++ ++CoreUserInputHandler::MsgSplitGenerator::~MsgSplitGenerator() {} ++ ++ ++QList CoreUserInputHandler::CoreUserInputHandler::MsgSplitGenerator::operator() (QString &splitMsg) { ++ QByteArray splitMsgEnc; ++ if (_encodeForChannel) { ++ splitMsgEnc = _parent->channelEncode(_target, splitMsg); ++ } else { ++ splitMsgEnc = _parent->userEncode(_target, splitMsg); ++ } ++ + #ifdef HAVE_QCA2 +- if (cipher && !cipher->key().isEmpty() && !message.isEmpty()) { +- isEncrypted = cipher->encrypt(crypted); +- } ++ if (_cipher && !_cipher->key().isEmpty() && !splitMsg.isEmpty()) { ++ _cipher->encrypt(splitMsgEnc); ++ } + #endif +- int overrun = lastParamOverrun(cmd, QList() << target << crypted); +- if (overrun) { +- // In case this is not an encrypted msg, we can just cut off at the end +- if (!isEncrypted) +- maxSplitPos = message.count() - overrun; +- +- splitPos = -1; +- for (const char *splitChar = splitter; *splitChar != 0; splitChar++) { +- splitPos = qMax(splitPos, message.lastIndexOf(*splitChar, maxSplitPos) + 1); // keep split char on old line +- } +- if (splitPos <= 0 || splitPos > maxSplitPos) +- splitPos = maxSplitPos; +- +- maxSplitPos = splitPos - 1; +- if (maxSplitPos <= 0) { // this should never happen, but who knows... +- qWarning() << tr("[Error] Could not encrypt your message: %1").arg(message.data()); +- return; +- } +- continue; // we never come back here for !encrypted! +- } +- +- // now we have found a valid splitpos (or didn't need to split to begin with) +- putCmd(cmd, QList() << target << crypted); +- if (splitPos < message.count()) +- putPrivmsg(target, message.mid(splitPos), cipher); ++ QByteArray targetEnc = _parent->serverEncode(_target); ++ return QList() << targetEnc << splitMsgEnc; ++} + +- return; +- } ++ ++void CoreUserInputHandler::putPrivmsg(const QString &target, const QString &message, bool encodeForChannel, Cipher *cipher) ++{ ++ QString cmd("PRIVMSG"); ++ ++ MsgSplitGenerator cmdGenerator(this, target, encodeForChannel, cipher); ++ putCmd(cmd, network()->splitMessage(cmd, message, &cmdGenerator)); + } + + +Index: quassel-0.10.0/src/core/coreuserinputhandler.h +=================================================================== +--- quassel-0.10.0.orig/src/core/coreuserinputhandler.h ++++ quassel-0.10.0/src/core/coreuserinputhandler.h +@@ -86,7 +87,7 @@ protected: + private: + void doMode(const BufferInfo& bufferInfo, const QChar &addOrRemove, const QChar &mode, const QString &nickList); + void banOrUnban(const BufferInfo &bufferInfo, const QString &text, bool ban); +- void putPrivmsg(const QByteArray &target, const QByteArray &message, Cipher *cipher = 0); ++ void putPrivmsg(const QString &target, const QString &message, bool encodeForChannel, Cipher *cipher = 0); + + #ifdef HAVE_QCA2 + QByteArray encrypt(const QString &target, const QByteArray &message, bool *didEncrypt = 0) const; +@@ -100,6 +100,20 @@ private: + Command() {} + }; + ++ // A SplitGenerator that generates privmsgs. ++ class MsgSplitGenerator : public CoreNetwork::SplitGenerator { ++ public: ++ MsgSplitGenerator(CoreUserInputHandler *parent, const QString &target, bool encodeForChannel, Cipher *cipher); ++ virtual ~MsgSplitGenerator(); ++ virtual QList operator() (QString &splitMsg); ++ ++ private: ++ CoreUserInputHandler *_parent; ++ const QString &_target; ++ bool _encodeForChannel; ++ Cipher *_cipher; ++ }; ++ + QHash _delayedCommands; + }; + +Index: quassel-0.10.0/src/core/ctcpparser.cpp +=================================================================== +--- quassel-0.10.0.orig/src/core/ctcpparser.cpp ++++ quassel-0.10.0/src/core/ctcpparser.cpp +@@ -309,31 +309,21 @@ QByteArray CtcpParser::pack(const QByteA + } + + ++CtcpParser::CtcpSplitGenerator::CtcpSplitGenerator( ++ CtcpParser *parent, CoreNetwork *net, const QString &bufname, const QString &ctcpTag) ++ : _parent(parent), _net(net), _bufname(bufname), _ctcpTag(ctcpTag) {} ++ ++CtcpParser::CtcpSplitGenerator::~CtcpSplitGenerator() {} ++ ++QList CtcpParser::CtcpSplitGenerator::operator() (QString &splitMsg) { ++ return QList() << _net->serverEncode(_bufname) << _parent->lowLevelQuote(_parent->pack(_net->serverEncode(_ctcpTag), _net->userEncode(_bufname, splitMsg))); ++} ++ + void CtcpParser::query(CoreNetwork *net, const QString &bufname, const QString &ctcpTag, const QString &message) + { +- QList params; +- params << net->serverEncode(bufname) << lowLevelQuote(pack(net->serverEncode(ctcpTag), net->userEncode(bufname, message))); +- +- static const char *splitter = " .,-!?"; +- int maxSplitPos = message.count(); +- int splitPos = maxSplitPos; +- +- int overrun = net->userInputHandler()->lastParamOverrun("PRIVMSG", params); +- if (overrun) { +- maxSplitPos = message.count() - overrun -2; +- splitPos = -1; +- for (const char *splitChar = splitter; *splitChar != 0; splitChar++) { +- splitPos = qMax(splitPos, message.lastIndexOf(*splitChar, maxSplitPos) + 1); // keep split char on old line +- } +- if (splitPos <= 0 || splitPos > maxSplitPos) +- splitPos = maxSplitPos; +- +- params = params.mid(0, 1) << lowLevelQuote(pack(net->serverEncode(ctcpTag), net->userEncode(bufname, message.left(splitPos)))); +- } +- net->putCmd("PRIVMSG", params); +- +- if (splitPos < message.count()) +- query(net, bufname, ctcpTag, message.mid(splitPos)); ++ QString cmd("PRIVMSG"); ++ CtcpSplitGenerator cmdGenerator(this, net, bufname, ctcpTag); ++ net->putCmd(cmd, net->splitMessage(cmd, message, &cmdGenerator)); + } + + +Index: quassel-0.10.0/src/core/ctcpparser.h +=================================================================== +--- quassel-0.10.0.orig/src/core/ctcpparser.h ++++ quassel-0.10.0/src/core/ctcpparser.h +@@ -92,6 +92,20 @@ private: + CtcpReply(CoreNetwork *net, const QString &buf) : network(net), bufferName(buf) {} + }; + ++ // A SplitGenerator that generates CTCP messages. ++ class CtcpSplitGenerator : public CoreNetwork::SplitGenerator { ++ public: ++ CtcpSplitGenerator(CtcpParser *parent, CoreNetwork *net, const QString &bufname, const QString &ctcpTag); ++ virtual ~CtcpSplitGenerator(); ++ virtual QList operator() (QString &splitMsg); ++ ++ private: ++ CtcpParser *_parent; ++ CoreNetwork *_net; ++ const QString &_bufname; ++ const QString &_ctcpTag; ++ }; ++ + QHash _replies; + + QHash _ctcpMDequoteHash; diff -Nru quassel-0.10.1/debian/patches/CVE-2015-3427.patch quassel-0.10.1/debian/patches/CVE-2015-3427.patch --- quassel-0.10.1/debian/patches/CVE-2015-3427.patch 1970-01-01 01:00:00.000000000 +0100 +++ quassel-0.10.1/debian/patches/CVE-2015-3427.patch 2015-05-01 18:46:17.000000000 +0200 @@ -0,0 +1,68 @@ +From 6605882f41331c80f7ac3a6992650a702ec71283 Mon Sep 17 00:00:00 2001 +From: Michael Marley +Date: Thu, 23 Apr 2015 08:46:43 -0400 +Subject: [PATCH] Execute initDbSession() on DB reconnects + +Previously, the initDbSession() function would only be run on the +initial connect. Since the initDbSession() code in PostgreSQL is +used to fix the CVE-2013-4422 SQL Injection bug, this means that +Quassel was still vulnerable to that CVE if the PostgreSQL server +is restarted or the connection is lost at any point while Quassel +is running. + +This bug also causes the Qt5 psql timezone fix to stop working +after a reconnect. + +The fix is to disable Qt's automatic reconnecting, check the +connection status ourselves, and reconnect if necessary, executing +the initDbSession() function afterward. +--- + src/core/abstractsqlstorage.cpp | 15 ++++++++++++++- + src/core/abstractsqlstorage.h | 1 + + 2 files changed, 15 insertions(+), 1 deletion(-) + +diff --git a/src/core/abstractsqlstorage.cpp b/src/core/abstractsqlstorage.cpp +index cb07454..dce39cb 100644 +--- a/src/core/abstractsqlstorage.cpp ++++ b/src/core/abstractsqlstorage.cpp +@@ -53,7 +53,14 @@ QSqlDatabase AbstractSqlStorage::logDb() + if (!_connectionPool.contains(QThread::currentThread())) + addConnectionToPool(); + +- return QSqlDatabase::database(_connectionPool[QThread::currentThread()]->name()); ++ QSqlDatabase db = QSqlDatabase::database(_connectionPool[QThread::currentThread()]->name(),false); ++ ++ if (!db.isOpen()) { ++ qWarning() << "Database connection" << displayName() << "for thread" << QThread::currentThread() << "was lost, attempting to reconnect..."; ++ dbConnect(db); ++ } ++ ++ return db; + } + + +@@ -90,6 +97,12 @@ void AbstractSqlStorage::addConnectionToPool() + db.setPassword(password()); + } + ++ dbConnect(db); ++} ++ ++ ++void AbstractSqlStorage::dbConnect(QSqlDatabase &db) ++{ + if (!db.open()) { + quWarning() << "Unable to open database" << displayName() << "for thread" << QThread::currentThread(); + quWarning() << "-" << db.lastError().text(); +diff --git a/src/core/abstractsqlstorage.h b/src/core/abstractsqlstorage.h +index 90a8aa9..c39e826 100644 +--- a/src/core/abstractsqlstorage.h ++++ b/src/core/abstractsqlstorage.h +@@ -87,6 +87,7 @@ private slots: + + private: + void addConnectionToPool(); ++ void dbConnect(QSqlDatabase &db); + + int _schemaVersion; + bool _debug; diff -Nru quassel-0.10.1/debian/patches/series quassel-0.10.1/debian/patches/series --- quassel-0.10.1/debian/patches/series 2014-11-17 14:32:36.000000000 +0100 +++ quassel-0.10.1/debian/patches/series 2015-05-01 18:46:49.000000000 +0200 @@ -1,3 +1,5 @@ kubuntu_01_default_network_channel.patch kubuntu_02_enable_message_indicator.diff CVE-2014-8483.patch +CVE-2015-2778.patch +CVE-2015-3427.patch