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