Dear maintainer,

I've been using the patched build locally for 2 working days without
issues, so I think it's time to push it to unstable for wider testing.

This is quite a complex patch for this late in the release cycle, but
I really don't see an option for a less complex one.  But I suggest we
let it spend a few days in unstable before seeking an unblock request.

Attached is the nmudiff.

Cheers,
    Olly
diff -Nru quassel-0.10.0/debian/changelog quassel-0.10.0/debian/changelog
--- quassel-0.10.0/debian/changelog	2014-11-09 02:29:37.000000000 +1300
+++ quassel-0.10.0/debian/changelog	2015-04-01 11:44:18.000000000 +1300
@@ -1,3 +1,12 @@
+quassel (1:0.10.0-2.3) unstable; urgency=high
+
+  * Non-maintainer upload with maintainer's permission.
+  * Improve the message-splitting algorithm for PRIVMSG and CTCP.  Original
+    patch from Michael Marley, backported by Steinar H. Gunderson.  Fixes
+    CVE-2015-2778 and CVE-2015-2779.  (Closes: #781024)
+
+ -- Olly Betts <o...@survex.com>  Wed, 01 Apr 2015 11:41:28 +1300
+
 quassel (1:0.10.0-2.2) unstable; urgency=high
 
   * Non-maintainer upload.
diff -Nru quassel-0.10.0/debian/patches/CVE-2015-2778.patch quassel-0.10.0/debian/patches/CVE-2015-2778.patch
--- quassel-0.10.0/debian/patches/CVE-2015-2778.patch	1970-01-01 12:00:00.000000000 +1200
+++ quassel-0.10.0/debian/patches/CVE-2015-2778.patch	2015-04-01 11:37:08.000000000 +1300
@@ -0,0 +1,421 @@
+>From b5e38970ffd55e2dd9f706ce75af9a8d7730b1b8 Mon Sep 17 00:00:00 2001
+From: Michael Marley <mich...@michaelmarley.com>
+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<QByteArray> &, const QByteArray &)),
+         network(), SLOT(putCmd(QString, const QList<QByteArray> &, const QByteArray &)));
+ 
++    connect(this, SIGNAL(putCmd(QString, const QList<QList<QByteArray> > &, const QByteArray &)),
++        network(), SLOT(putCmd(QString, const QList<QList<QByteArray> > &, 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<QByteArray> &params, const QByteArray &prefix = QByteArray());
++    void putCmd(const QString &cmd, const QList<QList<QByteArray> > &params, 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<QList<QByteArray> > &params, const QByteArray &prefix)
++{
++    QListIterator<QList<QByteArray> > i(params);
++    while (i.hasNext()) {
++        QList<QByteArray> 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<QList<QByteArray> > CoreNetwork::splitMessage(const QString &cmd, const QString &message, SplitGenerator *cmdGenerator)
++{
++    QString wrkMsg(message);
++    QList<QList<QByteArray> > 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<QByteArray> 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<QByteArray> 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<QList<QByteArray>(QString &)>.
++    struct SplitGenerator {
++        virtual ~SplitGenerator() = 0;
++        virtual QList<QByteArray> operator() (QString &) = 0;
++    };
++    QList<QList<QByteArray> > 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<QByteArray> &params, const QByteArray &prefix = QByteArray());
++    void putCmd(const QString &cmd, const QList<QList<QByteArray> > &params, 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<QByteArray> 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<QByteArray>() << 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<QByteArray>() << target << crypted);
+-        if (splitPos < message.count())
+-            putPrivmsg(target, message.mid(splitPos), cipher);
++    QByteArray targetEnc = _parent->serverEncode(_target);
++    return QList<QByteArray>() << 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
+@@ -36,6 +36,7 @@ public:
+     inline CoreNetwork *coreNetwork() const { return qobject_cast<CoreNetwork *>(parent()); }
+ 
+     void handleUserInput(const BufferInfo &bufferInfo, const QString &text);
++    int lastParamOverrun(const QString &cmd, const QList<QByteArray> &params);
+ 
+ public slots:
+     void handleAway(const BufferInfo &bufferInfo, const QString &text);
+@@ -86,8 +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);
+-    int lastParamOverrun(const QString &cmd, const QList<QByteArray> &params);
++    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<QByteArray> operator() (QString &splitMsg);
++
++    private:
++        CoreUserInputHandler *_parent;
++        const QString &_target;
++        bool _encodeForChannel;
++        Cipher *_cipher;
++    };
++
+     QHash<int, Command> _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,11 +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<QByteArray> CtcpParser::CtcpSplitGenerator::operator() (QString &splitMsg) {
++    return QList<QByteArray>() << _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<QByteArray> params;
+-    params << net->serverEncode(bufname) << lowLevelQuote(pack(net->serverEncode(ctcpTag), net->userEncode(bufname, message)));
+-    net->putCmd("PRIVMSG", params);
++    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<QByteArray> operator() (QString &splitMsg);
++
++    private:
++        CtcpParser *_parent;
++        CoreNetwork *_net;
++        const QString &_bufname;
++        const QString &_ctcpTag;
++    };
++
+     QHash<QUuid, CtcpReply> _replies;
+ 
+     QHash<QByteArray, QByteArray> _ctcpMDequoteHash;
diff -Nru quassel-0.10.0/debian/patches/series quassel-0.10.0/debian/patches/series
--- quassel-0.10.0/debian/patches/series	2014-10-29 05:16:01.000000000 +1300
+++ quassel-0.10.0/debian/patches/series	2015-04-01 11:37:31.000000000 +1300
@@ -1,2 +1,3 @@
 01_default_network_channel.patch
 CVE-2014-8483.patch
+CVE-2015-2778.patch

Reply via email to