Skip to content

Commit cbb79a9

Browse files
committed
nrf_compress: fix processing of the 'last part'
This commit fixes the processing of the 'last part' of compressed input. Previously, LZMA_FINISH_END was incorectly enforced and it caused issues if decompression output reached 'dicLimit' during this 'last part' processing. Please refer to following discussion for the detailed rationale: #20582 (comment) nrf_compress API was extended to support decompressed output limit checking. This is the correct way to use LZMA_FINISH_END flag. Also, decompression status at the end of input stream processing is now checked. ref: NCSDK-32340 Signed-off-by: Michal Kozikowski <[email protected]>
1 parent e715696 commit cbb79a9

File tree

8 files changed

+245
-93
lines changed

8 files changed

+245
-93
lines changed

include/nrf_compress/implementation.h

+23-15
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,20 @@ extern "C" {
3030
#endif
3131

3232
/**
33-
* @typedef nrf_compress_init_func_t
34-
* @brief Initialize compression implementation.
33+
* @typedef nrf_compress_init_func_t
34+
* @brief Initialize compression implementation.
3535
*
36-
* @param[in] inst Implementation specific initialization context.
37-
* Concrete implementation may cast it to predefined type.
36+
* @param[in] inst Implementation specific initialization context.
37+
* Concrete implementation may cast it to predefined type.
38+
* @param[in] decompressed_size Expected size of entire decompressed data stream. It is used
39+
* for checking that output produced during streaming does not
40+
* exceed the expected size. Set it to NULL if the size is not known;
41+
* there will be no otuput size checking then.
3842
*
39-
* @retval 0 Success.
40-
* @retval -errno Negative errno code on other failure.
43+
* @retval 0 Success.
44+
* @retval -errno Negative errno code on other failure.
4145
*/
42-
typedef int (*nrf_compress_init_func_t)(void *inst);
46+
typedef int (*nrf_compress_init_func_t)(void *inst, const size_t *decompressed_size);
4347

4448
/**
4549
* @typedef nrf_compress_deinit_func_t
@@ -54,17 +58,21 @@ typedef int (*nrf_compress_init_func_t)(void *inst);
5458
typedef int (*nrf_compress_deinit_func_t)(void *inst);
5559

5660
/**
57-
* @typedef nrf_compress_reset_func_t
58-
* @brief Reset compression state function. Used to abort current compression or
59-
* decompression task before starting a new one.
61+
* @typedef nrf_compress_reset_func_t
62+
* @brief Reset compression state function. Used to abort current
63+
* compression or decompression task before starting a new one.
6064
*
61-
* @param[in] inst Implementation specific initialization context.
62-
* Concrete implementation may cast it to predefined type.
65+
* @param[in] inst Implementation specific initialization context.
66+
* Concrete implementation may cast it to predefined type.
67+
* @param[in] decompressed_size Expected size of entire decompressed data stream. It is used
68+
* for checking that output produced during streaming does not
69+
* exceed the expected size. Set it to NULL if the size is not known;
70+
* there will be no otuput size checking then.
6371
*
64-
* @retval 0 Success.
65-
* @retval -errno Negative errno code on other failure.
72+
* @retval 0 Success.
73+
* @retval -errno Negative errno code on other failure.
6674
*/
67-
typedef int (*nrf_compress_reset_func_t)(void *inst);
75+
typedef int (*nrf_compress_reset_func_t)(void *inst, const size_t *decompressed_size);
6876

6977
/**
7078
* @typedef nrf_compress_compress_func_t

subsys/nrf_compress/src/arm_thumb.c

+11-3
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,14 @@ static uint8_t output_buffer[CONFIG_NRF_COMPRESS_CHUNK_SIZE + EXTRA_BUFFER_SIZE]
2323
static uint8_t temp_extra_buffer[EXTRA_BUFFER_SIZE];
2424
static uint32_t data_position = 0;
2525
static bool has_extra_buffer_data;
26+
static size_t output_limit = SIZE_MAX;
2627

27-
static int arm_thumb_init(void *inst)
28+
static int arm_thumb_reset(void *inst, const size_t *decompressed_size);
29+
30+
static int arm_thumb_init(void *inst, const size_t *decompressed_size)
2831
{
32+
output_limit = decompressed_size != NULL ? *decompressed_size : SIZE_MAX;
33+
2934
data_position = 0;
3035
has_extra_buffer_data = false;
3136

@@ -41,8 +46,10 @@ static int arm_thumb_deinit(void *inst)
4146
return 0;
4247
}
4348

44-
static int arm_thumb_reset(void *inst)
49+
static int arm_thumb_reset(void *inst, const size_t *decompressed_size)
4550
{
51+
output_limit = decompressed_size != NULL ? *decompressed_size : SIZE_MAX;
52+
4653
data_position = 0;
4754
has_extra_buffer_data = false;
4855
memset(output_buffer, 0x00, sizeof(output_buffer));
@@ -62,7 +69,7 @@ static int arm_thumb_decompress(void *inst, const uint8_t *input, size_t input_s
6269
bool end_part_match = false;
6370
bool extra_buffer_used = false;
6471

65-
if (input_size > CONFIG_NRF_COMPRESS_CHUNK_SIZE) {
72+
if (input_size > CONFIG_NRF_COMPRESS_CHUNK_SIZE || output_limit < input_size) {
6673
return -EINVAL;
6774
}
6875

@@ -88,6 +95,7 @@ static int arm_thumb_decompress(void *inst, const uint8_t *input, size_t input_s
8895

8996
*output = output_buffer;
9097
*output_size = input_size;
98+
output_limit -= input_size;
9199

92100
if (end_part_match == true && !last_part) {
93101
/* Partial match at end of input, need to cut the final 2 bytes off and stash

subsys/nrf_compress/src/lzma.c

+94-38
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ static dict_cache cache;
149149
#endif
150150
#endif
151151

152+
static size_t lzma_output_limit = SIZE_MAX;
152153
static bool allocated_probs = false;
153154
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2
154155
static CLzma2Dec lzma_decoder;
@@ -220,14 +221,14 @@ static int check_inst(void *inst)
220221
#define check_inst(...) 0
221222
#endif
222223

223-
static int lzma_reset(void *inst);
224+
static int lzma_reset(void *inst, const size_t *decompressed_size);
224225

225-
static int lzma_init(void *inst)
226+
static int lzma_init(void *inst, const size_t *decompressed_size)
226227
{
227228
int rc = 0;
228229

229230
#if defined(CONFIG_NRF_COMPRESS_EXTERNAL_DICTIONARY)
230-
if (inst == NULL) {
231+
if (inst == NULL || ext_dict != NULL) {
231232
return -EINVAL;
232233
}
233234

@@ -244,7 +245,7 @@ static int lzma_init(void *inst)
244245
&& !defined(CONFIG_NRF_COMPRESS_EXTERNAL_DICTIONARY)
245246
if (lzma_dict != NULL) {
246247
/* Already allocated */
247-
lzma_reset(inst);
248+
lzma_reset(inst, decompressed_size);
248249

249250
return rc;
250251
}
@@ -260,6 +261,11 @@ static int lzma_init(void *inst)
260261
rc = -ENOMEM;
261262
}
262263
#endif
264+
if (lzma_output_limit != SIZE_MAX) {
265+
lzma_reset(inst, decompressed_size);
266+
} else {
267+
lzma_output_limit = decompressed_size != NULL ? *decompressed_size : SIZE_MAX;
268+
}
263269

