data/ComposeImageLocation.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "ComposeImageLocation.h" | ||
| 2 | |||
| 3 | namespace ComposeImageLocation { | ||
| 4 | |||
| 5 | namespace { | ||
| 6 | |||
| 7 | class Tiff { | ||
| 8 | public: | ||
| 9 | 16 | Tiff(QByteArray &data, qsizetype start, qsizetype size) : m_data(data), m_start(start), m_size(size) {} | |
| 10 | 16 | bool readHeader() { | |
| 11 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 16 times.
|
16 | if (m_size < 8) return false; |
| 12 | 16 | const char a = at(0), b = at(1); | |
| 13 |
2/4✓ Branch 6 → 7 taken 16 times.
✗ Branch 6 → 9 not taken.
✓ Branch 7 → 8 taken 16 times.
✗ Branch 7 → 9 not taken.
|
16 | if (a == 'I' && b == 'I') m_little = true; |
| 14 | ✗ | else if (a == 'M' && b == 'M') m_little = false; | |
| 15 | ✗ | else return false; | |
| 16 | 16 | return u16(2) == 42; | |
| 17 | } | ||
| 18 | 60 | bool inside(qint64 offset, qint64 length) const { | |
| 19 |
5/8✓ Branch 2 → 3 taken 60 times.
✗ Branch 2 → 7 not taken.
✓ Branch 3 → 4 taken 60 times.
✗ Branch 3 → 7 not taken.
✓ Branch 4 → 5 taken 58 times.
✓ Branch 4 → 7 taken 2 times.
✓ Branch 5 → 6 taken 58 times.
✗ Branch 5 → 7 not taken.
|
60 | return offset >= 0 && length >= 0 && offset <= m_size && length <= m_size - offset; |
| 20 | } | ||
| 21 | 90 | quint16 u16(qint64 offset) const { | |
| 22 | 90 | const quint8 x = quint8(at(offset)), y = quint8(at(offset + 1)); | |
| 23 |
1/2✓ Branch 4 → 5 taken 90 times.
✗ Branch 4 → 6 not taken.
|
90 | return m_little ? quint16(x | (y << 8)) : quint16((x << 8) | y); |
| 24 | } | ||
| 25 | 48 | quint32 u32(qint64 offset) const { | |
| 26 | 48 | const quint32 x = quint8(at(offset)), y = quint8(at(offset + 1)), z = quint8(at(offset + 2)), | |
| 27 | 48 | w = quint8(at(offset + 3)); | |
| 28 |
1/2✓ Branch 6 → 7 taken 48 times.
✗ Branch 6 → 8 not taken.
|
48 | return m_little ? (x | (y << 8) | (z << 16) | (w << 24)) : ((x << 24) | (y << 16) | (z << 8) | w); |
| 29 | } | ||
| 30 | 4 | void setU16(qint64 offset, quint16 value) { | |
| 31 |
1/2✓ Branch 2 → 3 taken 4 times.
✗ Branch 2 → 5 not taken.
|
4 | if (m_little) { |
| 32 | 4 | put(offset, char(value & 0xff)); | |
| 33 | 4 | put(offset + 1, char(value >> 8)); | |
| 34 | } else { | ||
| 35 | ✗ | put(offset, char(value >> 8)); | |
| 36 | ✗ | put(offset + 1, char(value & 0xff)); | |
| 37 | } | ||
| 38 | 4 | } | |
| 39 | 8 | void zero(qint64 offset, qint64 length) { | |
| 40 |
2/2✓ Branch 5 → 3 taken 192 times.
✓ Branch 5 → 6 taken 8 times.
|
200 | for (qint64 i = 0; i < length; ++i) put(offset + i, 0); |
| 41 | 8 | } | |
| 42 | |||
| 43 | private: | ||
| 44 | QByteArray &m_data; | ||
| 45 | qsizetype m_start; | ||
| 46 | qsizetype m_size; | ||
| 47 | bool m_little = true; | ||
| 48 | 404 | char at(qint64 offset) const { return m_data.at(m_start + offset); } | |
| 49 | 200 | void put(qint64 offset, char value) { m_data[m_start + offset] = value; } | |
| 50 | }; | ||
| 51 | |||
| 52 | 8 | int typeSize(quint16 type) { | |
| 53 |
2/5✓ Branch 2 → 3 taken 4 times.
✗ Branch 2 → 4 not taken.
✗ Branch 2 → 5 not taken.
✓ Branch 2 → 6 taken 4 times.
✗ Branch 2 → 7 not taken.
|
8 | switch (type) { |
| 54 | 4 | case 1: case 2: case 6: case 7: return 1; | |
| 55 | ✗ | case 3: case 8: return 2; | |
| 56 | ✗ | case 4: case 9: case 11: return 4; | |
| 57 | 4 | case 5: case 10: case 12: return 8; | |
| 58 | ✗ | default: return 0; | |
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | // Empties every GPS IFD that IFD0 points to (a hostile file can name more | ||
| 63 | // than one) of one EXIF payload in place; true when one held data. | ||
| 64 | 16 | bool clearGps(QByteArray &data, qsizetype tiffStart, qsizetype tiffSize) { | |
| 65 | 16 | Tiff tiff(data, tiffStart, tiffSize); | |
| 66 |
2/3✓ Branch 3 → 4 taken 16 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 16 times.
|
16 | if (!tiff.readHeader()) return false; |
| 67 |
1/1✓ Branch 6 → 7 taken 16 times.
|
16 | const quint32 ifd0 = tiff.u32(4); |
| 68 |
1/2✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 16 times.
|
16 | if (!tiff.inside(ifd0, 2)) return false; |
| 69 |
1/1✓ Branch 10 → 11 taken 16 times.
|
16 | const quint16 count = tiff.u16(ifd0); |
| 70 |
3/6✓ Branch 11 → 12 taken 16 times.
✗ Branch 11 → 14 not taken.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 16 times.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 16 times.
|
16 | if (count > 512 || !tiff.inside(ifd0 + 2, qint64(count) * 12)) return false; |
| 71 | 16 | bool cleared = false; | |
| 72 |
2/2✓ Branch 50 → 19 taken 32 times.
✓ Branch 50 → 51 taken 16 times.
|
48 | for (quint16 i = 0; i < count; ++i) { |
| 73 | 32 | const qint64 entry = ifd0 + 2 + qint64(i) * 12; | |
| 74 |
3/3✓ Branch 19 → 20 taken 32 times.
✓ Branch 20 → 21 taken 12 times.
✓ Branch 20 → 22 taken 20 times.
|
32 | if (tiff.u16(entry) != 0x8825) continue; |
| 75 |
1/1✓ Branch 22 → 23 taken 20 times.
|
20 | const quint32 gps = tiff.u32(entry + 8); |
| 76 |
2/2✓ Branch 24 → 25 taken 2 times.
✓ Branch 24 → 26 taken 18 times.
|
20 | if (!tiff.inside(gps, 2)) continue; |
| 77 |
1/1✓ Branch 26 → 27 taken 18 times.
|
18 | const quint16 gpsCount = tiff.u16(gps); |
| 78 |
6/8✓ Branch 27 → 28 taken 4 times.
✓ Branch 27 → 31 taken 14 times.
✓ Branch 28 → 29 taken 4 times.
✗ Branch 28 → 31 not taken.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 4 times.
✓ Branch 33 → 34 taken 14 times.
✓ Branch 33 → 35 taken 4 times.
|
18 | if (gpsCount == 0 || gpsCount > 256 || !tiff.inside(gps + 2, qint64(gpsCount) * 12)) continue; |
| 79 |
2/2✓ Branch 45 → 36 taken 8 times.
✓ Branch 45 → 46 taken 4 times.
|
12 | for (quint16 j = 0; j < gpsCount; ++j) { |
| 80 | 8 | const qint64 gpsEntry = gps + 2 + qint64(j) * 12; | |
| 81 |
2/2✓ Branch 36 → 37 taken 8 times.
✓ Branch 38 → 39 taken 8 times.
|
8 | const qint64 bytes = qint64(typeSize(tiff.u16(gpsEntry + 2))) * tiff.u32(gpsEntry + 4); |
| 82 |
2/2✓ Branch 39 → 40 taken 4 times.
✓ Branch 39 → 44 taken 4 times.
|
8 | if (bytes > 4) { |
| 83 |
1/1✓ Branch 40 → 41 taken 4 times.
|
4 | const quint32 valueOffset = tiff.u32(gpsEntry + 8); |
| 84 |
2/3✓ Branch 42 → 43 taken 4 times.
✗ Branch 42 → 44 not taken.
✓ Branch 43 → 44 taken 4 times.
|
4 | if (tiff.inside(valueOffset, bytes)) tiff.zero(valueOffset, bytes); |
| 85 | } | ||
| 86 | } | ||
| 87 |
1/1✓ Branch 46 → 47 taken 4 times.
|
4 | tiff.zero(gps + 2, qint64(gpsCount) * 12); |
| 88 |
1/1✓ Branch 47 → 48 taken 4 times.
|
4 | tiff.setU16(gps, 0); |
| 89 | 4 | cleared = true; | |
| 90 | } | ||
| 91 | 16 | return cleared; | |
| 92 | } | ||
| 93 | |||
| 94 | 5 | bool xmpWithLocation(const QByteArray &payload) { | |
| 95 |
1/6✗ Branch 4 → 5 not taken.
✓ Branch 4 → 11 taken 5 times.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 11 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
5 | return payload.contains("GPSLatitude") || payload.contains("GPSLongitude") || payload.contains("exif:GPS"); |
| 96 | } | ||
| 97 | |||
| 98 | } // namespace | ||
| 99 | |||
| 100 | namespace { | ||
| 101 | 30 | Result stripOnce(const QByteArray &input) { | |
| 102 | 30 | Result result; | |
| 103 | 30 | result.data = input; | |
| 104 |
7/10✓ Branch 4 → 5 taken 30 times.
✗ Branch 4 → 11 not taken.
✓ Branch 6 → 7 taken 30 times.
✗ Branch 6 → 11 not taken.
✓ Branch 8 → 9 taken 20 times.
✓ Branch 8 → 11 taken 10 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 20 times.
✓ Branch 13 → 14 taken 10 times.
✓ Branch 13 → 15 taken 20 times.
|
30 | if (input.size() < 4 || input.size() > MaxBytes || quint8(input.at(0)) != 0xFF || quint8(input.at(1)) != 0xD8) |
| 105 | 10 | return result; | |
| 106 | 20 | result.jpeg = true; | |
| 107 | 20 | QByteArray &data = result.data; | |
| 108 | 20 | qsizetype position = 2; | |
| 109 | // Where the current run of fill bytes began: after a segment is removed | ||
| 110 | // the fill bytes before it meet the bytes after it, so parsing resumes | ||
| 111 | // there — the next pass would otherwise read them differently. | ||
| 112 | 20 | qsizetype runStart = position; | |
| 113 |
1/2✓ Branch 82 → 16 taken 93 times.
✗ Branch 82 → 83 not taken.
|
93 | while (position + 4 <= data.size()) { |
| 114 |
1/2✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 93 times.
|
113 | if (quint8(data.at(position)) != 0xFF) break; // not a marker: stop, leave the rest |
| 115 | 93 | quint8 marker = quint8(data.at(position + 1)); | |
| 116 |
2/2✓ Branch 20 → 21 taken 36 times.
✓ Branch 20 → 22 taken 57 times.
|
93 | if (marker == 0xFF) { // fill byte |
| 117 | 36 | ++position; | |
| 118 | 41 | continue; | |
| 119 | } | ||
| 120 |
5/8✓ Branch 22 → 23 taken 57 times.
✗ Branch 22 → 26 not taken.
✓ Branch 23 → 24 taken 57 times.
✗ Branch 23 → 26 not taken.
✓ Branch 24 → 25 taken 47 times.
✓ Branch 24 → 27 taken 10 times.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 47 times.
|
57 | if (marker == 0xD8 || marker == 0x01 || (marker >= 0xD0 && marker <= 0xD7)) { |
| 121 | ✗ | position += 2; | |
| 122 | ✗ | runStart = position; | |
| 123 | ✗ | continue; | |
| 124 | } | ||
| 125 |
3/4✓ Branch 27 → 28 taken 57 times.
✗ Branch 27 → 79 not taken.
✓ Branch 28 → 29 taken 39 times.
✓ Branch 28 → 79 taken 18 times.
|
57 | if (marker == 0xD9 || marker == 0xDA) break; // end of image / start of scan data |
| 126 | 39 | const qsizetype length = (quint8(data.at(position + 2)) << 8) | quint8(data.at(position + 3)); | |
| 127 |
5/6✓ Branch 31 → 32 taken 39 times.
✗ Branch 31 → 34 not taken.
✓ Branch 33 → 34 taken 2 times.
✓ Branch 33 → 35 taken 37 times.
✓ Branch 36 → 37 taken 2 times.
✓ Branch 36 → 38 taken 37 times.
|
39 | if (length < 2 || position + 2 + length > data.size()) break; |
| 128 | 37 | const qsizetype payload = position + 4, payloadSize = length - 2; | |
| 129 |
2/2✓ Branch 38 → 39 taken 21 times.
✓ Branch 38 → 78 taken 16 times.
|
37 | if (marker == 0xE1) { |
| 130 |
1/1✓ Branch 40 → 41 taken 21 times.
|
21 | const QByteArray head = data.mid(payload, qMin<qsizetype>(payloadSize, 40)); |
| 131 |
7/10✓ Branch 45 → 46 taken 16 times.
✓ Branch 45 → 48 taken 5 times.
✓ Branch 46 → 47 taken 16 times.
✗ Branch 46 → 48 not taken.
✓ Branch 49 → 50 taken 21 times.
✗ Branch 49 → 51 not taken.
✓ Branch 51 → 52 taken 21 times.
✗ Branch 51 → 53 not taken.
✓ Branch 53 → 54 taken 16 times.
✓ Branch 53 → 56 taken 5 times.
|
21 | if (head.startsWith(QByteArrayLiteral("Exif\0\0")) && payloadSize > 6) { |
| 132 |
1/1✓ Branch 54 → 55 taken 16 times.
|
16 | result.removed |= clearGps(data, payload + 6, payloadSize - 6); |
| 133 |
2/6✗ Branch 58 → 59 not taken.
✓ Branch 58 → 62 taken 5 times.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 66 not taken.
✓ Branch 64 → 65 taken 5 times.
✗ Branch 64 → 66 not taken.
|
10 | } else if ((head.startsWith("http://ns.adobe.com/xap/1.0/") || head.startsWith("http://ns.adobe.com/xmp/extension/")) && |
| 134 |
3/7✓ Branch 62 → 63 taken 5 times.
✓ Branch 67 → 68 taken 5 times.
✗ Branch 67 → 69 not taken.
✓ Branch 69 → 70 taken 5 times.
✗ Branch 69 → 72 not taken.
✗ Branch 86 → 87 not taken.
✗ Branch 86 → 88 not taken.
|
10 | xmpWithLocation(data.mid(payload, payloadSize))) { |
| 135 |
1/1✓ Branch 70 → 71 taken 5 times.
|
5 | data.remove(position, 2 + length); |
| 136 | 5 | result.removed = true; | |
| 137 | 5 | position = runStart; | |
| 138 | 5 | continue; | |
| 139 | } | ||
| 140 | 21 | } | |
| 141 | 32 | position += 2 + length; | |
| 142 | 32 | runStart = position; | |
| 143 | } | ||
| 144 | 20 | return result; | |
| 145 | ✗ | } | |
| 146 | } // namespace | ||
| 147 | |||
| 148 | // Zeroing values can change what a hostile file's overlapping structures | ||
| 149 | // say, and a removed segment can bring the next one into reach, so the | ||
| 150 | // stripper runs until nothing changes: the result | ||
| 151 | // strips to itself (fuzz_exif_location). | ||
| 152 | 20 | Result stripJpeg(const QByteArray &input) { | |
| 153 | 20 | Result result = stripOnce(input); | |
| 154 | // Every pass only zeroes bytes or drops a segment, so it converges; the | ||
| 155 | // bound keeps a hostile file's cost linear in practice. | ||
| 156 |
3/4✓ Branch 19 → 20 taken 20 times.
✗ Branch 19 → 21 not taken.
✓ Branch 20 → 4 taken 10 times.
✓ Branch 20 → 21 taken 10 times.
|
20 | for (int pass = 0; pass < 64 && result.jpeg; ++pass) { |
| 157 |
1/1✓ Branch 4 → 5 taken 10 times.
|
10 | const Result again = stripOnce(result.data); |
| 158 |
3/6✓ Branch 5 → 6 taken 10 times.
✗ Branch 5 → 9 not taken.
✓ Branch 7 → 8 taken 10 times.
✗ Branch 7 → 9 not taken.
✓ Branch 10 → 11 taken 10 times.
✗ Branch 10 → 12 not taken.
|
10 | if (!again.removed && again.data == result.data) break; |
| 159 | ✗ | result.data = again.data; | |
| 160 | ✗ | result.removed = true; | |
| 161 | 10 | } | |
| 162 | 20 | return result; | |
| 163 | ✗ | } | |
| 164 | |||
| 165 | } // namespace ComposeImageLocation | ||
| 166 |