From da7e54e45eb77bdf1aaa199c77a38fca826f4120 Mon Sep 17 00:00:00 2001
From: Olivier Dion <olivier.dion@savoirfairelinux.com>
Date: Thu, 10 Jun 2021 15:17:40 -0400
Subject: [PATCH] fuzzing/sipf-fmt: Add SIP format class and Flex lexer

Change-Id: Icecdfddd200932eb7f4f5fdd7e6a406472f85c18
---
 test/fuzzing/Makefile.am       |   6 +-
 test/fuzzing/lib/sip-fmt.cpp   | 152 ++++++++++++++++++++++++++++
 test/fuzzing/lib/sip-fmt.h     |  73 ++++++++++++++
 test/fuzzing/lib/sip-parser.ll | 177 +++++++++++++++++++++++++++++++++
 4 files changed, 407 insertions(+), 1 deletion(-)
 create mode 100644 test/fuzzing/lib/sip-fmt.cpp
 create mode 100644 test/fuzzing/lib/sip-fmt.h
 create mode 100644 test/fuzzing/lib/sip-parser.ll

diff --git a/test/fuzzing/Makefile.am b/test/fuzzing/Makefile.am
index f127c1c7d7..eda48c98c4 100644
--- a/test/fuzzing/Makefile.am
+++ b/test/fuzzing/Makefile.am
@@ -1,11 +1,15 @@
 include $(top_srcdir)/globals.mk
 
 if ENABLE_FUZZING
+
+%.cpp: %.ll
+	flex --outfile $@ $^
+
 AM_CXXFLAGS += -I$(top_srcdir)/src -I. -include common.h
 AM_LDFLAGS += $(top_builddir)/src/libring.la
 check_PROGRAMS =
 
 lib_LTLIBRARIES = libfuzz.la
 
-libfuzz_la_SOURCES = lib/utils.cpp lib/supervisor.cpp lib/gnutls.cpp lib/rand.cpp lib/syslog.cpp
+libfuzz_la_SOURCES = lib/utils.cpp lib/supervisor.cpp lib/gnutls.cpp lib/rand.cpp lib/syslog.cpp lib/sip-fmt.cpp lib/sip-parser.cpp
 endif
