Coverage Report

Created: 2025-06-22 08:04

/src/libjxl/lib/jxl/icc_codec.cc
Line
Count
Source (jump to first uncovered line)
1
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
2
//
3
// Use of this source code is governed by a BSD-style
4
// license that can be found in the LICENSE file.
5
6
#include "lib/jxl/icc_codec.h"
7
8
#include <jxl/memory_manager.h>
9
10
#include <cstdint>
11
12
#include "lib/jxl/base/status.h"
13
#include "lib/jxl/dec_ans.h"
14
#include "lib/jxl/fields.h"
15
#include "lib/jxl/icc_codec_common.h"
16
#include "lib/jxl/padded_bytes.h"
17
18
namespace jxl {
19
namespace {
20
21
// Shuffles or interleaves bytes, for example with width 2, turns "ABCDabcd"
22
// into "AaBbCcDd". Transposes a matrix of ceil(size / width) columns and
23
// width rows. There are size elements, size may be < width * height, if so the
24
// last elements of the rightmost column are missing, the missing spots are
25
// transposed along with the filled spots, and the result has the missing
26
// elements at the end of the bottom row. The input is the input matrix in
27
// scanline order but with missing elements skipped (which may occur in multiple
28
// locations), the output is the result matrix in scanline order (with
29
// no need to skip missing elements as they are past the end of the data).
30
Status Shuffle(JxlMemoryManager* memory_manager, uint8_t* data, size_t size,
31
25.7k
               size_t width) {
32
25.7k
  size_t height = (size + width - 1) / width;  // amount of rows of output
33
25.7k
  PaddedBytes result(memory_manager);
34
25.7k
  JXL_ASSIGN_OR_RETURN(result,
35
25.7k
                       PaddedBytes::WithInitialSpace(memory_manager, size));
36
  // i = output index, j input index
37
25.7k
  size_t s = 0;
38
25.7k
  size_t j = 0;
39
1.12M
  for (size_t i = 0; i < size; i++) {
40
1.10M
    result[i] = data[j];
41
1.10M
    j += height;
42
1.10M
    if (j >= size) j = ++s;
43
1.10M
  }
44
45
1.12M
  for (size_t i = 0; i < size; i++) {
46
1.10M
    data[i] = result[i];
47
1.10M
  }
48
25.7k
  return true;
49
25.7k
}
50
51
// TODO(eustas): should be 20, or even 18, once DecodeVarInt is improved;
52
//               currently DecodeVarInt does not signal the errors, and marks
53
//               11 bytes as used even if only 10 are used (and 9 is enough for
54
//               63-bit values).
55
constexpr const size_t kPreambleSize = 22;  // enough for reading 2 VarInts
56
57
132k
uint64_t DecodeVarInt(const uint8_t* input, size_t inputSize, size_t* pos) {
58
132k
  size_t i;
59
132k
  uint64_t ret = 0;
60
166k
  for (i = 0; *pos + i < inputSize && i < 10; ++i) {
61
166k
    ret |= static_cast<uint64_t>(input[*pos + i] & 127)
62
166k
           << static_cast<uint64_t>(7 * i);
63
    // If the next-byte flag is not set, stop
64
166k
    if ((input[*pos + i] & 128) == 0) break;
65
166k
  }
66
  // TODO(user): Return a decoding error if i == 10.
67
132k
  *pos += i + 1;
68
132k
  return ret;
69
132k
}
70
71
}  // namespace
72
73
// Mimics the beginning of UnpredictICC for quick validity check.
74
// At least kPreambleSize bytes of data should be valid at invocation time.
75
6.53k
Status CheckPreamble(const PaddedBytes& data, size_t enc_size) {
76
6.53k
  const uint8_t* enc = data.data();
77
6.53k
  size_t size = data.size();
78
6.53k
  size_t pos = 0;
79
6.53k
  uint64_t osize = DecodeVarInt(enc, size, &pos);
80
6.53k
  JXL_RETURN_IF_ERROR(CheckIs32Bit(osize));
81
6.50k
  if (pos >= size) return JXL_FAILURE("Out of bounds");
82
6.50k
  uint64_t csize = DecodeVarInt(enc, size, &pos);
83
6.50k
  JXL_RETURN_IF_ERROR(CheckIs32Bit(csize));
84
6.49k
  JXL_RETURN_IF_ERROR(CheckOutOfBounds(pos, csize, size));
85
  // We expect that UnpredictICC inflates input, not the other way round.
86
6.48k
  if (osize + 65536 < enc_size) return JXL_FAILURE("Malformed ICC");
87
88
  // NB(eustas): 64 MiB ICC should be enough for everything!?
89
6.46k
  const size_t output_limit = 1 << 28;
90
6.46k
  if (output_limit && osize > output_limit) {
91
4
    return JXL_FAILURE("Decoded ICC is too large");
92
4
  }
93
6.46k
  return true;
94
6.46k
}
95
96
// Decodes the result of PredictICC back to a valid ICC profile.
97
6.41k
Status UnpredictICC(const uint8_t* enc, size_t size, PaddedBytes* result) {
98
6.41k
  if (!result->empty()) return JXL_FAILURE("result must be empty initially");
99
6.41k
  JxlMemoryManager* memory_manager = result->memory_manager();
100
6.41k
  size_t pos = 0;
101
  // TODO(lode): technically speaking we need to check that the entire varint
102
  // decoding never goes out of bounds, not just the first byte. This requires
103
  // a DecodeVarInt function that returns an error code. It is safe to use
104
  // DecodeVarInt with out of bounds values, it silently returns, but the
105
  // specification requires an error. Idem for all DecodeVarInt below.
106
6.41k
  if (pos >= size) return JXL_FAILURE("Out of bounds");
107
6.41k
  uint64_t osize = DecodeVarInt(enc, size, &pos);  // Output size
108
6.41k
  JXL_RETURN_IF_ERROR(CheckIs32Bit(osize));
109
6.39k
  if (pos >= size) return JXL_FAILURE("Out of bounds");
110
6.39k
  uint64_t csize = DecodeVarInt(enc, size, &pos);  // Commands size
111
  // Every command is translated to at least on byte.
112
6.39k
  JXL_RETURN_IF_ERROR(CheckIs32Bit(csize));
113
6.39k
  size_t cpos = pos;  // pos in commands stream
114
6.39k
  JXL_RETURN_IF_ERROR(CheckOutOfBounds(pos, csize, size));
115
6.37k
  size_t commands_end = cpos + csize;
116
6.37k
  pos = commands_end;  // pos in data stream
117
118
  // Header
119
6.37k
  PaddedBytes header{memory_manager};
120
6.37k
  JXL_RETURN_IF_ERROR(header.append(ICCInitialHeaderPrediction(osize)));
121
798k
  for (size_t i = 0; i <= kICCHeaderSize; i++) {
122
798k
    if (result->size() == osize) {
123
189
      if (cpos != commands_end) return JXL_FAILURE("Not all commands used");
124
152
      if (pos != size) return JXL_FAILURE("Not all data used");
125
70
      return true;  // Valid end
126
152
    }
127
797k
    if (i == kICCHeaderSize) break;  // Done
128
791k
    ICCPredictHeader(result->data(), result->size(), header.data(), i);
129
791k
    if (pos >= size) return JXL_FAILURE("Out of bounds");
130
791k
    JXL_RETURN_IF_ERROR(result->push_back(enc[pos++] + header[i]));
131
791k
  }
132
6.17k
  if (cpos >= commands_end) return JXL_FAILURE("Out of bounds");
133
134
  // Tag list
135
6.17k
  uint64_t numtags = DecodeVarInt(enc, size, &cpos);
136
137
6.17k
  if (numtags != 0) {
138
5.98k
    numtags--;
139
5.98k
    JXL_RETURN_IF_ERROR(CheckIs32Bit(numtags));
140
5.91k
    JXL_RETURN_IF_ERROR(AppendUint32(numtags, result));
141
5.91k
    uint64_t prevtagstart = kICCHeaderSize + numtags * 12;
142
5.91k
    uint64_t prevtagsize = 0;
143
72.5k
    for (;;) {
144
72.5k
      if (result->size() > osize) return JXL_FAILURE("Invalid result size");
145
72.5k
      if (cpos > commands_end) return JXL_FAILURE("Out of bounds");
146
72.5k
      if (cpos == commands_end) break;  // Valid end
147
72.5k
      uint8_t command = enc[cpos++];
148
72.5k
      uint8_t tagcode = command & 63;
149
72.5k
      Tag tag;
150
72.5k
      if (tagcode == 0) {
151
5.80k
        break;
152
66.7k
      } else if (tagcode == kCommandTagUnknown) {
153
3.86k
        JXL_RETURN_IF_ERROR(CheckOutOfBounds(pos, 4, size));
154
3.86k
        tag = DecodeKeyword(enc, size, pos);
155
3.86k
        pos += 4;
156
62.8k
      } else if (tagcode == kCommandTagTRC) {
157
5.65k
        tag = kRtrcTag;
158
57.2k
      } else if (tagcode == kCommandTagXYZ) {
159
558
        tag = kRxyzTag;
160
56.6k
      } else {
161
56.6k
        if (tagcode - kCommandTagStringFirst >= kNumTagStrings) {
162
24
          return JXL_FAILURE("Unknown tagcode");
163
24
        }
164
56.6k
        tag = *kTagStrings[tagcode - kCommandTagStringFirst];
165
56.6k
      }
166
66.7k
      JXL_RETURN_IF_ERROR(AppendKeyword(tag, result));
167
168
66.7k
      uint64_t tagstart;
169
66.7k
      uint64_t tagsize = prevtagsize;
170
66.7k
      if (tag == kRxyzTag || tag == kGxyzTag || tag == kBxyzTag ||
171
66.7k
          tag == kKxyzTag || tag == kWtptTag || tag == kBkptTag ||
172
66.7k
          tag == kLumiTag) {
173
24.8k
        tagsize = 20;
174
24.8k
      }
175
176
66.7k
      if (command & kFlagBitOffset) {
177
16.1k
        if (cpos >= commands_end) return JXL_FAILURE("Out of bounds");
178
16.1k
        tagstart = DecodeVarInt(enc, size, &cpos);
179
50.5k
      } else {
180
50.5k
        JXL_RETURN_IF_ERROR(CheckIs32Bit(prevtagstart));
181
50.5k
        tagstart = prevtagstart + prevtagsize;
182
50.5k
      }
183
66.6k
      JXL_RETURN_IF_ERROR(CheckIs32Bit(tagstart));
184
66.6k
      JXL_RETURN_IF_ERROR(AppendUint32(tagstart, result));
185
66.6k
      if (command & kFlagBitSize) {
186
34.6k
        if (cpos >= commands_end) return JXL_FAILURE("Out of bounds");
187
34.6k
        tagsize = DecodeVarInt(enc, size, &cpos);
188
34.6k
      }
189
66.6k
      JXL_RETURN_IF_ERROR(CheckIs32Bit(tagsize));
190
66.6k
      JXL_RETURN_IF_ERROR(AppendUint32(tagsize, result));
191
66.6k
      prevtagstart = tagstart;
192
66.6k
      prevtagsize = tagsize;
193
194
66.6k
      if (tagcode == kCommandTagTRC) {
195
5.65k
        JXL_RETURN_IF_ERROR(AppendKeyword(kGtrcTag, result));
196
5.65k
        JXL_RETURN_IF_ERROR(AppendUint32(tagstart, result));
197
5.65k
        JXL_RETURN_IF_ERROR(AppendUint32(tagsize, result));
198
5.65k
        JXL_RETURN_IF_ERROR(AppendKeyword(kBtrcTag, result));
199
5.65k
        JXL_RETURN_IF_ERROR(AppendUint32(tagstart, result));
200
5.65k
        JXL_RETURN_IF_ERROR(AppendUint32(tagsize, result));
201
5.65k
      }
202
203
66.6k
      if (tagcode == kCommandTagXYZ) {
204
558
        JXL_RETURN_IF_ERROR(CheckIs32Bit(tagstart + tagsize * 2));
205
557
        JXL_RETURN_IF_ERROR(AppendKeyword(kGxyzTag, result));
206
557
        JXL_RETURN_IF_ERROR(AppendUint32(tagstart + tagsize, result));
207
557
        JXL_RETURN_IF_ERROR(AppendUint32(tagsize, result));
208
557
        JXL_RETURN_IF_ERROR(AppendKeyword(kBxyzTag, result));
209
557
        JXL_RETURN_IF_ERROR(AppendUint32(tagstart + tagsize * 2, result));
210
557
        JXL_RETURN_IF_ERROR(AppendUint32(tagsize, result));
211
557
      }
212
66.6k
    }
213
5.91k
  }
214
215
  // Main Content
216
114k
  for (;;) {
217
114k
    if (result->size() > osize) return JXL_FAILURE("Invalid result size");
218
114k
    if (cpos > commands_end) return JXL_FAILURE("Out of bounds");
219
114k
    if (cpos == commands_end) break;  // Valid end
220
108k
    uint8_t command = enc[cpos++];
221
108k
    if (command == kCommandInsert) {
222
22.7k
      if (cpos >= commands_end) return JXL_FAILURE("Out of bounds");
223
22.7k
      uint64_t num = DecodeVarInt(enc, size, &cpos);
224
22.7k
      JXL_RETURN_IF_ERROR(CheckOutOfBounds(pos, num, size));
225
13.1M
      for (size_t i = 0; i < num; i++) {
226
13.1M
        JXL_RETURN_IF_ERROR(result->push_back(enc[pos++]));
227
13.1M
      }
228
85.5k
    } else if (command == kCommandShuffle2 || command == kCommandShuffle4) {
229
24.9k
      if (cpos >= commands_end) return JXL_FAILURE("Out of bounds");
230
24.9k
      uint64_t num = DecodeVarInt(enc, size, &cpos);
231
24.9k
      JXL_RETURN_IF_ERROR(CheckOutOfBounds(pos, num, size));
232
24.9k
      PaddedBytes shuffled(memory_manager);
233
24.9k
      JXL_ASSIGN_OR_RETURN(shuffled,
234
24.9k
                           PaddedBytes::WithInitialSpace(memory_manager, num));
235
1.11M
      for (size_t i = 0; i < num; i++) {
236
1.08M
        shuffled[i] = enc[pos + i];
237
1.08M
      }
238
24.9k
      if (command == kCommandShuffle2) {
239
22.7k
        JXL_RETURN_IF_ERROR(Shuffle(memory_manager, shuffled.data(), num, 2));
240
22.7k
      } else if (command == kCommandShuffle4) {
241
2.13k
        JXL_RETURN_IF_ERROR(Shuffle(memory_manager, shuffled.data(), num, 4));
242
2.13k
      }
243
1.11M
      for (size_t i = 0; i < num; i++) {
244
1.08M
        JXL_RETURN_IF_ERROR(result->push_back(shuffled[i]));
245
1.08M
        pos++;
246
1.08M
      }
247
60.5k
    } else if (command == kCommandPredict) {
248
2.25k
      JXL_RETURN_IF_ERROR(CheckOutOfBounds(cpos, 2, commands_end));
249
2.25k
      uint8_t flags = enc[cpos++];
250
251
2.25k
      size_t width = (flags & 3) + 1;
252
2.25k
      if (width == 3) return JXL_FAILURE("Invalid width");
253
254
2.24k
      int order = (flags & 12) >> 2;
255
2.24k
      if (order == 3) return JXL_FAILURE("Invalid order");
256
257
2.24k
      uint64_t stride = width;
258
2.24k
      if (flags & 16) {
259
327
        if (cpos >= commands_end) return JXL_FAILURE("Out of bounds");
260
327
        stride = DecodeVarInt(enc, size, &cpos);
261
327
        if (stride < width) {
262
1
          return JXL_FAILURE("Invalid stride");
263
1
        }
264
327
      }
265
      // If stride * 4 >= result->size(), return failure. The check
266
      // "size == 0 || ((size - 1) >> 2) < stride" corresponds to
267
      // "stride * 4 >= size", but does not suffer from integer overflow.
268
      // This check is more strict than necessary but follows the specification
269
      // and the encoder should ensure this is followed.
270
2.24k
      if (result->empty() || ((result->size() - 1u) >> 2u) < stride) {
271
77
        return JXL_FAILURE("Invalid stride");
272
77
      }
273
274
2.17k
      if (cpos >= commands_end) return JXL_FAILURE("Out of bounds");
275
2.16k
      uint64_t num = DecodeVarInt(enc, size, &cpos);  // in bytes
276
2.16k
      JXL_RETURN_IF_ERROR(CheckOutOfBounds(pos, num, size));
277
278
2.11k
      PaddedBytes shuffled(memory_manager);
279
2.11k
      JXL_ASSIGN_OR_RETURN(shuffled,
280
2.11k
                           PaddedBytes::WithInitialSpace(memory_manager, num));
281
282
22.6k
      for (size_t i = 0; i < num; i++) {
283
20.5k
        shuffled[i] = enc[pos + i];
284
20.5k
      }
285
2.11k
      if (width > 1) {
286
775
        JXL_RETURN_IF_ERROR(
287
775
            Shuffle(memory_manager, shuffled.data(), num, width));
288
775
      }
289
290
2.11k
      size_t start = result->size();
291
22.6k
      for (size_t i = 0; i < num; i++) {
292
20.5k
        uint8_t predicted = LinearPredictICCValue(result->data(), start, i,
293
20.5k
                                                  stride, width, order);
294
20.5k
        JXL_RETURN_IF_ERROR(result->push_back(predicted + shuffled[i]));
295
20.5k
      }
296
2.11k
      pos += num;
297
58.3k
    } else if (command == kCommandXYZ) {
298
22.5k
      JXL_RETURN_IF_ERROR(AppendKeyword(kXyz_Tag, result));
299
112k
      for (int i = 0; i < 4; i++) {
300
90.0k
        JXL_RETURN_IF_ERROR(result->push_back(0));
301
90.0k
      }
302
22.5k
      JXL_RETURN_IF_ERROR(CheckOutOfBounds(pos, 12, size));
303
292k
      for (size_t i = 0; i < 12; i++) {
304
270k
        JXL_RETURN_IF_ERROR(result->push_back(enc[pos++]));
305
270k
      }
306
35.8k
    } else if (command >= kCommandTypeStartFirst &&
307
35.8k
               command < kCommandTypeStartFirst + kNumTypeStrings) {
308
35.7k
      JXL_RETURN_IF_ERROR(AppendKeyword(
309
35.7k
          *kTypeStrings[command - kCommandTypeStartFirst], result));
310
178k
      for (size_t i = 0; i < 4; i++) {
311
143k
        JXL_RETURN_IF_ERROR(result->push_back(0));
312
143k
      }
313
35.7k
    } else {
314
58
      return JXL_FAILURE("Unknown command");
315
58
    }
316
108k
  }
317
318
5.75k
  if (pos != size) return JXL_FAILURE("Not all data used");
319
5.70k
  if (result->size() != osize) return JXL_FAILURE("Invalid result size");
320
321
5.69k
  return true;
322
5.70k
}
323
324
15.4k
Status ICCReader::Init(BitReader* reader) {
325
15.4k
  JXL_RETURN_IF_ERROR(CheckEOI(reader));
326
15.4k
  JxlMemoryManager* memory_manager = decompressed_.memory_manager();
327
15.4k
  used_bits_base_ = reader->TotalBitsConsumed();
328
15.4k
  if (bits_to_skip_ == 0) {
329
14.1k
    enc_size_ = U64Coder::Read(reader);
330
14.1k
    if (enc_size_ > 268435456) {
331
      // Avoid too large memory allocation for invalid file.
332
143
      return JXL_FAILURE("Too large encoded profile");
333
143
    }
334
14.0k
    JXL_RETURN_IF_ERROR(DecodeHistograms(
335
14.0k
        memory_manager, reader, kNumICCContexts, &code_, &context_map_));
336
18.5k
    JXL_ASSIGN_OR_RETURN(ans_reader_, ANSSymbolReader::Create(&code_, reader));
337
18.5k
    i_ = 0;
338
18.5k
    JXL_RETURN_IF_ERROR(
339
18.5k
        decompressed_.resize(std::min<size_t>(i_ + 0x400, enc_size_)));
340
27.6k
    for (; i_ < std::min<size_t>(2, enc_size_); i_++) {
341
18.3k
      decompressed_[i_] = ans_reader_.ReadHybridUint(
342
18.3k
          ICCANSContext(i_, i_ > 0 ? decompressed_[i_ - 1] : 0,
343
18.3k
                        i_ > 1 ? decompressed_[i_ - 2] : 0),
344
18.3k
          reader, context_map_);
345
18.3k
    }
346
9.27k
    if (enc_size_ > kPreambleSize) {
347
189k
      for (; i_ < kPreambleSize; i_++) {
348
180k
        decompressed_[i_] = ans_reader_.ReadHybridUint(
349
180k
            ICCANSContext(i_, decompressed_[i_ - 1], decompressed_[i_ - 2]),
350
180k
            reader, context_map_);
351
180k
      }
352
9.02k
      JXL_RETURN_IF_ERROR(CheckEOI(reader));
353
6.53k
      JXL_RETURN_IF_ERROR(CheckPreamble(decompressed_, enc_size_));
354
6.53k
    }
355
6.70k
    bits_to_skip_ = reader->TotalBitsConsumed() - used_bits_base_;
356
6.70k
  } else {
357
1.29k
    reader->SkipBits(bits_to_skip_);
358
1.29k
  }
359
8.00k
  return true;
360
15.4k
}
361
362
7.47k
Status ICCReader::Process(BitReader* reader, PaddedBytes* icc) {
363
7.47k
  ANSSymbolReader::Checkpoint checkpoint;
364
7.47k
  size_t saved_i = 0;
365
52.6k
  auto save = [&]() {
366
52.6k
    ans_reader_.Save(&checkpoint);
367
52.6k
    bits_to_skip_ = reader->TotalBitsConsumed() - used_bits_base_;
368
52.6k
    saved_i = i_;
369
52.6k
  };
370
7.47k
  save();
371
52.6k
  auto check_and_restore = [&]() {
372
52.6k
    Status status = CheckEOI(reader);
373
52.6k
    if (!status) {
374
      // not enough bytes.
375
1.05k
      ans_reader_.Restore(checkpoint);
376
1.05k
      i_ = saved_i;
377
1.05k
      return status;
378
1.05k
    }
379
51.6k
    return Status(true);
380
52.6k
  };
381
25.2M
  for (; i_ < enc_size_; i_++) {
382
25.1M
    if (i_ % ANSSymbolReader::kMaxCheckpointInterval == 0 && i_ > 0) {
383
45.7k
      JXL_RETURN_IF_ERROR(check_and_restore());
384
45.1k
      save();
385
45.1k
      if ((i_ > 0) && (((i_ & 0xFFFF) == 0))) {
386
222
        float used_bytes =
387
222
            (reader->TotalBitsConsumed() - used_bits_base_) / 8.0f;
388
222
        if (i_ > used_bytes * 256) return JXL_FAILURE("Corrupted stream");
389
222
      }
390
45.1k
      JXL_RETURN_IF_ERROR(
391
45.1k
          decompressed_.resize(std::min<size_t>(i_ + 0x400, enc_size_)));
392
45.1k
    }
393
25.1M
    JXL_ENSURE(i_ >= 2);
394
25.1M
    decompressed_[i_] = ans_reader_.ReadHybridUint(
395
25.1M
        ICCANSContext(i_, decompressed_[i_ - 1], decompressed_[i_ - 2]), reader,
396
25.1M
        context_map_);
397
25.1M
  }
398
6.96k
  JXL_RETURN_IF_ERROR(check_and_restore());
399
6.41k
  bits_to_skip_ = reader->TotalBitsConsumed() - used_bits_base_;
400
6.41k
  if (!ans_reader_.CheckANSFinalState()) {
401
0
    return JXL_FAILURE("Corrupted ICC profile");
402
0
  }
403
404
6.41k
  icc->clear();
405
6.41k
  return UnpredictICC(decompressed_.data(), decompressed_.size(), icc);
406
6.41k
}
407
408
77.1k
Status ICCReader::CheckEOI(BitReader* reader) {
409
77.1k
  if (reader->AllReadsWithinBounds()) return true;
410
3.55k
  return JXL_STATUS(StatusCode::kNotEnoughBytes,
411
77.1k
                    "Not enough bytes for reading ICC profile");
412
77.1k
}
413
414
}  // namespace jxl