Skip to content

Commit 57b66a8

Browse files
Validation: fix out-of-bounds access when content ends in a string
We can only validate_number() if we know that we have a number to validate in the first place. If we've reached the end of our string, the content that follows is not necessarily a number (it could be a Break byte). More importantly, we could reach the end of the buffer. This issue was masked by the way we provided data to the parser. It always came from read-only memory becausee of QByteArray::fromRawData(), so valgrind never caught any issues. Using QByteArray directly wouldn't have helped because it always inserts a terminating null byte, which always validates as a correct number (unsigned 0) and fails to trigger valgrind. So we need to use malloc() directly to make Valgrind complain. And there was already a test that did: ==26543== Invalid read of size 1 ==26543== at 0x483EA10: memmove (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) ==26543== by 0x43CEEA: read_bytes_unchecked (cborinternal_p.h:239) ==26543== by 0x43CFEC: extract_number_checked (cborinternal_p.h:286) ==26543== by 0x43D3E9: validate_number (cborvalidation.c:304) ==26543== by 0x43DC7B: validate_value (cborvalidation.c:551) ==26543== by 0x43DE8C: cbor_value_validate (cborvalidation.c:645) ==26543== by 0x4328D2: tst_Parser::strictValidation() (tst_parser.cpp:1637) ==26543== by 0x434632: tst_Parser::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) (tst_parser.moc:291) ==26543== by 0x4C0B36D: QMetaMethod::invoke(QObject*, Qt::ConnectionType, QGenericReturnArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument) const (qmetaobject.cpp:2310) ==26543== by 0x48673E9: QMetaMethod::invoke(QObject*, Qt::ConnectionType, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument) const (qmetaobject.h:122) ==26543== by 0x4860256: QTest::TestMethods::invokeTestOnData(int) const (qtestcase.cpp:922) ==26543== by 0x4860D4B: QTest::TestMethods::invokeTest(int, char const*, QTest::WatchDog*) const (qtestcase.cpp:1121) ==26543== Address 0x61c4db1 is 0 bytes after a block of size 1 alloc'd ==26543== at 0x483777F: malloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) ==26543== by 0x403891: ParserWrapper::allocateMemory(unsigned long) (tst_parser.cpp:181) ==26543== by 0x436898: ParserWrapper::init(QByteArray const&) (tst_parser.cpp:126) ==26543== by 0x432712: tst_Parser::strictValidation() (tst_parser.cpp:1634) ==26543== by 0x434632: tst_Parser::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) (tst_parser.moc:291) ==26543== by 0x4C0B36D: QMetaMethod::invoke(QObject*, Qt::ConnectionType, QGenericReturnArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument) const (qmetaobject.cpp:2310) ==26543== by 0x48673E9: QMetaMethod::invoke(QObject*, Qt::ConnectionType, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument) const (qmetaobject.h:122) ==26543== by 0x4860256: QTest::TestMethods::invokeTestOnData(int) const (qtestcase.cpp:922) ==26543== by 0x4860D4B: QTest::TestMethods::invokeTest(int, char const*, QTest::WatchDog*) const (qtestcase.cpp:1121) ==26543== by 0x4862083: QTest::TestMethods::invokeTests(QObject*) const (qtestcase.cpp:1465) ==26543== by 0x4862C14: QTest::qRun() (qtestcase.cpp:1903) ==26543== by 0x48626C3: QTest::qExec(QObject*, int, char**) (qtestcase.cpp:1792) ==26543== PASS : tst_Parser::strictValidation(bytearray-0) This commit goes further and makes it so an out-of-bounds access will cause a pagefault. Fixes #156. Signed-off-by: Thiago Macieira <[email protected]>
1 parent 49ef3f8 commit 57b66a8

File tree

2 files changed

+113
-10
lines changed

2 files changed

+113
-10
lines changed

src/cborvalidation.c

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/****************************************************************************
22
**
3-
** Copyright (C) 2017 Intel Corporation
3+
** Copyright (C) 2019 Intel Corporation
44
**
55
** Permission is hereby granted, free of charge, to any person obtaining a copy
66
** of this software and associated documentation files (the "Software"), to deal
@@ -561,13 +561,17 @@ static CborError validate_value(CborValue *it, uint32_t flags, int recursionLeft
561561
return err;
562562

563563
while (1) {
564-
err = validate_number(it, type, flags);
564+
CborValue next;
565+
err = _cbor_value_get_string_chunk(it, &ptr, &n, &next);
565566
if (err)
566567
return err;
568+
if (ptr) {
569+
err = validate_number(it, type, flags);
570+
if (err)
571+
return err;
572+
}
567573

568-
err = _cbor_value_get_string_chunk(it, &ptr, &n, it);
569-
if (err)
570-
return err;
574+
*it = next;
571575
if (!ptr)
572576
break;
573577

tests/parser/tst_parser.cpp

Lines changed: 104 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/****************************************************************************
22
**
3-
** Copyright (C) 2017 Intel Corporation
3+
** Copyright (C) 2019 Intel Corporation
44
**
55
** Permission is hereby granted, free of charge, to any person obtaining a copy
66
** of this software and associated documentation files (the "Software"), to deal
@@ -23,12 +23,23 @@
2323
****************************************************************************/
2424

