Coverage Report

Created: 2026-04-01 07:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/jxl/icc_codec.cc
Line
Count
Source
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 <algorithm>
11
#include <cstddef>
12
#include <cstdint>
13
14
#include "lib/jxl/base/common.h"
15
#include "lib/jxl/base/status.h"
16
#include "lib/jxl/dec_ans.h"
17
#include "lib/jxl/dec_bit_reader.h"
18
#include "lib/jxl/fields.h"
19
#include "lib/jxl/icc_codec_common.h"
20
#include "lib/jxl/padded_bytes.h"
21
22
namespace jxl {
23
namespace {
24
25
// Shuffles or interleaves bytes, for example with width 2, turns "ABCDabcd"
26
// into "AaBbCcDd". Transposes a matrix of ceil(size / width) columns and
27
// width rows. There are size elements, size may be < width * height, if so the
28
// last elements of the rightmost column are missing, the missing spots are
29
// transposed along with the filled spots, and the result has the missing
30
// elements at the end of the bottom row. The input is the input matrix in
31
// scanline order but with missing elements skipped (which may occur in multiple
32
// locations), the output is the result matrix in scanline order (with
33
// no need to skip missing elements as they are past the end of the data).
34
Status Shuffle(JxlMemoryManager* memory_manager, uint8_t* data, size_t size,
35
2.47k
               size_t width) {
36
2.47k
  size_t height = (size + width - 1) / width;  // amount of rows of output
37
2.47k
  PaddedBytes result(memory_manager);
38
2.47k
  JXL_ASSIGN_OR_RETURN(result,
39
2.47k
                       PaddedBytes::WithInitialSpace(memory_manager, size));
40
  // i = output index, j input index
41
2.47k
  size_t s = 0;
42
2.47k
  size_t j = 0;
43
33.8k
  for (size_t i = 0; i < size; i++) {
44
31.4k
    result[i] = data[j];
45
31.4k
    j += height;
46
31.4k
    if (j >= size) j = ++s;
47
31.4k
  }
48
49
33.8k
  for (size_t i = 0; i < size; i++) {
50
31.4k
    data[i] = result[i];
51
31.4k
  }
52
2.47k
  return true;
53
2.47k
}
54
55
// TODO(eustas): should be 20, or even 18, once DecodeVarInt is improved;
56
//               currently DecodeVarInt does not signal the errors, and marks
57
//               11 bytes as used even if only 10 are used (and 9 is enough for
58
//               63-bit values).
59
constexpr const size_t kPreambleSize = 22;  // enough for reading 2 VarInts
60
61
32.4k
uint64_t DecodeVarInt(const uint8_t* input, size_t inputSize, size_t* pos) {
62
32.4k
  size_t i;
63
32.4k
  uint64_t ret = 0;
64
41.3k
  for (i = 0; *pos + i < inputSize && i < 10; ++i) {
65
41.0k
    ret |= static_cast<uint64_t>(input[*pos + i] & 127)
66
41.0k
           << static_cast<uint64_t>(7 * i);
67
    // If the next-byte flag is not set, stop
68
41.0k
    if ((input[*pos + i] & 128) == 0) break;
69
41.0k
  }
70
  // TODO(user): Return a decoding error if i == 10.
71
32.4k
  *pos += i + 1;
72
32.4k
  return ret;
73
32.4k
}
74
75
}  // namespace
76
77
// Mimics the beginning of UnpredictICC for quick validity check.
78
// At least kPreambleSize bytes of data should be valid at invocation time.
79
6.04k
Status CheckPreamble(const PaddedBytes& data, size_t enc_size) {
80
6.04k
  const uint8_t* enc = data.data();
81
6.04k
  size_t size = data.size();
82
6.04k
  size_t pos = 0;
83
6.04k
  uint64_t osize = DecodeVarInt(enc, size, &pos);
84
6.04k
  JXL_RETURN_IF_ERROR(CheckIs32Bit(osize));
85
5.99k
  if (pos >= size) return JXL_FAILURE("Out of bounds");
86
5.99k
  uint64_t csize = DecodeVarInt(enc, size, &pos);
87
5.99k
  JXL_RETURN_IF_ERROR(CheckIs32Bit(csize));
88
5.98k
  JXL_RETURN_IF_ERROR(CheckOutOfBounds(pos, csize, size));
89
  // We expect that UnpredictICC inflates input, not the other way round.
90
5.94k
  if (osize + 65536 < enc_size) return JXL_FAILURE("Malformed ICC");
91
92
  // NB(eustas): 64 MiB ICC should be enough for everything!?
93
5.87k
  const size_t output_limit = 1 << 28;
94
5.87k
  if (output_limit && osize > output_limit) {
95
9
    return JXL_FAILURE("Decoded ICC is too large");
96
9
  }
97
5.86k
  return true;
98
5.87k
}
99
100
// Decodes the result of PredictICC back to a valid ICC profile.
101
13.6k
Status UnpredictICC(const uint8_t* enc, size_t size, PaddedBytes* result) {
102
13.6k
  if (!result->empty()) return JXL_FAILURE("result must be empty initially");
103
13.6k
  JxlMemoryManager* memory_manager = result->memory_manager();
104
13.6k
  size_t pos = 0;
105
  // TODO(lode): technically speaking we need to check that the entire varint
106
  // decoding never goes out of bounds, not just the first byte. This requires
107
  // a DecodeVarInt function that returns an error code. It is safe to use
108
  // DecodeVarInt with out of bounds values, it silently returns, but the
109
  // specification requires an error. Idem for all DecodeVarInt below.
110
13.6k
  if (pos >= size) return JXL_FAILURE("Out of bounds");
111
4.01k
  uint64_t osize = DecodeVarInt(enc, size, &pos);  // Output size
112
4.01k
  JXL_RETURN_IF_ERROR(CheckIs32Bit(osize));
113
4.00k
  if (pos >= size) return JXL_FAILURE("Out of bounds");
114
3.93k
  uint64_t csize = DecodeVarInt(enc, size, &pos);  // Commands size
115
  // Every command is translated to at least on byte.
116
3.93k
  JXL_RETURN_IF_ERROR(CheckIs32Bit(csize));
117
3.92k
  size_t cpos = pos;  // pos in commands stream
118
3.92k
  JXL_RETURN_IF_ERROR(CheckOutOfBounds(pos, csize, size));
119
2.65k
  size_t commands_end = cpos + csize;
120
2.65k
  pos = commands_end;  // pos in data stream
121
122
  // Header
123
2.65k
  PaddedBytes header{memory_manager};
124
2.65k
  JXL_RETURN_IF_ERROR(header.append(ICCInitialHeaderPrediction(osize)));
125
137k
  for (size_t i = 0; i <= kICCHeaderSize; i++) {
126
137k
    if (result->size() == osize) {
127
1.52k
      if (cpos != commands_end) return JXL_FAILURE("Not all commands used");
128
1.32k
      if (pos != size) return JXL_FAILURE("Not all data used");
129
41
      return true;  // Valid end
130
1.32k
    }
131
135k
    if (i == kICCHeaderSize) break;  // Done
132
134k
    ICCPredictHeader(result->data(), result->size(), header.data(), i);
133
134k
    if (pos >= size) return JXL_FAILURE("Out of bounds");
134
134k
    JXL_RETURN_IF_ERROR(result->push_back(enc[pos++] + header[i]));
135
134k
  }
136
1.01k
  if (cpos >= commands_end) return JXL_FAILURE("Out of bounds");
137
138
  // Tag list
139
1.00k
  uint64_t numtags = DecodeVarInt(enc, size, &cpos);
140
141
1.00k
  if (numtags != 0) {
142
788
    numtags--;
143
788
    JXL_RETURN_IF_ERROR(CheckIs32Bit(numtags));
144
716
    JXL_RETURN_IF_ERROR(AppendUint32(numtags, result));
145
716
    uint64_t prevtagstart = kICCHeaderSize + numtags * 12;
146
716
    uint64_t prevtagsize = 0;
147
12.1k
    for (;;) {
148
12.1k
      if (result->size() > osize) return JXL_FAILURE("Invalid result size");
149
12.1k
      if (cpos > commands_end) return JXL_FAILURE("Out of bounds");
150
12.1k
      if (cpos == commands_end) break;  // Valid end
151
12.0k
      uint8_t command = enc[cpos++];
152
12.0k
      uint8_t tagcode = command & 63;
153
12.0k
      Tag tag;
154
12.0k
      if (tagcode == 0) {
155
393
        break;
156
11.6k
      } else if (tagcode == kCommandTagUnknown) {
157
594
        JXL_RETURN_IF_ERROR(CheckOutOfBounds(pos, 4, size));
158
580
        tag = DecodeKeyword(enc, size, pos);
159
580
        pos += 4;
160
11.0k
      } else if (tagcode == kCommandTagTRC) {
161
1.23k
        tag = kRtrcTag;
162
9.80k
      } else if (tagcode == kCommandTagXYZ) {
163
736
        tag = kRxyzTag;
164
9.06k
      } else {
165
9.06k
        if (tagcode - kCommandTagStringFirst >= kNumTagStrings) {
166
68
          return JXL_FAILURE("Unknown tagcode");
167
68
        }
168
8.99k
        tag = *kTagStrings[tagcode - kCommandTagStringFirst];
169
8.99k
      }
170
11.5k
      JXL_RETURN_IF_ERROR(AppendKeyword(tag, result));
171
172
11.5k
      uint64_t tagstart;
173
11.5k
      uint64_t tagsize = prevtagsize;
174
11.5k
      if (tag == kRxyzTag || tag == kGxyzTag || tag == kBxyzTag ||
175
8.70k
          tag == kKxyzTag || tag == kWtptTag || tag == kBkptTag ||
176
6.59k
          tag == kLumiTag) {
177
6.59k
        tagsize = 20;
178
6.59k
      }
179
180
11.5k
      if (command & kFlagBitOffset) {
181
2.77k
        if (cpos >= commands_end) return JXL_FAILURE("Out of bounds");
182
2.74k
        tagstart = DecodeVarInt(enc, size, &cpos);
183
8.77k
      } else {
184
8.77k
        JXL_RETURN_IF_ERROR(CheckIs32Bit(prevtagstart));
185
8.77k
        tagstart = prevtagstart + prevtagsize;
186
8.77k
      }
187
11.5k
      JXL_RETURN_IF_ERROR(CheckIs32Bit(tagstart));
188
11.5k
      JXL_RETURN_IF_ERROR(AppendUint32(tagstart, result));
189
11.5k
      if (command & kFlagBitSize) {
190
2.56k
        if (cpos >= commands_end) return JXL_FAILURE("Out of bounds");
191
2.52k
        tagsize = DecodeVarInt(enc, size, &cpos);
192
2.52k
      }
193
11.4k
      JXL_RETURN_IF_ERROR(CheckIs32Bit(tagsize));
194
11.4k
      JXL_RETURN_IF_ERROR(AppendUint32(tagsize, result));
195
11.4k
      prevtagstart = tagstart;
196
11.4k
      prevtagsize = tagsize;
197
198
11.4k
      if (tagcode == kCommandTagTRC) {
199
1.22k
        JXL_RETURN_IF_ERROR(AppendKeyword(kGtrcTag, result));
200
1.22k
        JXL_RETURN_IF_ERROR(AppendUint32(tagstart, result));
201
1.22k
        JXL_RETURN_IF_ERROR(AppendUint32(tagsize, result));
202
1.22k
        JXL_RETURN_IF_ERROR(AppendKeyword(kBtrcTag, result));
203
1.22k
        JXL_RETURN_IF_ERROR(AppendUint32(tagstart, result));
204
1.22k
        JXL_RETURN_IF_ERROR(AppendUint32(tagsize, result));
205
1.22k
      }
206
207
11.4k
      if (tagcode == kCommandTagXYZ) {
208
733
        JXL_RETURN_IF_ERROR(CheckIs32Bit(tagstart + tagsize * 2));
209
732
        JXL_RETURN_IF_ERROR(AppendKeyword(kGxyzTag, result));
210
732
        JXL_RETURN_IF_ERROR(AppendUint32(tagstart + tagsize, result));
211
732
        JXL_RETURN_IF_ERROR(AppendUint32(tagsize, result));
212
732
        JXL_RETURN_IF_ERROR(AppendKeyword(kBxyzTag, result));
213
732
        JXL_RETURN_IF_ERROR(AppendUint32(tagstart + tagsize * 2, result));
214
732
        JXL_RETURN_IF_ERROR(AppendUint32(tagsize, result));
215
732
      }
216
11.4k
    }
217
716
  }
218
219
  // Main Content
220
8.64k
  for (;;) {
221
8.64k
    if (result->size() > osize) return JXL_FAILURE("Invalid result size");
222
8.63k
    if (cpos > commands_end) return JXL_FAILURE("Out of bounds");
223
8.63k
    if (cpos == commands_end) break;  // Valid end
224
8.37k
    uint8_t command = enc[cpos++];
225
8.37k
    if (command == kCommandInsert) {
226
1.61k
      if (cpos >= commands_end) return JXL_FAILURE("Out of bounds");
227
1.60k
      uint64_t num = DecodeVarInt(enc, size, &cpos);
228
1.60k
      JXL_RETURN_IF_ERROR(CheckOutOfBounds(pos, num, size));
229
371k
      for (size_t i = 0; i < num; i++) {
230
370k
        JXL_RETURN_IF_ERROR(result->push_back(enc[pos++]));
231
370k
      }
232
6.76k
    } else if (command == kCommandShuffle2 || command == kCommandShuffle4) {
233
1.57k
      if (cpos >= commands_end) return JXL_FAILURE("Out of bounds");
234
1.56k
      uint64_t num = DecodeVarInt(enc, size, &cpos);
235
1.56k
      JXL_RETURN_IF_ERROR(CheckOutOfBounds(pos, num, size));
236
1.52k
      PaddedBytes shuffled(memory_manager);
237
1.52k
      JXL_ASSIGN_OR_RETURN(shuffled,
238
1.52k
                           PaddedBytes::WithInitialSpace(memory_manager, num));
239
24.6k
      for (size_t i = 0; i < num; i++) {
240
23.1k
        shuffled[i] = enc[pos + i];
241
23.1k
      }
242
1.52k
      if (command == kCommandShuffle2) {
243
799
        JXL_RETURN_IF_ERROR(Shuffle(memory_manager, shuffled.data(), num, 2));
244
799
      } else if (command == kCommandShuffle4) {
245
724
        JXL_RETURN_IF_ERROR(Shuffle(memory_manager, shuffled.data(), num, 4));
246
724
      }
247
24.6k
      for (size_t i = 0; i < num; i++) {
248
23.1k
        JXL_RETURN_IF_ERROR(result->push_back(shuffled[i]));
249
23.1k
        pos++;
250
23.1k
      }
251
5.18k
    } else if (command == kCommandPredict) {
252
2.41k
      JXL_RETURN_IF_ERROR(CheckOutOfBounds(cpos, 2, commands_end));
253
2.39k
      uint8_t flags = enc[cpos++];
254
255
2.39k
      size_t width = (flags & 3) + 1;
256
2.39k
      if (width == 3) return JXL_FAILURE("Invalid width");
257
258
2.38k
      int order = (flags & 12) >> 2;
259
2.38k
      if (order == 3) return JXL_FAILURE("Invalid order");
260
261
2.38k
      uint64_t stride = width;
262
2.38k
      if (flags & 16) {
263
743
        if (cpos >= commands_end) return JXL_FAILURE("Out of bounds");
264
743
        stride = DecodeVarInt(enc, size, &cpos);
265
743
        if (stride < width) {
266
4
          return JXL_FAILURE("Invalid stride");
267
4
        }
268
743
      }
269
      // If stride * 4 >= result->size(), return failure. The check
270
      // "size == 0 || ((size - 1) >> 2) < stride" corresponds to
271
      // "stride * 4 >= size", but does not suffer from integer overflow.
272
      // This check is more strict than necessary but follows the specification
273
      // and the encoder should ensure this is followed.
274
2.37k
      if (result->empty() || ((result->size() - 1u) >> 2u) < stride) {
275
72
        return JXL_FAILURE("Invalid stride");
276
72
      }
277
278
2.30k
      if (cpos >= commands_end) return JXL_FAILURE("Out of bounds");
279
2.30k
      uint64_t num = DecodeVarInt(enc, size, &cpos);  // in bytes
280
2.30k
      JXL_RETURN_IF_ERROR(CheckOutOfBounds(pos, num, size));
281
282
2.26k
      PaddedBytes shuffled(memory_manager);
283
2.26k
      JXL_ASSIGN_OR_RETURN(shuffled,
284
2.26k
                           PaddedBytes::WithInitialSpace(memory_manager, num));
285
286
27.7k
      for (size_t i = 0; i < num; i++) {
287
25.4k
        shuffled[i] = enc[pos + i];
288
25.4k
      }
289
2.26k
      if (width > 1) {
290
949
        JXL_RETURN_IF_ERROR(
291
949
            Shuffle(memory_manager, shuffled.data(), num, width));
292
949
      }
293
294
2.26k
      size_t start = result->size();
295
27.7k
      for (size_t i = 0; i < num; i++) {
296
25.4k
        uint8_t predicted = LinearPredictICCValue(result->data(), start, i,
297
25.4k
                                                  stride, width, order);
298
25.4k
        JXL_RETURN_IF_ERROR(result->push_back(predicted + shuffled[i]));
299
25.4k
      }
300
2.26k
      pos += num;
301
2.77k
    } else if (command == kCommandXYZ) {
302
926
      JXL_RETURN_IF_ERROR(AppendKeyword(kXyz_Tag, result));
303
4.63k
      for (int i = 0; i < 4; i++) {
304
3.70k
        JXL_RETURN_IF_ERROR(result->push_back(0));
305
3.70k
      }
306
926
      JXL_RETURN_IF_ERROR(CheckOutOfBounds(pos, 12, size));
307
11.9k
      for (size_t i = 0; i < 12; i++) {
308
10.9k
        JXL_RETURN_IF_ERROR(result->push_back(enc[pos++]));
309
10.9k
      }
310
1.84k
    } else if (command >= kCommandTypeStartFirst &&
311
1.76k
               command < kCommandTypeStartFirst + kNumTypeStrings) {
312
1.64k
      JXL_RETURN_IF_ERROR(AppendKeyword(
313
1.64k
          *kTypeStrings[command - kCommandTypeStartFirst], result));
314
8.24k
      for (size_t i = 0; i < 4; i++) {
315
6.59k
        JXL_RETURN_IF_ERROR(result->push_back(0));
316
6.59k
      }
317
1.64k
    } else {
318
201
      return JXL_FAILURE("Unknown command");
319
201
    }
320
8.37k
  }
321
322
259
  if (pos != size) return JXL_FAILURE("Not all data used");
323
88
  if (result->size() != osize) return JXL_FAILURE("Invalid result size");
324
325
85
  return true;
326
88
}
327
328
52.8k
Status ICCReader::Init(BitReader* reader) {
329
52.8k
  JXL_RETURN_IF_ERROR(CheckEOI(reader));
330
52.8k
  JxlMemoryManager* memory_manager = decompressed_.memory_manager();
331
52.8k
  used_bits_base_ = reader->TotalBitsConsumed();
332
52.8k
  if (bits_to_skip_ == 0) {
333
51.9k
    enc_size_ = U64Coder::Read(reader);
334
51.9k
    if (enc_size_ > 268435456) {
335
      // Avoid too large memory allocation for invalid file.
336
316
      return JXL_FAILURE("Too large encoded profile");
337
316
    }
338
51.6k
    JXL_RETURN_IF_ERROR(DecodeHistograms(
339
51.6k
        memory_manager, reader, kNumICCContexts, &code_, &context_map_));
340
53.3k
    JXL_ASSIGN_OR_RETURN(ans_reader_, ANSSymbolReader::Create(&code_, reader));
341
53.3k
    i_ = 0;
342
53.3k
    JXL_RETURN_IF_ERROR(
343
53.3k
        decompressed_.resize(std::min<size_t>(i_ + 0x400, enc_size_)));
344
47.1k
    for (; i_ < std::min<size_t>(2, enc_size_); i_++) {
345
20.4k
      decompressed_[i_] = ans_reader_.ReadHybridUint(
346
20.4k
          ICCANSContext(i_, i_ > 0 ? decompressed_[i_ - 1] : 0,
347
20.4k
                        i_ > 1 ? decompressed_[i_ - 2] : 0),
348
20.4k
          reader, context_map_);
349
20.4k
    }
350
26.6k
    if (enc_size_ > kPreambleSize) {
351
178k
      for (; i_ < kPreambleSize; i_++) {
352
169k
        decompressed_[i_] = ans_reader_.ReadHybridUint(
353
169k
            ICCANSContext(i_, decompressed_[i_ - 1], decompressed_[i_ - 2]),
354
169k
            reader, context_map_);
355
169k
      }
356
8.48k
      JXL_RETURN_IF_ERROR(CheckEOI(reader));
357
6.04k
      JXL_RETURN_IF_ERROR(CheckPreamble(decompressed_, enc_size_));
358
6.04k
    }
359
24.0k
    bits_to_skip_ = reader->TotalBitsConsumed() - used_bits_base_;
360
24.0k
  } else {
361
904
    reader->SkipBits(bits_to_skip_);
362
904
  }
363
24.9k
  return true;
364
52.8k
}
365
366
17.9k
Status ICCReader::Process(BitReader* reader, PaddedBytes* icc) {
367
17.9k
  auto checkpoint = jxl::make_unique<ANSSymbolReader::Checkpoint>();
368
17.9k
  size_t saved_i = 0;
369
45.8k
  auto save = [&]() {
370
45.8k
    ans_reader_.Save(checkpoint.get());
371
45.8k
    bits_to_skip_ = reader->TotalBitsConsumed() - used_bits_base_;
372
45.8k
    saved_i = i_;
373
45.8k
  };
374
17.9k
  save();
375
45.8k
  auto check_and_restore = [&]() -> Status {
376
45.8k
    Status status = CheckEOI(reader);
377
45.8k
    if (!status) {
378
      // not enough bytes.
379
4.37k
      ans_reader_.Restore(*checkpoint);
380
4.37k
      i_ = saved_i;
381
4.37k
      return status;
382
4.37k
    }
383
41.5k
    return true;
384
45.8k
  };
385
16.0M
  for (; i_ < enc_size_; i_++) {
386
16.0M
    if (i_ % ANSSymbolReader::kMaxCheckpointInterval == 0 && i_ > 0) {
387
29.1k
      JXL_RETURN_IF_ERROR(check_and_restore());
388
27.9k
      save();
389
27.9k
      if ((i_ > 0) && (((i_ & 0xFFFF) == 0))) {
390
134
        float used_bytes =
391
134
            (reader->TotalBitsConsumed() - used_bits_base_) / 8.0f;
392
134
        if (i_ > used_bytes * 256) return JXL_FAILURE("Corrupted stream");
393
134
      }
394
27.9k
      JXL_RETURN_IF_ERROR(
395
27.9k
          decompressed_.resize(std::min<size_t>(i_ + 0x400, enc_size_)));
396
27.9k
    }
397
16.0M
    JXL_ENSURE(i_ >= 2);
398
16.0M
    decompressed_[i_] = ans_reader_.ReadHybridUint(
399
16.0M
        ICCANSContext(i_, decompressed_[i_ - 1], decompressed_[i_ - 2]), reader,
400
16.0M
        context_map_);
401
16.0M
  }
402
16.7k
  JXL_RETURN_IF_ERROR(check_and_restore());
403
13.6k
  bits_to_skip_ = reader->TotalBitsConsumed() - used_bits_base_;
404
13.6k
  if (!ans_reader_.CheckANSFinalState()) {
405
0
    return JXL_FAILURE("Corrupted ICC profile");
406
0
  }
407
408
13.6k
  icc->clear();
409
13.6k
  return UnpredictICC(decompressed_.data(), decompressed_.size(), icc);
410
13.6k
}
411
412
107k
Status ICCReader::CheckEOI(BitReader* reader) {
413
107k
  if (reader->AllReadsWithinBounds()) return true;
414
6.81k
  return JXL_NOT_ENOUGH_BYTES("Not enough bytes for reading ICC profile");
415
107k
}
416
417
}  // namespace jxl