Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion lang/c++/impl/Resolver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ NoOp noop;
#define DEBUG_OUT(str) noop << str
#endif

// The branch index is read straight off the wire, so it has to be checked
// against the writer union before it is used to index the resolver vectors.
static void checkUnionChoice(size_t choice, size_t branches) {
if (choice >= branches) {
throw Exception("Union selection out of range, got {}, expecting 0-{}",
choice, branches - 1);
}
}

template<typename T>
class PrimitiveSkipper : public Resolver {
public:
Expand Down Expand Up @@ -307,7 +316,10 @@ class EnumParser : public Resolver {

void parse(Reader &reader, uint8_t *address) const final {
auto val = static_cast<size_t>(reader.readEnum());
assert(val < mapping_.size());
if (val >= mapping_.size()) {
throw Exception("Enum index out of range, got {}, expecting 0-{}",
val, mapping_.size() - 1);
}

if (mapping_[val] < readerSize_) {
auto *location = reinterpret_cast<EnumRepresentation *>(address + offset_);
Expand All @@ -329,6 +341,7 @@ class UnionSkipper : public Resolver {
void parse(Reader &reader, uint8_t *address) const final {
DEBUG_OUT("Skipping union");
auto choice = static_cast<size_t>(reader.readUnion());
checkUnionChoice(choice, resolvers_.size());
resolvers_[choice]->parse(reader, address);
}

Expand All @@ -345,6 +358,7 @@ class UnionParser : public Resolver {
void parse(Reader &reader, uint8_t *address) const final {
DEBUG_OUT("Reading union");
auto writerChoice = static_cast<size_t>(reader.readUnion());
checkUnionChoice(writerChoice, resolvers_.size());
auto *readerChoice = reinterpret_cast<int64_t *>(address + choiceOffset_);

*readerChoice = choiceMapping_[writerChoice];
Expand Down Expand Up @@ -375,6 +389,7 @@ class UnionToNonUnionParser : public Resolver {
void parse(Reader &reader, uint8_t *address) const final {
DEBUG_OUT("Reading union to non-union");
auto choice = static_cast<size_t>(reader.readUnion());
checkUnionChoice(choice, resolvers_.size());
resolvers_[choice]->parse(reader, address);
}

Expand Down
61 changes: 61 additions & 0 deletions lang/c++/test/unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@
#include "Compiler.hh"
#include "Decoder.hh"
#include "Encoder.hh"
#include "Layout.hh"
#include "Node.hh"
#include "Parser.hh"
#include "ResolverSchema.hh"
#include "ResolvingReader.hh"
#include "Schema.hh"
#include "SchemaResolution.hh"
#include "Serializer.hh"
Expand Down Expand Up @@ -912,10 +915,68 @@ struct TestBadStuff {
std::cout << "(intentional) error: " << error << '\n';
}

// A union branch index and an enum ordinal are read straight off the wire,
// so a corrupt or hostile datum can name a branch the writer schema does
// not have. Both must be rejected instead of indexing out of range.
void testOutOfRangeUnionIndex() {
std::cout << "TestOutOfRangeUnionIndex\n";

avro::ValidSchema writer = avro::compileJsonSchemaFromString(
R"({"type":"record","name":"R","fields":[)"
R"({"name":"x","type":["null","int"]}]})");
avro::ValidSchema reader = avro::compileJsonSchemaFromString(
R"({"type":"record","name":"R","fields":[)"
R"({"name":"y","type":"int"}]})");

avro::CompoundLayout layout(0);
std::unique_ptr<avro::Layout> field(new avro::PrimitiveLayout(0));
layout.add(field);

// zigzag(10000) == branch 5000, the writer union has 2 branches
const char data[] = {'\x90', '\x4e'};
avro::OutputBuffer buf;
buf.writeTo(data, sizeof(data));

avro::ResolverSchema xSchema(writer, reader, layout);
avro::ResolvingReader r(xSchema, avro::InputBuffer(buf));

uint8_t object[64] = {0};
BOOST_CHECK_THROW(r.parse(object), avro::Exception);
}

void testOutOfRangeEnumIndex() {
std::cout << "TestOutOfRangeEnumIndex\n";

const char *json =
R"({"type":"record","name":"R","fields":[{"name":"e","type":)"
R"({"type":"enum","name":"E","symbols":["A","B"]}}]})";
avro::ValidSchema schema = avro::compileJsonSchemaFromString(json);

avro::CompoundLayout layout(0);
auto *enumLayout = new avro::CompoundLayout(0);
std::unique_ptr<avro::Layout> value(new avro::PrimitiveLayout(0));
enumLayout->add(value);
std::unique_ptr<avro::Layout> field(enumLayout);
layout.add(field);

// zigzag(10000) == ordinal 5000, the enum has 2 symbols
const char data[] = {'\x90', '\x4e'};
avro::OutputBuffer buf;
buf.writeTo(data, sizeof(data));

avro::ResolverSchema xSchema(schema, schema, layout);
avro::ResolvingReader r(xSchema, avro::InputBuffer(buf));

uint8_t object[64] = {0};
BOOST_CHECK_THROW(r.parse(object), avro::Exception);
}

void test() {
std::cout << "TestBadStuff\n";
testBadFile();
testBadSchema();
testOutOfRangeUnionIndex();
testOutOfRangeEnumIndex();
}
};

Expand Down