test: fuzz test tr_variantFromBuf() (#2892)

* test: fuzz test tr_torrent_metainfo.parseBenc()

* fix: better error-checking in benc string parsing

* fix: return benc failure if the parse stack is unbalanced

* fix: stack range error when logging json parse errors

* test: fuzz test tr_variantFromBuf()
This commit is contained in:
Charles Kerr 2022-04-06 16:39:39 -05:00 committed by GitHub
parent a134445caa
commit f79a75cb21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 61 additions and 31 deletions

View File

@ -167,6 +167,11 @@ struct ParserStack
depth = 0;
}
[[nodiscard]] constexpr auto empty() const noexcept
{
return depth == 0;
}
constexpr void tokenWalked()
{
++stack[depth].n_children_walked;

View File

@ -101,6 +101,11 @@ std::optional<std::string_view> ParseString(std::string_view* benc)
// get the string length
auto svtmp = benc->substr(0, colon_pos);
if (!std::all_of(std::begin(svtmp), std::end(svtmp), [](auto ch) { return isdigit(ch) != 0; }))
{
return {};
}
auto const len = tr_parseNum<size_t>(svtmp);
if (!len || *len >= MaxBencStrLength)
{
@ -256,7 +261,7 @@ bool tr_variantParseBenc(tr_variant& top, int parse_opts, std::string_view benc,
using Stack = transmission::benc::ParserStack<512>;
auto stack = Stack{};
auto handler = MyHandler{ &top, parse_opts };
return transmission::benc::parse(benc, stack, handler, setme_end, error);
return transmission::benc::parse(benc, stack, handler, setme_end, error) && std::empty(stack);
}
/****

View File

@ -45,4 +45,4 @@ std::optional<std::string_view> tr_bencParseStr(std::string_view* benc_inout);
bool tr_variantParseBenc(tr_variant& top, int parse_opts, std::string_view benc, char const** setme_end, tr_error** error);
int tr_variantParseJson(tr_variant& setme, int opts, std::string_view benc, char const** setme_end);
bool tr_variantParseJson(tr_variant& setme, int opts, std::string_view json, char const** setme_end, tr_error** error);

View File

@ -23,6 +23,7 @@
#include "transmission.h"
#include "error.h"
#include "jsonsl.h"
#include "log.h"
#include "quark.h"
@ -39,10 +40,11 @@ static auto constexpr MaxDepth = int{ 64 };
struct json_wrapper_data
{
bool has_content;
size_t size;
std::string_view key;
evbuffer* keybuf;
evbuffer* strbuf;
int error;
tr_error* error;
std::deque<tr_variant*> stack;
tr_variant* top;
int parse_opts;
@ -82,14 +84,15 @@ static void error_handler(jsonsl_t jsn, jsonsl_error_t error, jsonsl_state_st* /
{
auto* data = static_cast<struct json_wrapper_data*>(jsn->data);
tr_logAddError(fmt::format(
_("Couldn't parse JSON at position {position} '{text:16s}': {error} ({error_code})"),
fmt::arg("position", jsn->pos),
fmt::arg("text", buf),
fmt::arg("error", jsonsl_strerror(error)),
fmt::arg("error_code", error)));
data->error = EILSEQ;
tr_error_set(
&data->error,
EILSEQ,
fmt::format(
_("Couldn't parse JSON at position {position} '{text}': {error} ({error_code})"),
fmt::arg("position", jsn->pos),
fmt::arg("text", std::string_view{ buf, std::min(size_t{ 16U }, data->size - jsn->pos) }),
fmt::arg("error", jsonsl_strerror(error)),
fmt::arg("error_code", error)));
}
static int error_callback(jsonsl_t jsn, jsonsl_error_t error, struct jsonsl_state_st* state, jsonsl_char_t* at)
@ -104,7 +107,7 @@ static void action_callback_PUSH(
struct jsonsl_state_st* state,
jsonsl_char_t const* /*buf*/)
{
auto* data = static_cast<struct json_wrapper_data*>(jsn->data);
auto* const data = static_cast<json_wrapper_data*>(jsn->data);
if ((state->type == JSONSL_T_LIST) || (state->type == JSONSL_T_OBJECT))
{
@ -345,7 +348,7 @@ static void action_callback_POP(
}
}
int tr_variantParseJson(tr_variant& setme, int parse_opts, std::string_view benc, char const** setme_end)
bool tr_variantParseJson(tr_variant& setme, int parse_opts, std::string_view benc, char const** setme_end, tr_error** error)
{
TR_ASSERT((parse_opts & TR_VARIANT_PARSE_JSON) != 0);
@ -358,7 +361,8 @@ int tr_variantParseJson(tr_variant& setme, int parse_opts, std::string_view benc
jsn->data = &data;
jsonsl_enable_all_callbacks(jsn);
data.error = 0;
data.error = nullptr;
data.size = std::size(benc);
data.has_content = false;
data.key = ""sv;
data.keybuf = evbuffer_new();
@ -372,9 +376,9 @@ int tr_variantParseJson(tr_variant& setme, int parse_opts, std::string_view benc
jsonsl_feed(jsn, static_cast<jsonsl_char_t const*>(std::data(benc)), std::size(benc));
/* EINVAL if there was no content */
if (data.error == 0 && !data.has_content)
if (data.error == nullptr && !data.has_content)
{
data.error = EINVAL;
tr_error_set(&data.error, EINVAL, "No content");
}
/* maybe set the end ptr */
@ -384,11 +388,15 @@ int tr_variantParseJson(tr_variant& setme, int parse_opts, std::string_view benc
}
/* cleanup */
int const error = data.error;
auto const success = data.error == nullptr;
if (data.error != nullptr)
{
tr_error_propagate(error, &data.error);
}
evbuffer_free(data.keybuf);
evbuffer_free(data.strbuf);
jsonsl_destroy(jsn);
return error;
return success;
}
/****

View File

@ -1240,20 +1240,14 @@ bool tr_variantFromBuf(tr_variant* setme, int opts, std::string_view buf, char c
auto locale_ctx = locale_context{};
use_numeric_locale(&locale_ctx, "C");
auto success = bool{};
if ((opts & TR_VARIANT_PARSE_BENC) != 0)
*setme = {};
auto const success = ((opts & TR_VARIANT_PARSE_BENC) != 0) ? tr_variantParseBenc(*setme, opts, buf, setme_end, error) :
tr_variantParseJson(*setme, opts, buf, setme_end, error);
if (!success)
{
success = tr_variantParseBenc(*setme, opts, buf, setme_end, error);
}
else
{
// TODO: tr_variantParseJson() should take a tr_error* same as ParseBenc
auto err = tr_variantParseJson(*setme, opts, buf, setme_end);
if (err != 0)
{
tr_error_set(error, EILSEQ, "error parsing encoded data"sv);
}
success = err == 0;
tr_variantFree(setme);
}
/* restore the previous locale */

View File

@ -8,6 +8,7 @@
#include "transmission.h"
#include "benc.h"
#include "crypto-utils.h"
#include "error.h"
#include "utils.h" /* tr_free */
#include "variant-common.h"
@ -536,3 +537,20 @@ TEST_F(VariantTest, dictFindType)
tr_variantFree(&top);
}
TEST_F(VariantTest, variantFromBufFuzz)
{
auto buf = std::vector<char>{};
auto top = tr_variant{};
for (size_t i = 0; i < 100000; ++i)
{
buf.resize(tr_rand_int(4096));
tr_rand_buffer(std::data(buf), std::size(buf));
auto const sv = std::string_view{ std::data(buf), std::size(buf) };
// std::cerr << '[' << tr_base64_encode({ std::data(buf), std::size(buf) }) << ']' << std::endl;
tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, sv, nullptr, nullptr);
tr_variantFromBuf(&top, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, sv, nullptr, nullptr);
}
}