diff --git a/test/fuzzing/lib/sip-fmt.cpp b/test/fuzzing/lib/sip-fmt.cpp
new file mode 100644
index 0000000000..9fdb131ed3
--- /dev/null
+++ b/test/fuzzing/lib/sip-fmt.cpp
@@ -0,0 +1,152 @@
+/*
+ *  Copyright (C) 2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Olivier Dion <olivier.dion>@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#include <cassert>
+
+#include "lib/sip-fmt.h"
+
+SIPFmt::SIPFmt(const std::vector<uint8_t>& data)
+    : isValid_(false)
+
+{
+    parse(data);
+}
+
+void
+SIPFmt::pushBody(char *bytes, size_t len)
+{
+        for (size_t i=0; i<len; ++i) {
+            body_.emplace_back(bytes[i]);
+        }
+}
+
+void
+SIPFmt::setField(const std::string& field)
+{
+    size_t at = field.find_first_of(':');
+
+    assert(at != std::string::npos);
+
+    setFieldValue(field.substr(0, at), field.substr(at + 1));
+}
+
+void
+SIPFmt::setFieldValue(const std::string& field, const std::string& value)
+{
+    std::string fieldLow;
+
+    fieldLow.reserve(field.size());
+
+    for (auto it = field.cbegin(); it != field.cend(); ++it) {
+        fieldLow.push_back(tolower(*it));
+    }
+
+    fields_[fieldLow] = value;
+}
+
+const std::string&
+SIPFmt::getField(const std::string& field) const
+{
+    static std::string emptyString("");
+
+    std::string fieldLow;
+
+    fieldLow.reserve(field.size());
+
+     for (auto it = field.cbegin(); it != field.cend(); ++it) {
+        fieldLow.push_back(tolower(*it));
+    }
+
+    try {
+            return fields_.at(fieldLow);
+    } catch (...) {
+            return emptyString;
+    }
+}
+
+const std::vector<uint8_t>&
+SIPFmt::getBody()
+{
+    return body_;
+}
+
+void
+SIPFmt::swapBody(std::vector<uint8_t>& newBody)
+{
+    body_.swap(newBody);
+}
+
+void
+SIPFmt::swap(std::vector<uint8_t>& with)
+{
+    if (not isValid_) {
+        return;
+    }
+
+    std::vector<uint8_t> data;
+
+    auto push_str = [&](const std::string& str) {
+        for (auto it = str.cbegin(); it != str.cend(); ++it) {
+            data.emplace_back((uint8_t) *it);
+        }
+    };
+
+    auto push_CRLN = [&] {
+        data.emplace_back((uint8_t) '\r');
+        data.emplace_back((uint8_t) '\n');
+    };
+
+    if (isResponse()) {
+        push_str(version_);
+        data.emplace_back((uint8_t) ' ');
+        push_str(status_);
+        data.emplace_back((uint8_t) ' ');
+        push_str(msg_);
+        push_CRLN();
+    } else {
+        push_str(method_);
+        data.emplace_back(' ');
+        push_str(URI_);
+        data.emplace_back(' ');
+        push_str(version_);
+        push_CRLN();
+    }
+
+    setFieldValue("content-length", std::to_string(body_.size()));
+
+    for (auto it = fields_.cbegin(); it != fields_.cend(); ++it) {
+        push_str(it->first);
+        data.emplace_back((uint8_t) ':');
+        data.emplace_back((uint8_t) ' ');
+
+        push_str(it->second);
+        push_CRLN();
+    }
+
+    push_CRLN();
+
+    for (auto it = body_.begin(); it != body_.end(); ++it) {
+        data.emplace_back((uint8_t) *it);
+    }
+
+    data.shrink_to_fit();
+
+    data.swap(with);
+}
diff --git a/test/fuzzing/lib/sip-fmt.h b/test/fuzzing/lib/sip-fmt.h
new file mode 100644
index 0000000000..f2378746f9
--- /dev/null
+++ b/test/fuzzing/lib/sip-fmt.h
@@ -0,0 +1,73 @@
+/*
+ *  Copyright (C) 2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Olivier Dion <olivier.dion>@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#include <vector>
+#include <iostream>
+#include <string>
+#include <map>
+
+class SIPFmt
+{
+public:
+    SIPFmt(const std::vector<uint8_t>& data);
+
+    bool parse(const std::vector<uint8_t>& blob);
+
+    void pushBody(char *bytes, size_t len);
+
+
+    const std::string& getField(const std::string& field) const ;
+    const std::vector<uint8_t>& getBody();
+
+    void setVersion(const std::string& version) { version_ = version; }
+    void setField(const std::string& field);
+    void setFieldValue(const std::string& field, const std::string& value);
+    void setMethod(const std::string& method) { method_ = method; }
+    void setURI(const std::string& URI) { URI_ = URI; }
+    void setStatus(const std::string& status) { status_ = status; }
+    void setMsg(const std::string& msg) { msg_ = msg; }
+
+    void swapBody(std::vector<uint8_t>& body);
+
+    bool isValid() const { return isValid_; }
+    bool isRequest() const { return isValid_ and isRequest_; }
+    bool isResponse() const { return isValid_ and not isRequest_; }
+
+    bool isApplication(const std::string& app) const
+    {
+          return isValid() and (std::string::npos != getField("content-type").find("application/" + app));
+    }
+
+    void setAsRequest() { isRequest_ = true; };
+    void setAsResponse() { isRequest_ = false; };
+
+    void swap(std::vector<uint8_t>& with);
+
+private:
+    std::vector<uint8_t> body_ {};
+    std::string status_ {};
+    std::string version_ {};
+    std::string msg_ {};
+    std::string URI_ {};
+    std::string method_ {};
+    std::map<std::string, std::string> fields_ {};
+    bool isValid_;
+    bool isRequest_ {};
+};
diff --git a/test/fuzzing/lib/sip-parser.ll b/test/fuzzing/lib/sip-parser.ll
new file mode 100644
index 0000000000..bf8061463f
--- /dev/null
+++ b/test/fuzzing/lib/sip-parser.ll
@@ -0,0 +1,177 @@
+/*
+ *  Copyright (C) 2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Olivier Dion <olivier.dion>@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+%{
+
+#include <vector>
+#include <cstdint>
+
+#include "lib/sip-fmt.h"
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wsuggest-attribute=pure"
+#pragma GCC diagnostic ignored "-Wsuggest-attribute=malloc"
+#pragma GCC diagnostic ignored "-Wnull-dereference"
+#pragma GCC diagnostic ignored "-Wstrict-overflow"
+%}
+
+ /* Definitions */
+%option 8bit
+%option warn
+%option always-interactive
+%option pointer
+%option reentrant
+%option noyywrap
+%option noyylineno
+%option nounput
+%option nodefault
+%option noinput
+%option debug
+%option prefix="sip_yy"
+%option extra-type="SIPFmt*"
+
+%x RESPONSE_STATUS
+%x RESPONSE_MSG
+%x REQUEST_URI
+%x REQUEST_VERSION
+%x FIELD_SEQUENCE
+%x BODY
+
+CRLF                    "\r\n"
+SIP_METHOD              REGISTER|INVITE|ACK|CANCEL|BYE|OPTIONS|MESSAGE|INFO
+SIP_VERSION             "SIP/2.0"
+SIP_STATUS              [0-9]{3}
+SIP_MSG                 [^\r\n]*
+SIP_URI                 ("sip"|"sips")":"[^\r\n ]+
+FIELD_NAME              ([a-zA-Z0-9]|[-_ ])+
+FIELD_VALUE             [^\r\n]+
+
+ /* Rules */
+%%
+
+ /* Reponse line */
+<INITIAL>^{SIP_VERSION} {
+        BEGIN(RESPONSE_STATUS);
+        yyextra->setAsResponse();
+        yyextra->setVersion(yytext);
+}
+
+<INITIAL>^{SIP_METHOD} {
+        BEGIN(REQUEST_URI);
+        yyextra->setAsRequest();
+        yyextra->setMethod(yytext);
+ }
+
+<REQUEST_URI>{SIP_URI} {
+        BEGIN(REQUEST_VERSION);
+        yyextra->setURI(yytext);
+}
+
+<REQUEST_VERSION>{SIP_VERSION} {
+        yyextra->setVersion(yytext);
+}
+
+<RESPONSE_STATUS>{SIP_STATUS} {
+        BEGIN(RESPONSE_MSG);
+        yyextra->setStatus(yytext);
+}
+
+<RESPONSE_MSG>{SIP_MSG} {
+        yyextra->setMsg(yytext);
+}
+
+<REQUEST_VERSION,RESPONSE_MSG>{CRLF} {
+        BEGIN(FIELD_SEQUENCE);
+}
+
+
+ /*
+  * Field sequence
+  *
+  * We don't support multi-line field value.
+  */
+<FIELD_SEQUENCE>{
+
+{FIELD_NAME}":"{FIELD_VALUE}{CRLF} {
+        yytext[yyleng - 2] = '\0';
+        yyextra->setField(yytext);
+        yytext[yyleng - 2] = '\r';
+}
+
+{CRLF} {
+        BEGIN(BODY);
+}
+
+}
+
+<BODY>{
+
+.+|\n {
+     yyextra->pushBody(yytext, yyleng);
+}
+
+}
+
+ /* Don't care about spaces */
+<INITIAL,RESPONSE_MSG,RESPONSE_STATUS,REQUEST_VERSION,REQUEST_URI>[ \t]+  { }
+
+<INITIAL,BODY><<EOF>> { return 0; }
+<RESPONSE_STATUS,RESPONSE_MSG,FIELD_SEQUENCE><<EOF>> { return -1; }
+
+ /* Default rule */
+<*>.|\n { return -1; }
+
+%%
+
+/* END LEX */
+
+bool
+SIPFmt::parse(const std::vector<uint8_t>& blob)
+{
+        yyscan_t scanner = NULL;
+        int err;
+
+        YY_BUFFER_STATE state;
+
+        isValid_ = false;
+
+        if (sip_yylex_init_extra(this, &scanner)) {
+                return isValid_;
+        }
+
+        // sip_yyset_debug(1, scanner);
+
+        state = sip_yy_scan_bytes((const char*)blob.data(),
+                                  blob.size(),
+                                  scanner);
+
+        sip_yy_switch_to_buffer(state, scanner);
+
+        err = sip_yylex(scanner);
+
+        sip_yy_delete_buffer(state, scanner);
+        sip_yylex_destroy(scanner);
+
+        if (err >= 0) {
+                isValid_ = true;
+        }
+
+        return isValid_;
+}
-- 
GitLab