264270
return rc;
265271
}
@@ -283,8 +289,9 @@ static int lzma_deinit(void *inst)
283289
lzma_dict = NULL;
284290
}
285291
#endif
292+
size_t const decompress_size = SIZE_MAX;
286293

287-
rc = lzma_reset(inst);
294+
rc = lzma_reset(inst, &decompress_size);
288295

289296
#if defined(CONFIG_NRF_COMPRESS_EXTERNAL_DICTIONARY)
290297
ext_dict = NULL;
@@ -293,7 +300,7 @@ static int lzma_deinit(void *inst)
293300
return rc;
294301
}
295302

296-
static int lzma_reset(void *inst)
303+
static int lzma_reset(void *inst, const size_t *decompressed_size)
297304
{
298305
int rc = check_inst(inst);
299306

@@ -330,6 +337,8 @@ static int lzma_reset(void *inst)
330337
#endif
331338
}
332339

340+
lzma_output_limit = decompressed_size != NULL ? *decompressed_size : SIZE_MAX;
341+
333342
return rc;
334343
}
335344

@@ -362,6 +371,15 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
362371
int rc;
363372
ELzmaStatus status;
364373
size_t chunk_size = input_size;
374+
ELzmaFinishMode finish_mode = LZMA_FINISH_ANY;
375+
SizeT dic_limit = MAX_LZMA_DICT_SIZE;
376+
377+
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2 /* For convenience */
378+
SizeT *dic_pos = &lzma_decoder.decoder.dicPos;
379+
#else
380+
SizeT *dic_pos = &lzma_decoder.dicPos;
381+
#endif
382+
SizeT curr_dic_pos = *dic_pos;
365383

