1 /*
2 * Copyright 2023 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "src/codec/SkTiffUtility.h"
9
10 #include "include/core/SkData.h"
11 #include "src/codec/SkCodecPriv.h"
12
13 #include <cstddef>
14 #include <utility>
15
16 namespace SkTiff {
17
IsValidType(uint16_t type)18 bool ImageFileDirectory::IsValidType(uint16_t type) { return type >= 1 && type <= 12; }
19
BytesForType(uint16_t type)20 size_t ImageFileDirectory::BytesForType(uint16_t type) {
21 switch (type) {
22 case kTypeUnsignedByte:
23 return 1;
24 case kTypeAsciiString:
25 return 1;
26 case kTypeUnsignedShort:
27 return kSizeShort;
28 case kTypeUnsignedLong:
29 return kSizeLong;
30 case kTypeUnsignedRational:
31 return 8;
32 case kTypeSignedByte:
33 return 1;
34 case kTypeUndefined:
35 return 1;
36 case kTypeSignedShort:
37 return kSizeShort;
38 case kTypeSignedLong:
39 return kSizeLong;
40 case kTypeSignedRational:
41 return 8;
42 case kTypeSingleFloat:
43 return 4;
44 case kTypeDoubleFloat:
45 return 8;
46 }
47 return 0;
48 }
49
50 // Helper function for computing the address of an entry.
get_entry_address(const SkData * data,uint32_t ifdOffset,uint16_t entryIndex)51 static const uint8_t* get_entry_address(const SkData* data,
52 uint32_t ifdOffset,
53 uint16_t entryIndex) {
54 return data->bytes() + // Base address
55 ifdOffset + // IFD offset
56 kSizeShort + // IFD number of entries
57 kSizeEntry * entryIndex; // Entries
58 }
59
60 // Return true if the IFD starting at |ifdOffset| contains valid number of entries (that doesn't
61 // overrun |data|).
validate_ifd(const SkData * data,bool littleEndian,uint32_t ifdOffset,bool allowTruncated,uint16_t * outNumEntries,uint32_t * outNextIfdOffset)62 static bool validate_ifd(const SkData* data,
63 bool littleEndian,
64 uint32_t ifdOffset,
65 bool allowTruncated,
66 uint16_t* outNumEntries,
67 uint32_t* outNextIfdOffset) {
68 const uint8_t* dataCurrent = data->bytes();
69 size_t dataSize = data->size();
70
71 // Seek to the IFD offset.
72 if (dataSize < ifdOffset) {
73 SkCodecPrintf("IFD offset is too large.\n");
74 return false;
75 }
76 dataCurrent += ifdOffset;
77 dataSize -= ifdOffset;
78
79 // Read the number of entries.
80 if (dataSize < kSizeShort) {
81 SkCodecPrintf("Insufficient space to store number of entries.\n");
82 return false;
83 }
84 uint16_t numEntries = get_endian_short(dataCurrent, littleEndian);
85 dataCurrent += kSizeShort;
86 dataSize -= kSizeShort;
87
88 // Check that there is enough space for all entries.
89 if (dataSize < kSizeEntry * numEntries) {
90 SkCodecPrintf("Insufficient space (%u) to store all %u entries.\n",
91 static_cast<uint32_t>(data->size()),
92 numEntries);
93 if (allowTruncated) {
94 // Set the number of entries to the number of entries that can be fully read, and set
95 // the next IFD offset to 0 (indicating that there is no next IFD).
96 *outNumEntries = dataSize / kSizeEntry;
97 *outNextIfdOffset = 0;
98 return true;
99 }
100 return false;
101 }
102
103 // Save the number of entries.
104 *outNumEntries = numEntries;
105
106 // Seek past the entries.
107 dataCurrent += kSizeEntry * numEntries;
108 dataSize -= kSizeEntry * numEntries;
109
110 // Read the next IFD offset.
111 if (dataSize < kSizeLong) {
112 SkCodecPrintf("Insufficient space to store next IFD offset.\n");
113 if (allowTruncated) {
114 // Set the next IFD offset to 0 (indicating that there is no next IFD).
115 *outNextIfdOffset = 0;
116 return true;
117 }
118 return false;
119 }
120
121 // Save the next IFD offset.
122 *outNextIfdOffset = get_endian_int(dataCurrent, littleEndian);
123 return true;
124 }
125
ParseHeader(const SkData * data,bool * outLittleEndian,uint32_t * outIfdOffset)126 bool ImageFileDirectory::ParseHeader(const SkData* data,
127 bool* outLittleEndian,
128 uint32_t* outIfdOffset) {
129 // Read the endianness (4 bytes) and IFD offset (4 bytes).
130 if (data->size() < 8) {
131 SkCodecPrintf("Tiff header must be at least 8 bytes.\n");
132 return false;
133 }
134 if (!is_valid_endian_marker(data->bytes(), outLittleEndian)) {
135 SkCodecPrintf("Tiff header had invalid endian marker 0x%x,0x%x,0x%x,0x%x.\n",
136 data->bytes()[0],
137 data->bytes()[1],
138 data->bytes()[2],
139 data->bytes()[3]);
140 return false;
141 }
142 *outIfdOffset = get_endian_int(data->bytes() + 4, *outLittleEndian);
143 return true;
144 }
145
MakeFromOffset(sk_sp<SkData> data,bool littleEndian,uint32_t ifdOffset,bool allowTruncated)146 std::unique_ptr<ImageFileDirectory> ImageFileDirectory::MakeFromOffset(sk_sp<SkData> data,
147 bool littleEndian,
148 uint32_t ifdOffset,
149 bool allowTruncated) {
150 uint16_t numEntries = 0;
151 uint32_t nextOffset = 0;
152 if (!validate_ifd(
153 data.get(), littleEndian, ifdOffset, allowTruncated, &numEntries, &nextOffset)) {
154 SkCodecPrintf("Failed to validate IFD.\n");
155 return nullptr;
156 }
157 return std::unique_ptr<ImageFileDirectory>(new ImageFileDirectory(
158 std::move(data), littleEndian, ifdOffset, numEntries, nextOffset));
159 }
160
ImageFileDirectory(sk_sp<SkData> data,bool littleEndian,uint32_t offset,uint16_t numEntries,uint32_t nextIfdOffset)161 ImageFileDirectory::ImageFileDirectory(sk_sp<SkData> data,
162 bool littleEndian,
163 uint32_t offset,
164 uint16_t numEntries,
165 uint32_t nextIfdOffset)
166 : fData(std::move(data))
167 , fLittleEndian(littleEndian)
168 , fOffset(offset)
169 , fNumEntries(numEntries)
170 , fNextIfdOffset(nextIfdOffset) {}
171
getEntryTag(uint16_t entryIndex) const172 uint16_t ImageFileDirectory::getEntryTag(uint16_t entryIndex) const {
173 const uint8_t* entry = get_entry_address(fData.get(), fOffset, entryIndex);
174 return get_endian_short(entry, fLittleEndian);
175 }
176
getEntryRawData(uint16_t entryIndex,uint16_t * outTag,uint16_t * outType,uint32_t * outCount,const uint8_t ** outData,size_t * outDataSize) const177 bool ImageFileDirectory::getEntryRawData(uint16_t entryIndex,
178 uint16_t* outTag,
179 uint16_t* outType,
180 uint32_t* outCount,
181 const uint8_t** outData,
182 size_t* outDataSize) const {
183 const uint8_t* entry = get_entry_address(fData.get(), fOffset, entryIndex);
184
185 // Read the tag
186 const uint16_t tag = get_endian_short(entry, fLittleEndian);
187 entry += kSizeShort;
188
189 // Read the type.
190 const uint16_t type = get_endian_short(entry, fLittleEndian);
191 entry += kSizeShort;
192 if (!IsValidType(type)) {
193 return false;
194 }
195
196 // Read the count.
197 const uint32_t count = get_endian_int(entry, fLittleEndian);
198 entry += kSizeLong;
199
200 // If the entry fits in the remaining 4 bytes, use that.
201 const size_t entryDataBytes = BytesForType(type) * count;
202 const uint8_t* entryData = nullptr;
203 if (entryDataBytes <= kSizeLong) {
204 entryData = entry;
205 } else {
206 // Otherwise, the next 4 bytes specify an offset where the data can be found.
207 const uint32_t entryDataOffset = get_endian_int(entry, fLittleEndian);
208 if (fData->size() < entryDataOffset || fData->size() - entryDataOffset < entryDataBytes) {
209 return false;
210 }
211 entryData = fData->bytes() + entryDataOffset;
212 }
213
214 if (outTag) *outTag = tag;
215 if (outType) *outType = type;
216 if (outCount) *outCount = count;
217 if (outData) *outData = entryData;
218 if (outDataSize) *outDataSize = entryDataBytes;
219 return true;
220 }
221
getEntryUndefinedData(uint16_t entryIndex) const222 sk_sp<SkData> ImageFileDirectory::getEntryUndefinedData(uint16_t entryIndex) const {
223 uint16_t type = 0;
224 uint32_t count = 0;
225 const uint8_t* data = nullptr;
226 size_t size = 0;
227 if (!getEntryRawData(entryIndex, nullptr, &type, &count, &data, &size)) {
228 return nullptr;
229 }
230 if (type != kTypeUndefined) {
231 return nullptr;
232 }
233 return SkData::MakeSubset(fData.get(), data - fData->bytes(), size);
234 }
235
getEntryValuesGeneric(uint16_t entryIndex,uint16_t type,uint32_t count,void * values) const236 bool ImageFileDirectory::getEntryValuesGeneric(uint16_t entryIndex,
237 uint16_t type,
238 uint32_t count,
239 void* values) const {
240 uint16_t entryType = 0;
241 uint32_t entryCount = 0;
242 const uint8_t* entryData = nullptr;
243 if (!getEntryRawData(entryIndex, nullptr, &entryType, &entryCount, &entryData, nullptr)) {
244 return false;
245 }
246 if (type != entryType) {
247 return false;
248 }
249 if (count != entryCount) {
250 return false;
251 }
252 for (uint32_t i = 0; i < count; ++i) {
253 const uint8_t* data = entryData + i * BytesForType(kTypeUnsignedLong);
254 switch (type) {
255 case kTypeUnsignedShort:
256 reinterpret_cast<uint16_t*>(values)[i] = get_endian_short(data, fLittleEndian);
257 break;
258 case kTypeUnsignedLong:
259 reinterpret_cast<uint32_t*>(values)[i] = get_endian_int(data, fLittleEndian);
260 break;
261 case kTypeSignedRational: {
262 uint32_t numerator = get_endian_int(data, fLittleEndian);
263 uint32_t denominator = get_endian_int(data + kSizeLong, fLittleEndian);
264 if (denominator == 0) {
265 // The TIFF specification does not indicate a behavior when the denominator is
266 // zero. The behavior of returning zero for a denominator of zero is a
267 // preservation of the behavior introduced in https://crrev.com/767874.
268 reinterpret_cast<float*>(values)[i] = 0;
269 } else {
270 reinterpret_cast<float*>(values)[i] =
271 numerator / static_cast<float>(denominator);
272 }
273 break;
274 }
275 case kTypeUnsignedRational: {
276 uint32_t numerator = get_endian_int(data, fLittleEndian);
277 uint32_t denominator = get_endian_int(data + kSizeLong, fLittleEndian);
278 if (denominator == 0) {
279 // See comments in kTypeSignedRational.
280 reinterpret_cast<float*>(values)[i] = 0.f;
281 } else {
282 reinterpret_cast<float*>(values)[i] =
283 numerator / static_cast<float>(denominator);
284 }
285 break;
286 }
287 default:
288 SkCodecPrintf("Unsupported type %u\n", type);
289 return false;
290 break;
291 }
292 }
293 return true;
294 }
295
296 } // namespace SkTiff
297