2525
#define _XOPEN_SOURCE 700
26+
#define _DARWIN_C_SOURCE 1 /* need MAP_ANON */
2627
#include <QtTest>
2728
#include "cbor.h"
2829
#include <stdio.h>
2930
#include <stdarg.h>
3031

32+
#if defined(Q_OS_UNIX)
33+
# include <sys/mman.h>
34+
# include <unistd.h>
35+
#elif defined(Q_OS_WIN)
36+
# define WIN32_LEAN_AND_MEAN 1
37+
# define NOMINMAX 1
38+
# include <windows.h>
39+
#endif
40+
3141
Q_DECLARE_METATYPE(CborError)
42+
3243
namespace QTest {
3344
template<> char *toString<CborError>(const CborError &err)
3445
{
@@ -101,6 +112,95 @@ private slots:
101112
void recursionLimit();
102113
};
103114

115+
struct ParserWrapper
116+
{
117+
void *realdata = nullptr;
118+
uint8_t *data;
119+
size_t len;
120+
CborParser parser;
121+
CborValue first;
122+
123+
~ParserWrapper() { freeMemory(); }
124+
125+
CborError init(const QByteArray &ba)
126+
{
127+
freeMemory();
128+
data = allocateMemory(ba.size());
129+
memcpy(data, ba.data(), ba.size());
130+
return cbor_parser_init(data, len, 0, &parser, &first);
131+
}
132+
133+
uint8_t *allocateMemory(size_t);
134+
void freeMemory();
135+
136+
static const size_t PageSize = 4096;
137+
static inline size_t mmapAllocation(size_t n)
138+
{
139+
// round up and add one page
140+
return (n + 2*PageSize) & ~(PageSize - 1);
141+
}
142+
static bool shouldUseMmap();
143+
};
144+
145+
bool ParserWrapper::shouldUseMmap()
146+
{
147+
static int v = qEnvironmentVariableIntValue("PARSER_NO_MMAP");
148+
return !v;
149+
}
150+
151+
uint8_t *ParserWrapper::allocateMemory(size_t n)
152+
{
153+
len = n;
154+
if (shouldUseMmap()) {
155+
size_t alloc = mmapAllocation(n);
156+
#if defined(Q_OS_UNIX)
157+
realdata = mmap(nullptr, alloc, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
158+
Q_ASSERT_X(realdata != MAP_FAILED, "allocateMemory", "mmap failed!");
159+
160+
// mark last page inaccessible
161+
uint8_t *ptr = static_cast<uint8_t *>(realdata);
162+
ptr += alloc - PageSize;
163+
mprotect(ptr, PageSize, PROT_NONE);
164+
165+
ptr -= n;
166+
return ptr;
167+
#elif defined(Q_OS_WIN)
168+
// ### implement me
169+
DWORD flAllocationType = MEM_COMMIT | MEM_RESERVE;
170+
DWORD flProtect = PAGE_READWRITE;
171+
realdata = VirtualAlloc(nullptr, alloc, flAllocationType, flProtect);
172+
Q_ASSERT_X(realdata, "allocateMemory", "VirtualAlloc failed!");
173+
174+
// mark last page inaccessible
175+
uint8_t *ptr = static_cast<uint8_t *>(realdata);
176+
ptr += alloc - PageSize;
177+
VirtualProtect(ptr, PageSize, PAGE_NOACCESS, nullptr);
178+
179+
ptr -= n;
180+
return ptr;
181+
#endif
182+
}
183+
realdata = malloc(n);
184+
return static_cast<uint8_t *>(realdata);
185+
}
186+
187+
void ParserWrapper::freeMemory()
188+
{
189+
if (shouldUseMmap()) {
190+
if (realdata) {
191+
#if defined(Q_OS_UNIX)
192+
size_t alloc = mmapAllocation(len);
193+
munmap(realdata, alloc);
194+
#elif defined(Q_OS_WIN)
195+
VirtualFree(realdata, 0, MEM_RELEASE);
196+
#endif
197+
}
198+
return;
199+
}
200+
201+
free(realdata);
202+
}
203+
104204
static CborError qstring_printf(void *out, const char *fmt, ...)
105205
{
106206
auto str = static_cast<QString *>(out);
@@ -1947,12 +2047,11 @@ void tst_Parser::strictValidation()
19472047
QFETCH(CborError, expectedError);
19482048

19492049
QString decoded;
1950-
CborParser parser;
1951-
CborValue first;
1952-
CborError err = cbor_parser_init(reinterpret_cast<const quint8 *>(data.constData()), data.length(), 0, &parser, &first);
2050+
ParserWrapper w;
2051+
CborError err = w.init(data);
19532052
QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\"");
19542053

1955-
err = cbor_value_validate(&first, flags);
2054+
err = cbor_value_validate(&w.first, flags);
19562055
QCOMPARE(err, expectedError);
19572056
}
19582057

0 commit comments

Comments
 (0)