366384
ARG_UNUSED(inst);
367385

@@ -388,22 +406,20 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
388406
#endif
389407

390408
if (rc) {
391-
rc = -EINVAL;
392-
goto done;
409+
return -EINVAL;
393410
}
394411

395412
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2
396413
if (lzma_decoder.decoder.prop.dicSize > MAX_LZMA_DICT_SIZE) {
397414
#else
398415
if (lzma_decoder.prop.dicSize > MAX_LZMA_DICT_SIZE) {
399416
#endif
400-
rc = -EINVAL;
401417
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2
402418
Lzma2Dec_FreeProbs(&lzma_decoder, &lzma_probs_allocator);
403419
#else
404420
LzmaDec_FreeProbs(&lzma_decoder, &lzma_probs_allocator);
405421
#endif
406-
goto done;
422+
return -EINVAL;
407423
}
408424

409425
allocated_probs = true;
@@ -427,45 +443,58 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
427443
return 0;
428444
}
429445

446+
if (MAX_LZMA_DICT_SIZE - curr_dic_pos >= lzma_output_limit) {
447+
/* Limit the output size because we are reaching
448+
* the limit of expected decompressed data size.
449+
*/
450+
finish_mode = LZMA_FINISH_END;
451+
dic_limit = lzma_output_limit + curr_dic_pos;
452+
}
453+
430454
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2
431-
rc = Lzma2Dec_DecodeToDic(&lzma_decoder, MAX_LZMA_DICT_SIZE, input, &chunk_size,
432-
(last_part ? LZMA_FINISH_END : LZMA_FINISH_ANY), &status);
455+
rc = Lzma2Dec_DecodeToDic(&lzma_decoder, dic_limit,
456+
input, &chunk_size, finish_mode, &status);
433457
#else
434-
rc = LzmaDec_DecodeToDic(&lzma_decoder, MAX_LZMA_DICT_SIZE, input, &chunk_size,
435-
(last_part ? LZMA_FINISH_END : LZMA_FINISH_ANY), &status);
458+
rc = LzmaDec_DecodeToDic(&lzma_decoder, dic_limit,
459+
input, &chunk_size, finish_mode, &status);
436460
#endif
437461

