Security fixes to woff2 decompression
This patch addresses the security and other problems found by agl in his review of a previous version of the code.
This commit is contained in:
+21
-25
@@ -66,11 +66,14 @@ const size_t kWoff2EntrySize = 20;
|
|||||||
|
|
||||||
const size_t kLzmaHeaderSize = 13;
|
const size_t kLzmaHeaderSize = 13;
|
||||||
|
|
||||||
|
// Compression type values common to both short and long formats
|
||||||
const uint32_t kCompressionTypeMask = 0xf;
|
const uint32_t kCompressionTypeMask = 0xf;
|
||||||
const uint32_t kCompressionTypeNone = 0;
|
const uint32_t kCompressionTypeNone = 0;
|
||||||
const uint32_t kCompressionTypeGzip = 1;
|
const uint32_t kCompressionTypeGzip = 1;
|
||||||
const uint32_t kCompressionTypeLzma = 2;
|
const uint32_t kCompressionTypeLzma = 2;
|
||||||
// agl: This doesn't seem to match the value in the spec, which is 16.
|
|
||||||
|
// This is a special value for the short format only, as described in
|
||||||
|
// "Design for compressed header format" in draft doc.
|
||||||
const uint32_t kShortFlagsContinue = 3;
|
const uint32_t kShortFlagsContinue = 3;
|
||||||
|
|
||||||
struct Point {
|
struct Point {
|
||||||
@@ -174,10 +177,7 @@ size_t Store16(uint8_t* dst, size_t offset, int x) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int WithSign(int flag, int baseval) {
|
int WithSign(int flag, int baseval) {
|
||||||
// agl: it isn't generally possible to negate an arbitary integer. (For
|
// Precondition: 0 <= baseval < 65536 (to avoid integer overflow)
|
||||||
// example, -2137483648 cannot be negated.) However, I believe that the range
|
|
||||||
// is 0 <= baseval < 65536 in which case it's safe. If you agree then there
|
|
||||||
// should be a comment documenting the precondition of this function.
|
|
||||||
return (flag & 1) ? baseval : -baseval;
|
return (flag & 1) ? baseval : -baseval;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ bool TripletDecode(const uint8_t* flags_in, const uint8_t* in, size_t in_size,
|
|||||||
(in[triplet_index + 2] << 8) + in[triplet_index + 3]);
|
(in[triplet_index + 2] << 8) + in[triplet_index + 3]);
|
||||||
}
|
}
|
||||||
triplet_index += n_data_bytes;
|
triplet_index += n_data_bytes;
|
||||||
// agl: these additions can overflow, although I'm not sure that we care.
|
// Possible overflow but coordinate values are not security sensitive
|
||||||
x += dx;
|
x += dx;
|
||||||
y += dy;
|
y += dy;
|
||||||
result->push_back(Point());
|
result->push_back(Point());
|
||||||
@@ -315,10 +315,10 @@ bool StorePoints(const std::vector<Point>& points,
|
|||||||
}
|
}
|
||||||
dst[flag_offset++] = repeat_count;
|
dst[flag_offset++] = repeat_count;
|
||||||
}
|
}
|
||||||
// agl: overflow checks can't have multiple additions in them.
|
unsigned int xy_bytes = x_bytes + y_bytes;
|
||||||
if (flag_offset + x_bytes < flag_offset ||
|
if (xy_bytes < x_bytes ||
|
||||||
flag_offset + x_bytes + y_bytes < flag_offset + x_bytes ||
|
flag_offset + xy_bytes < flag_offset ||
|
||||||
flag_offset + x_bytes + y_bytes > dst_size) {
|
flag_offset + xy_bytes > dst_size) {
|
||||||
return OTS_FAILURE();
|
return OTS_FAILURE();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,8 +333,7 @@ bool StorePoints(const std::vector<Point>& points,
|
|||||||
} else if (dx > -256 && dx < 256) {
|
} else if (dx > -256 && dx < 256) {
|
||||||
dst[x_offset++] = std::abs(dx);
|
dst[x_offset++] = std::abs(dx);
|
||||||
} else {
|
} else {
|
||||||
// agl: this can go wrong if dx doesn't fit in an int16_t, but it's not
|
// will always fit for valid input, but overflow is harmless
|
||||||
// clear if that matters.
|
|
||||||
x_offset = Store16(dst, x_offset, dx);
|
x_offset = Store16(dst, x_offset, dx);
|
||||||
}
|
}
|
||||||
last_x += dx;
|
last_x += dx;
|
||||||
@@ -381,8 +380,10 @@ bool ProcessBboxStream(ots::Buffer* bbox_stream, unsigned int n_glyphs,
|
|||||||
const std::vector<uint32_t>& loca_values, uint8_t* glyf_buf,
|
const std::vector<uint32_t>& loca_values, uint8_t* glyf_buf,
|
||||||
size_t glyf_buf_length) {
|
size_t glyf_buf_length) {
|
||||||
const uint8_t* buf = bbox_stream->buffer();
|
const uint8_t* buf = bbox_stream->buffer();
|
||||||
// agl: I belive that n_glyphs is < 65536, making the next line safe. But a
|
if (n_glyphs >= 65536 || loca_values.size() != n_glyphs + 1) {
|
||||||
// comment and/or assert is needed to document that.
|
return OTS_FAILURE();
|
||||||
|
}
|
||||||
|
// Safe because n_glyphs is bounded
|
||||||
unsigned int bitmap_length = ((n_glyphs + 31) >> 5) << 2;
|
unsigned int bitmap_length = ((n_glyphs + 31) >> 5) << 2;
|
||||||
if (!bbox_stream->Skip(bitmap_length)) {
|
if (!bbox_stream->Skip(bitmap_length)) {
|
||||||
return OTS_FAILURE();
|
return OTS_FAILURE();
|
||||||
@@ -390,8 +391,6 @@ bool ProcessBboxStream(ots::Buffer* bbox_stream, unsigned int n_glyphs,
|
|||||||
for (unsigned int i = 0; i < n_glyphs; ++i) {
|
for (unsigned int i = 0; i < n_glyphs; ++i) {
|
||||||
if (buf[i >> 3] & (0x80 >> (i & 7))) {
|
if (buf[i >> 3] & (0x80 >> (i & 7))) {
|
||||||
uint32_t loca_offset = loca_values[i];
|
uint32_t loca_offset = loca_values[i];
|
||||||
// agl: again, I believe that this is safe, but the fact that loca_values
|
|
||||||
// has n_glyphs+1 elements should be documented.
|
|
||||||
if (loca_values[i + 1] - loca_offset < kEndPtsOfContoursOffset) {
|
if (loca_values[i + 1] - loca_offset < kEndPtsOfContoursOffset) {
|
||||||
return OTS_FAILURE();
|
return OTS_FAILURE();
|
||||||
}
|
}
|
||||||
@@ -490,11 +489,10 @@ bool ReconstructGlyf(const uint8_t* data, size_t data_size,
|
|||||||
return OTS_FAILURE();
|
return OTS_FAILURE();
|
||||||
}
|
}
|
||||||
unsigned int offset = (2 + kNumSubStreams) * 4;
|
unsigned int offset = (2 + kNumSubStreams) * 4;
|
||||||
// agl: data_size must be >= offset, but this condition isn't established
|
|
||||||
// initially.
|
|
||||||
if (offset > data_size) {
|
if (offset > data_size) {
|
||||||
return OTS_FAILURE();
|
return OTS_FAILURE();
|
||||||
}
|
}
|
||||||
|
// Invariant from here on: data_size >= offset
|
||||||
for (int i = 0; i < kNumSubStreams; ++i) {
|
for (int i = 0; i < kNumSubStreams; ++i) {
|
||||||
uint32_t substream_size;
|
uint32_t substream_size;
|
||||||
if (!file.ReadU32(&substream_size)) {
|
if (!file.ReadU32(&substream_size)) {
|
||||||
@@ -588,7 +586,9 @@ bool ReconstructGlyf(const uint8_t* data, size_t data_size,
|
|||||||
int end_point = -1;
|
int end_point = -1;
|
||||||
for (unsigned int contour_ix = 0; contour_ix < n_contours; ++contour_ix) {
|
for (unsigned int contour_ix = 0; contour_ix < n_contours; ++contour_ix) {
|
||||||
end_point += n_points_vec[contour_ix];
|
end_point += n_points_vec[contour_ix];
|
||||||
// agl: this fails if end_point > 65535, does that matter?
|
if (end_point >= 65536) {
|
||||||
|
return OTS_FAILURE();
|
||||||
|
}
|
||||||
offset = Store16(glyf_dst, offset, end_point);
|
offset = Store16(glyf_dst, offset, end_point);
|
||||||
}
|
}
|
||||||
if (!flag_stream.Skip(flag_size)) {
|
if (!flag_stream.Skip(flag_size)) {
|
||||||
@@ -685,10 +685,7 @@ bool ReconstructTransformed(const std::vector<Table>& tables, uint32_t tag,
|
|||||||
uint32_t ComputeChecksum(const uint8_t* buf, size_t size) {
|
uint32_t ComputeChecksum(const uint8_t* buf, size_t size) {
|
||||||
uint32_t checksum = 0;
|
uint32_t checksum = 0;
|
||||||
for (size_t i = 0; i < size; i += 4) {
|
for (size_t i = 0; i < size; i += 4) {
|
||||||
// We assume the addition is mod 2^32. This is a pretty safe assumption,
|
// We assume the addition is mod 2^32, which is valid because unsigned
|
||||||
// but technically it's undefined behavior.
|
|
||||||
// agl: in reference to the comment above: when using uint32_t, the fact
|
|
||||||
// that addition is mod 2^32 is well defined.
|
|
||||||
checksum += (buf[i] << 24) | (buf[i + 1] << 16) |
|
checksum += (buf[i] << 24) | (buf[i + 1] << 16) |
|
||||||
(buf[i + 2] << 8) | buf[i + 3];
|
(buf[i + 2] << 8) | buf[i + 3];
|
||||||
}
|
}
|
||||||
@@ -726,8 +723,7 @@ bool Woff2Uncompress(uint8_t* dst_buf, size_t dst_size,
|
|||||||
uLongf uncompressed_length = dst_size;
|
uLongf uncompressed_length = dst_size;
|
||||||
int r = uncompress(reinterpret_cast<Bytef *>(dst_buf), &uncompressed_length,
|
int r = uncompress(reinterpret_cast<Bytef *>(dst_buf), &uncompressed_length,
|
||||||
src_buf, src_size);
|
src_buf, src_size);
|
||||||
// agl: I think src_size should be dst_size in the next line.
|
if (r != Z_OK || uncompressed_length != dst_size) {
|
||||||
if (r != Z_OK || uncompressed_length != src_size) {
|
|
||||||
return OTS_FAILURE();
|
return OTS_FAILURE();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user