mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-31 14:01:59 +00:00
Security: avoid pre-allocating vectors based on file data
In places where std::vector<T>(size_t) was used, either validate that the size parameter is sane or refactor code to avoid the need to pre-allocate the vector.
This commit is contained in:
parent
10bceb552f
commit
0bfe902489
@ -1,5 +1,11 @@
|
|||||||
2013-10-05 Jay Berkenbilt <ejb@ql.org>
|
2013-10-05 Jay Berkenbilt <ejb@ql.org>
|
||||||
|
|
||||||
|
* Security fix: In places where std::vector<T>(size_t) was used,
|
||||||
|
either validate that the size parameter is sane or refactor code
|
||||||
|
to avoid the need to pre-allocate the vector. This reduces the
|
||||||
|
likelihood of allocating a lot of memory in response to invalid
|
||||||
|
data in linearization hint streams.
|
||||||
|
|
||||||
* Security fix: sanitize /W array in cross reference stream to
|
* Security fix: sanitize /W array in cross reference stream to
|
||||||
avoid a potential integer overflow in a multiplication. It is
|
avoid a potential integer overflow in a multiplication. It is
|
||||||
unlikely that any exploits were possible from this bug as
|
unlikely that any exploits were possible from this bug as
|
||||||
|
@ -23,13 +23,22 @@ static void
|
|||||||
load_vector_int(BitStream& bit_stream, int nitems, std::vector<T>& vec,
|
load_vector_int(BitStream& bit_stream, int nitems, std::vector<T>& vec,
|
||||||
int bits_wanted, int_type T::*field)
|
int bits_wanted, int_type T::*field)
|
||||||
{
|
{
|
||||||
|
bool append = vec.empty();
|
||||||
// nitems times, read bits_wanted from the given bit stream,
|
// nitems times, read bits_wanted from the given bit stream,
|
||||||
// storing results in the ith vector entry.
|
// storing results in the ith vector entry.
|
||||||
|
|
||||||
for (int i = 0; i < nitems; ++i)
|
for (int i = 0; i < nitems; ++i)
|
||||||
{
|
{
|
||||||
|
if (append)
|
||||||
|
{
|
||||||
|
vec.push_back(T());
|
||||||
|
}
|
||||||
vec[i].*field = bit_stream.getBits(bits_wanted);
|
vec[i].*field = bit_stream.getBits(bits_wanted);
|
||||||
}
|
}
|
||||||
|
if (static_cast<int>(vec.size()) != nitems)
|
||||||
|
{
|
||||||
|
throw std::logic_error("vector has wrong size in load_vector_int");
|
||||||
|
}
|
||||||
// The PDF spec says that each hint table starts at a byte
|
// The PDF spec says that each hint table starts at a byte
|
||||||
// boundary. Each "row" actually must start on a byte boundary.
|
// boundary. Each "row" actually must start on a byte boundary.
|
||||||
bit_stream.skipToNextByte();
|
bit_stream.skipToNextByte();
|
||||||
@ -255,6 +264,17 @@ QPDF::readLinearizationData()
|
|||||||
|
|
||||||
// Store linearization parameter data
|
// Store linearization parameter data
|
||||||
|
|
||||||
|
// Various places in the code use linp.npages, which is
|
||||||
|
// initialized from N, to pre-allocate memory, so make sure it's
|
||||||
|
// accurate and bail right now if it's not.
|
||||||
|
if (N.getIntValue() != static_cast<long long>(getAllPages().size()))
|
||||||
|
{
|
||||||
|
throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
|
||||||
|
"linearization hint table",
|
||||||
|
this->file->getLastOffset(),
|
||||||
|
"/N does not match number of pages");
|
||||||
|
}
|
||||||
|
|
||||||
// file_size initialized by isLinearized()
|
// file_size initialized by isLinearized()
|
||||||
this->linp.first_page_object = O.getIntValue();
|
this->linp.first_page_object = O.getIntValue();
|
||||||
this->linp.first_page_end = E.getIntValue();
|
this->linp.first_page_end = E.getIntValue();
|
||||||
@ -396,10 +416,9 @@ QPDF::readHPageOffset(BitStream h)
|
|||||||
t.nbits_shared_numerator = h.getBits(16); // 12
|
t.nbits_shared_numerator = h.getBits(16); // 12
|
||||||
t.shared_denominator = h.getBits(16); // 13
|
t.shared_denominator = h.getBits(16); // 13
|
||||||
|
|
||||||
unsigned int nitems = this->linp.npages;
|
|
||||||
std::vector<HPageOffsetEntry>& entries = t.entries;
|
std::vector<HPageOffsetEntry>& entries = t.entries;
|
||||||
entries = std::vector<HPageOffsetEntry>(nitems);
|
entries.clear();
|
||||||
|
unsigned int nitems = this->linp.npages;
|
||||||
load_vector_int(h, nitems, entries,
|
load_vector_int(h, nitems, entries,
|
||||||
t.nbits_delta_nobjects,
|
t.nbits_delta_nobjects,
|
||||||
&HPageOffsetEntry::delta_nobjects);
|
&HPageOffsetEntry::delta_nobjects);
|
||||||
@ -441,10 +460,9 @@ QPDF::readHSharedObject(BitStream h)
|
|||||||
QTC::TC("qpdf", "QPDF lin nshared_total > nshared_first_page",
|
QTC::TC("qpdf", "QPDF lin nshared_total > nshared_first_page",
|
||||||
(t.nshared_total > t.nshared_first_page) ? 1 : 0);
|
(t.nshared_total > t.nshared_first_page) ? 1 : 0);
|
||||||
|
|
||||||
int nitems = t.nshared_total;
|
|
||||||
std::vector<HSharedObjectEntry>& entries = t.entries;
|
std::vector<HSharedObjectEntry>& entries = t.entries;
|
||||||
entries = std::vector<HSharedObjectEntry>(nitems);
|
entries.clear();
|
||||||
|
int nitems = t.nshared_total;
|
||||||
load_vector_int(h, nitems, entries,
|
load_vector_int(h, nitems, entries,
|
||||||
t.nbits_delta_group_length,
|
t.nbits_delta_group_length,
|
||||||
&HSharedObjectEntry::delta_group_length);
|
&HSharedObjectEntry::delta_group_length);
|
||||||
@ -1466,8 +1484,11 @@ QPDF::calculateLinearizationData(std::map<int, int> const& object_stream_data)
|
|||||||
// validation code can compute them relatively easily given the
|
// validation code can compute them relatively easily given the
|
||||||
// rest of the information.
|
// rest of the information.
|
||||||
|
|
||||||
|
// npages is the size of the existing pages vector, which has been
|
||||||
|
// created by traversing the pages tree, and as such is a
|
||||||
|
// reasonable size.
|
||||||
this->c_linp.npages = npages;
|
this->c_linp.npages = npages;
|
||||||
this->c_page_offset_data.entries = std::vector<CHPageOffsetEntry>(npages);
|
this->c_page_offset_data.entries = std::vector<CHPageOffsetEntry>(npages);
|
||||||
|
|
||||||
// Part 4: open document objects. We don't care about the order.
|
// Part 4: open document objects. We don't care about the order.
|
||||||
|
|
||||||
@ -1861,6 +1882,7 @@ QPDF::calculateHPageOffset(
|
|||||||
|
|
||||||
HPageOffset& ph = this->page_offset_hints;
|
HPageOffset& ph = this->page_offset_hints;
|
||||||
std::vector<HPageOffsetEntry>& phe = ph.entries;
|
std::vector<HPageOffsetEntry>& phe = ph.entries;
|
||||||
|
// npages is the size of the existing pages array.
|
||||||
phe = std::vector<HPageOffsetEntry>(npages);
|
phe = std::vector<HPageOffsetEntry>(npages);
|
||||||
|
|
||||||
for (unsigned int i = 0; i < npages; ++i)
|
for (unsigned int i = 0; i < npages; ++i)
|
||||||
@ -1935,7 +1957,7 @@ QPDF::calculateHSharedObject(
|
|||||||
std::vector<CHSharedObjectEntry>& csoe = cso.entries;
|
std::vector<CHSharedObjectEntry>& csoe = cso.entries;
|
||||||
HSharedObject& so = this->shared_object_hints;
|
HSharedObject& so = this->shared_object_hints;
|
||||||
std::vector<HSharedObjectEntry>& soe = so.entries;
|
std::vector<HSharedObjectEntry>& soe = so.entries;
|
||||||
soe = std::vector<HSharedObjectEntry>(cso.nshared_total);
|
soe.clear();
|
||||||
|
|
||||||
int min_length = outputLengthNextN(
|
int min_length = outputLengthNextN(
|
||||||
csoe[0].object, 1, lengths, obj_renumber);
|
csoe[0].object, 1, lengths, obj_renumber);
|
||||||
@ -1948,8 +1970,13 @@ QPDF::calculateHSharedObject(
|
|||||||
csoe[i].object, 1, lengths, obj_renumber);
|
csoe[i].object, 1, lengths, obj_renumber);
|
||||||
min_length = std::min(min_length, length);
|
min_length = std::min(min_length, length);
|
||||||
max_length = std::max(max_length, length);
|
max_length = std::max(max_length, length);
|
||||||
|
soe.push_back(HSharedObjectEntry());
|
||||||
soe[i].delta_group_length = length;
|
soe[i].delta_group_length = length;
|
||||||
}
|
}
|
||||||
|
if (soe.size() != static_cast<size_t>(cso.nshared_total))
|
||||||
|
{
|
||||||
|
throw std::logic_error("soe has wrong size after initialization");
|
||||||
|
}
|
||||||
|
|
||||||
so.nshared_total = cso.nshared_total;
|
so.nshared_total = cso.nshared_total;
|
||||||
so.nshared_first_page = cso.nshared_first_page;
|
so.nshared_first_page = cso.nshared_first_page;
|
||||||
|
@ -199,7 +199,7 @@ $td->runtest("remove page we don't have",
|
|||||||
show_ntests();
|
show_ntests();
|
||||||
# ----------
|
# ----------
|
||||||
$td->notify("--- Miscellaneous Tests ---");
|
$td->notify("--- Miscellaneous Tests ---");
|
||||||
$n_tests += 69;
|
$n_tests += 70;
|
||||||
|
|
||||||
$td->runtest("qpdf version",
|
$td->runtest("qpdf version",
|
||||||
{$td->COMMAND => "qpdf --version"},
|
{$td->COMMAND => "qpdf --version"},
|
||||||
@ -537,6 +537,13 @@ $td->runtest("bounds check linearization data 2",
|
|||||||
{$td->FILE => "linearization-bounds-2.out",
|
{$td->FILE => "linearization-bounds-2.out",
|
||||||
$td->EXIT_STATUS => 2},
|
$td->EXIT_STATUS => 2},
|
||||||
$td->NORMALIZE_NEWLINES);
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
# Throws logic error, not bad_alloc
|
||||||
|
$td->runtest("sanity check array size",
|
||||||
|
{$td->COMMAND =>
|
||||||
|
"qpdf --check linearization-large-vector-alloc.pdf"},
|
||||||
|
{$td->FILE => "linearization-large-vector-alloc.out",
|
||||||
|
$td->EXIT_STATUS => 2},
|
||||||
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
|
||||||
show_ntests();
|
show_ntests();
|
||||||
# ----------
|
# ----------
|
||||||
|
6
qpdf/qtest/qpdf/linearization-large-vector-alloc.out
Normal file
6
qpdf/qtest/qpdf/linearization-large-vector-alloc.out
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
checking linearization-large-vector-alloc.pdf
|
||||||
|
PDF Version: 1.3
|
||||||
|
File is not encrypted
|
||||||
|
File is linearized
|
||||||
|
WARNING: linearization-large-vector-alloc.pdf (linearization hint stream: object 62 0, file position 1183): attempting to recover stream length
|
||||||
|
overflow reading bit stream
|
BIN
qpdf/qtest/qpdf/linearization-large-vector-alloc.pdf
Normal file
BIN
qpdf/qtest/qpdf/linearization-large-vector-alloc.pdf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user