Skip to content

Commit 5d93f0e

Browse files
committed
fixes
1 parent 11eb0c3 commit 5d93f0e

13 files changed

Lines changed: 279 additions & 253 deletions

stadb/StaDbCodec.cc

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,26 @@ DbReader::DbReader(const uint8_t *data,
173173
void
174174
DbReader::require(size_t count) const
175175
{
176-
if (pos_ + count > size_)
176+
// Subtraction, because a corrupt length can be large enough that pos_ + count
177+
// wraps and lands back inside the section.
178+
if (count > size_ - pos_)
177179
throw DbCorrupt(sta::format("stadb section truncated at offset {}", pos_));
178180
}
179181

182+
size_t
183+
DbReader::getCount(const char *what)
184+
{
185+
uint64_t count = getU64();
186+
// Every element costs at least one byte, so a count larger than the bytes
187+
// left cannot be honest. Checking here means a crafted file is rejected
188+
// before the count reaches a reserve, instead of after the allocator has
189+
// already tried to find room for it.
190+
if (count > remaining())
191+
throw DbCorrupt(sta::format("stadb {} count {} exceeds the {} bytes left "
192+
"in the section", what, count, remaining()));
193+
return static_cast<size_t>(count);
194+
}
195+
180196
uint8_t
181197
DbReader::getU8()
182198
{

stadb/StaDbCodec.hh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,20 @@ private:
5353
std::string msg_;
5454
};
5555

56+
// Enum singletons and pooled objects come back null for an index or name the
57+
// file made up. Every lookup driven by file data goes through here, because the
58+
// result is used as an object and a bad id is indistinguishable from a good one
59+
// until something dereferences it.
60+
template <class T>
61+
inline T *
62+
dbCheck(T *object,
63+
const char *what)
64+
{
65+
if (object == nullptr)
66+
throw DbCorrupt(std::string("stadb ") + what + " could not be resolved");
67+
return object;
68+
}
69+
5670
// A string table id. Distinct from a bare uint32_t so that assigning an
5771
// interned id to a narrower record field fails to compile. Such a truncation
5872
// is invisible until the table grows past the field's range, at which point it
@@ -180,6 +194,10 @@ public:
180194
void getBytes(void *data, size_t size);
181195
// Returns a view into the underlying buffer; valid while the buffer lives.
182196
std::string_view getBlob();
197+
// Reads an element count that is about to size a container. Throws unless the
198+
// section still holds a byte per element, so a small corrupt file cannot ask
199+
// the reader to reserve more memory than the file could possibly describe.
200+
size_t getCount(const char *what);
183201

184202
DbStringTable *strings() const { return strings_; }
185203
size_t offset() const { return pos_; }

stadb/StaDbFile.cc

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ dbUncompress(const uint8_t *stored,
9494
std::vector<uint8_t> &raw)
9595
{
9696
#ifdef ZLIB_FOUND
97+
// Deflate cannot expand by more than 1032:1, so a larger claim is a corrupt
98+
// or crafted header rather than a real payload. Without this the declared
99+
// size alone decides the allocation, and a few hundred bytes on disk can ask
100+
// for every byte of address space.
101+
if (raw_size > stored_size * stadb_max_inflate_ratio)
102+
throw DbCorrupt("stadb section claims more decompressed bytes than deflate "
103+
"can produce");
97104
raw.resize(raw_size);
98105
uLongf out_size = static_cast<uLongf>(raw_size);
99106
int status = uncompress(raw.data(), &out_size, stored,
@@ -230,7 +237,9 @@ DbFileReader::read(std::string_view filename)
230237
uint64_t stored_size = header.getRawU64();
231238
uint64_t raw_size = header.getRawU64();
232239
uint32_t checksum = header.getRawU32();
233-
if (offset + stored_size > file.size())
240+
// Subtraction, because offset + stored_size can wrap and describe a range
241+
// that looks like it fits while pointing outside the file.
242+
if (offset > file.size() || stored_size > file.size() - offset)
234243
throw DbCorrupt("stadb section extends past end of file");
235244
const uint8_t *stored = file.data() + offset;
236245
if (dbChecksum(stored, stored_size) != checksum)
@@ -246,7 +255,7 @@ DbFileReader::read(std::string_view filename)
246255
if (!hasSection(DbSectionId::strings))
247256
throw DbCorrupt("stadb has no string table");
248257
DbReader string_reader = sectionReader(DbSectionId::strings);
249-
size_t string_count = string_reader.getU64();
258+
size_t string_count = string_reader.getCount("string table");
250259
std::vector<std::string> strings;
251260
strings.reserve(string_count);
252261
for (size_t i = 0; i < string_count; i++) {

stadb/StaDbFormat.hh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ enum class DbSectionId : uint32_t {
5656

5757
constexpr uint32_t stadb_flag_compressed = 1 << 0;
5858

59+
// Largest expansion deflate can achieve, used to reject a section header that
60+
// claims a decompressed size its stored bytes could not produce.
61+
constexpr uint64_t stadb_max_inflate_ratio = 1032;
62+
63+
// Sanity bound on a bus port's bit count, which is generated from an index
64+
// range rather than from bytes in the file. Far above any real bus.
65+
constexpr int64_t stadb_max_bus_width = 1 << 20;
66+
5967
////////////////////////////////////////////////////////////////
6068
//
6169
// ABI guard.

stadb/StaDbGraph.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -427,9 +427,9 @@ void
427427
DbGraphReader::readVertices()
428428
{
429429
size_t slew_count = RiseFall::index_count * ap_count_;
430-
uint32_t count = reader_.getU32();
430+
size_t count = reader_.getCount("graph vertex");
431431
vertices_.reserve(count);
432-
for (uint32_t i = 0; i < count; i++) {
432+
for (size_t i = 0; i < count; i++) {
433433
Pin *pin = getPin();
434434
uint8_t flags = reader_.getU8();
435435
bool is_bidirect_drvr = flags & db_vertex_bidirect_drvr;
@@ -467,9 +467,9 @@ DbGraphReader::readVertices()
467467
void
468468
DbGraphReader::readEdges()
469469
{
470-
uint32_t count = reader_.getU32();
470+
size_t count = reader_.getCount("graph edge");
471471
edges_.reserve(count);
472-
for (uint32_t i = 0; i < count; i++) {
472+
for (size_t i = 0; i < count; i++) {
473473
Vertex *from = vertex(reader_.getU32());
474474
Vertex *to = vertex(reader_.getU32());
475475
TimingArcSet *arc_set = getArcSet();

stadb/StaDbReader.cc

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,20 @@
5353

5454
namespace sta {
5555

56+
// Bus bits are generated from the index range instead of being listed, so the
57+
// range is the one count in the format that costs no bytes to inflate. Two int
58+
// fields can otherwise ask for billions of ports out of a handful of bytes.
59+
static void
60+
checkBusRange(int from_index,
61+
int to_index)
62+
{
63+
int64_t from = from_index;
64+
int64_t to = to_index;
65+
int64_t width = (from < to ? to - from : from - to) + 1;
66+
if (width > stadb_max_bus_width)
67+
throw DbCorrupt(sta::format("stadb bus port has {} bits", width));
68+
}
69+
5670
// Rebuilds liberty libraries by replaying the same construction calls that
5771
// LibertyReader makes when parsing a .lib, so derived state ends up identical
5872
// without being stored.
@@ -131,25 +145,25 @@ DbLibertyReader::port(uint32_t id) const
131145
void
132146
DbLibertyReader::readPools()
133147
{
134-
uint32_t axis_count = reader_.getU32();
148+
size_t axis_count = reader_.getCount("liberty axis");
135149
axes_.reserve(axis_count);
136-
for (uint32_t i = 0; i < axis_count; i++) {
150+
for (size_t i = 0; i < axis_count; i++) {
137151
uint8_t variable = reader_.getU8();
138152
if (variable > static_cast<uint8_t>(TableAxisVariable::unknown))
139153
throw DbCorrupt("stadb liberty table axis variable out of range");
140-
uint32_t value_count = reader_.getU32();
154+
size_t value_count = reader_.getCount("liberty axis value");
141155
FloatSeq values;
142156
values.reserve(value_count);
143-
for (uint32_t v = 0; v < value_count; v++)
157+
for (size_t v = 0; v < value_count; v++)
144158
values.push_back(reader_.getF32());
145159
axes_.push_back(std::make_shared<TableAxis>(
146160
static_cast<TableAxisVariable>(variable),
147161
std::move(values)));
148162
}
149163

150-
uint32_t table_count = reader_.getU32();
164+
size_t table_count = reader_.getCount("liberty table");
151165
tables_.reserve(table_count);
152-
for (uint32_t i = 0; i < table_count; i++) {
166+
for (size_t i = 0; i < table_count; i++) {
153167
int order = reader_.getU8();
154168
TableAxisPtr axis1 = axis(reader_.getU32());
155169
TableAxisPtr axis2 = axis(reader_.getU32());
@@ -159,24 +173,24 @@ DbLibertyReader::readPools()
159173
tables_.push_back(std::make_shared<Table>(reader_.getF32()));
160174
break;
161175
case 1: {
162-
uint32_t value_count = reader_.getU32();
176+
size_t value_count = reader_.getCount("liberty table value");
163177
FloatSeq values;
164178
values.reserve(value_count);
165-
for (uint32_t v = 0; v < value_count; v++)
179+
for (size_t v = 0; v < value_count; v++)
166180
values.push_back(reader_.getF32());
167181
tables_.push_back(std::make_shared<Table>(std::move(values), axis1));
168182
break;
169183
}
170184
case 2:
171185
case 3: {
172-
uint32_t row_count = reader_.getU32();
186+
size_t row_count = reader_.getCount("liberty table row");
173187
FloatTable values;
174188
values.reserve(row_count);
175-
for (uint32_t r = 0; r < row_count; r++) {
176-
uint32_t col_count = reader_.getU32();
189+
for (size_t r = 0; r < row_count; r++) {
190+
size_t col_count = reader_.getCount("liberty table column");
177191
FloatSeq row;
178192
row.reserve(col_count);
179-
for (uint32_t c = 0; c < col_count; c++)
193+
for (size_t c = 0; c < col_count; c++)
180194
row.push_back(reader_.getF32());
181195
values.push_back(std::move(row));
182196
}
@@ -322,6 +336,7 @@ DbLibertyReader::readPortStructure(LibertyCell *cell)
322336
std::string bus_dcl_name(reader_.getStr());
323337
BusDcl *bus_dcl = bus_dcl_name.empty()
324338
? nullptr : library_->findBusDcl(bus_dcl_name);
339+
checkBusRange(from_index, to_index);
325340
builder_.makeBusPort(cell, name, from_index, to_index, bus_dcl);
326341
break;
327342
}
@@ -759,9 +774,11 @@ DbNetworkReader::readPort(Cell *cell)
759774
{
760775
std::string name(reader_.getStr());
761776
uint8_t kind = reader_.getU8();
762-
PortDirection *dir = PortDirection::find(reader_.getCstring());
763-
if (dir == nullptr)
764-
throw DbCorrupt("stadb network port direction unknown");
777+
// Not getCstring: it hands back null for the empty string, and the lookup
778+
// below reads it as a C string.
779+
std::string dir_name(reader_.getStr());
780+
PortDirection *dir = dbCheck(PortDirection::find(dir_name.c_str()),
781+
"network port direction");
765782

766783
Port *port = nullptr;
767784
switch (static_cast<DbPortKind>(kind)) {
@@ -771,6 +788,7 @@ DbNetworkReader::readPort(Cell *cell)
771788
case DbPortKind::bus: {
772789
int from_index = reader_.getI32();
773790
int to_index = reader_.getI32();
791+
checkBusRange(from_index, to_index);
774792
port = network_->makeBusPort(cell, name, from_index, to_index);
775793
break;
776794
}
@@ -833,9 +851,9 @@ DbNetworkReader::readLibraries()
833851
void
834852
DbNetworkReader::readCellRefs()
835853
{
836-
uint32_t cell_count = reader_.getU32();
854+
size_t cell_count = reader_.getCount("network cell ref");
837855
cells_.reserve(cell_count);
838-
for (uint32_t i = 0; i < cell_count; i++) {
856+
for (size_t i = 0; i < cell_count; i++) {
839857
std::string lib_name(reader_.getStr());
840858
std::string cell_name(reader_.getStr());
841859
Library *library = network_->findLibrary(lib_name);
@@ -854,9 +872,9 @@ void
854872
DbNetworkReader::readInstances()
855873
{
856874
readCellRefs();
857-
uint32_t inst_count = reader_.getU32();
875+
size_t inst_count = reader_.getCount("network instance");
858876
instances_.reserve(inst_count);
859-
for (uint32_t i = 0; i < inst_count; i++) {
877+
for (size_t i = 0; i < inst_count; i++) {
860878
DbInstanceRec rec;
861879
visit(reader_, rec);
862880
// Parents always precede their children because ids are handed out in
@@ -884,10 +902,10 @@ DbNetworkReader::readInstances()
884902
void
885903
DbNetworkReader::readNets()
886904
{
887-
uint32_t net_count = reader_.getU32();
905+
size_t net_count = reader_.getCount("network net");
888906
std::vector<DbNetRec> recs(net_count);
889907
nets_.reserve(net_count);
890-
for (uint32_t i = 0; i < net_count; i++) {
908+
for (size_t i = 0; i < net_count; i++) {
891909
DbNetRec &rec = recs[i];
892910
visit(reader_, rec);
893911
std::string name(reader_.strings()->string(rec.name));
@@ -910,11 +928,11 @@ DbNetworkReader::readNets()
910928
void
911929
DbNetworkReader::readPins()
912930
{
913-
uint32_t pin_count = reader_.getU32();
931+
size_t pin_count = reader_.getCount("network pin");
914932
std::vector<Pin*> pins(pin_count);
915933
std::vector<DbNetworkId> term_nets(pin_count, db_network_id_null);
916934
std::vector<bool> has_term(pin_count, false);
917-
for (uint32_t i = 0; i < pin_count; i++) {
935+
for (size_t i = 0; i < pin_count; i++) {
918936
DbPinRec rec;
919937
visit(reader_, rec);
920938
Instance *inst = instance(rec.instance);
@@ -933,8 +951,8 @@ DbNetworkReader::readPins()
933951

934952
// Terms come last and in their own order, because a net holds its terms in
935953
// creation order and upstream does not create them alongside their pin.
936-
uint32_t term_count = reader_.getU32();
937-
for (uint32_t i = 0; i < term_count; i++) {
954+
size_t term_count = reader_.getCount("network term");
955+
for (size_t i = 0; i < term_count; i++) {
938956
uint32_t pin_id = reader_.getU32();
939957
if (pin_id >= pin_count || !has_term[pin_id])
940958
throw DbCorrupt("stadb network term pin index out of range");

0 commit comments

Comments
 (0)