438-
if (rc) {
439-
rc = -EINVAL;
440-
goto done;
462+
if (rc || chunk_size == 0) {
463+
return -EINVAL;
441464
}
442465

443466
*offset = chunk_size;
467+
lzma_output_limit -= (*dic_pos - curr_dic_pos);
444468

445-
if (last_part && (status == LZMA_STATUS_FINISHED_WITH_MARK ||
446-
status == LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) &&
469+
if (last_part && status == LZMA_STATUS_FINISHED_WITH_MARK &&
447470
*offset < input_size) {
448471
/* If last block, ensure offset matches complete file size */
449472
*offset = input_size;
450473
}
451474

475+
if (last_part && *offset == input_size) {
476+
/* Check status of decompression on end of input stream.
477+
* We accept LZMA_STATUS_NEEDS_MORE_INPUT if we reached
478+
* the output limit (and only then) because we don't enforce that
479+
* end-of-stream marker needs to be present in the compressed data.
480+
*/
481+
if (status != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK
482+
&& status != LZMA_STATUS_FINISHED_WITH_MARK
483+
&& (status != LZMA_STATUS_NEEDS_MORE_INPUT && lzma_output_limit == 0)) {
484+
return -EINVAL;
485+
}
486+
}
487+
488+
if (*dic_pos >= MAX_LZMA_DICT_SIZE || last_part) {
452489
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2
453-
if (lzma_decoder.decoder.dicPos >= lzma_decoder.decoder.dicBufSize ||
454-
(last_part && input_size == *offset)) {
455490
*output = lzma_decoder.decoder.dic;
456-
*output_size = lzma_decoder.decoder.dicPos;
457-
lzma_decoder.decoder.dicPos = 0;
458-
}
459491
#else
460-
if (lzma_decoder.dicPos >= lzma_decoder.dicBufSize ||
461-
(last_part && input_size == *offset)) {
462492
*output = lzma_decoder.dic;
463-
*output_size = lzma_decoder.dicPos;
464-
lzma_decoder.dicPos = 0;
465-
}
466493
#endif
494+
*output_size = *dic_pos;
495+
*dic_pos = 0;
496+
}
467497

468-
done:
469498
return rc;
470499
}
471500
#else
@@ -476,6 +505,9 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
476505
ELzmaStatus status;
477506
size_t chunk_size = input_size;
478507
CLzmaDec *decoder;
508+
ELzmaFinishMode finish_mode = LZMA_FINISH_ANY;
509+
SizeT dic_limit;
510+
SizeT curr_dic_pos;
479511

480512
rc = check_inst(inst);
481513

@@ -496,6 +528,7 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
496528
#else
497529
decoder = &lzma_decoder;
498530
#endif
531+
curr_dic_pos = decoder->dicPos;
499532

500533
if (!allocated_probs) {
501534
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2
@@ -538,27 +571,50 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
538571
return 0;
539572
}
540573

574+
if (decoder->dicHandle->dicBufSize - curr_dic_pos >= lzma_output_limit) {
575+
/* Limit the output size because we are reaching
576+
* the limit of expected decompressed data size.
577+
*/
578+
finish_mode = LZMA_FINISH_END;
579+
dic_limit = lzma_output_limit + curr_dic_pos;
580+
} else {
581+
dic_limit = decoder->dicHandle->dicBufSize;
582+
}
583+
541584
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2
542-
rc = Lzma2Dec_DecodeToDic(&lzma_decoder, decoder->dicHandle->dicBufSize, input, &chunk_size,
543-
LZMA_FINISH_ANY, &status);
585+
rc = Lzma2Dec_DecodeToDic(&lzma_decoder, dic_limit,
586+
input, &chunk_size, finish_mode, &status);
544587
#else
545-
rc = LzmaDec_DecodeToDic(&lzma_decoder, decoder->dicHandle->dicBufSize, input, &chunk_size,
546-
LZMA_FINISH_ANY, &status);
588+
rc = LzmaDec_DecodeToDic(&lzma_decoder, dic_limit,
589+
input, &chunk_size, finish_mode, &status);
547590
#endif
548-
if (rc) {
591+
if (rc || chunk_size == 0) {
549592
return -EINVAL;
550593
}
551594

552595
*offset = chunk_size;
596+
lzma_output_limit -= (decoder->dicPos - curr_dic_pos);
553597

554-
if (last_part && (status == LZMA_STATUS_FINISHED_WITH_MARK) &&
598+
if (last_part && status == LZMA_STATUS_FINISHED_WITH_MARK &&
555599
*offset < input_size) {
556600
/* If last block, ensure offset matches complete file size. */
557601
*offset = input_size;
558602
}
559603

560-
if (decoder->dicPos >= decoder->dicHandle->dicBufSize ||
561-
(last_part && input_size == *offset)) {
604+
if (last_part && *offset == input_size) {
605+
/* Check status of decompression on end of input stream.
606+
* We accept LZMA_STATUS_NEEDS_MORE_INPUT if we reached
607+
* the output limit (and only then) because we don't enforce that
608+
* end-of-stream marker needs to be present in the compressed data.
609+
*/
610+
if (status != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK
611+
&& status != LZMA_STATUS_FINISHED_WITH_MARK
612+
&& (status != LZMA_STATUS_NEEDS_MORE_INPUT && lzma_output_limit == 0)) {
613+
return -EINVAL;
614+
}
615+
}
616+
617+
if (decoder->dicPos >= decoder->dicHandle->dicBufSize || last_part) {
562618
#if CONFIG_NRF_COMPRESS_DICTIONARY_CACHE_SIZE > 0
563619
if (cache.invalid) {
564620
rc = synchronize_cache(decoder->dicHandle);

0 commit comments

Comments
 (0)