Skip to content

Commit cf4a214

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 cf4a214

File tree

8 files changed

+245
-94
lines changed

8 files changed

+245
-94
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-39
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,8 @@ static int lzma_deinit(void *inst)
283289
lzma_dict = NULL;
284290
}
285291
#endif
286-
287-
rc = lzma_reset(inst);
292+
size_t const decompress_size = SIZE_MAX;
293+
rc = lzma_reset(inst, &decompress_size);
288294

289295
#if defined(CONFIG_NRF_COMPRESS_EXTERNAL_DICTIONARY)
290296
ext_dict = NULL;
@@ -293,7 +299,7 @@ static int lzma_deinit(void *inst)
293299
return rc;
294300
}
295301

296-
static int lzma_reset(void *inst)
302+
static int lzma_reset(void *inst, const size_t *decompressed_size)
297303
{
298304
int rc = check_inst(inst);
299305

@@ -330,6 +336,8 @@ static int lzma_reset(void *inst)
330336
#endif
331337
}
332338

339+
lzma_output_limit = decompressed_size != NULL ? *decompressed_size : SIZE_MAX;
340+
333341
return rc;
334342
}
335343

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

366383
ARG_UNUSED(inst);
367384

@@ -388,22 +405,20 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
388405
#endif
389406

390407
if (rc) {
391-
rc = -EINVAL;
392-
goto done;
408+
return -EINVAL;
393409
}
394410

395411
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2
396412
if (lzma_decoder.decoder.prop.dicSize > MAX_LZMA_DICT_SIZE) {
397413
#else
398414
if (lzma_decoder.prop.dicSize > MAX_LZMA_DICT_SIZE) {
399415
#endif
400-
rc = -EINVAL;
401416
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2
402417
Lzma2Dec_FreeProbs(&lzma_decoder, &lzma_probs_allocator);
403418
#else
404419
LzmaDec_FreeProbs(&lzma_decoder, &lzma_probs_allocator);
405420
#endif
406-
goto done;
421+
return -EINVAL;
407422
}
408423

409424
allocated_probs = true;
@@ -427,45 +442,58 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
427442
return 0;
428443
}
429444

445+
if (MAX_LZMA_DICT_SIZE - curr_dic_pos >= lzma_output_limit) {
446+
/* Limit the output size because we are reaching
447+
* the limit of expected decompressed data size.
448+
*/
449+
finish_mode = LZMA_FINISH_END;
450+
dic_limit = lzma_output_limit + curr_dic_pos;
451+
}
452+
430453
#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);
454+
rc = Lzma2Dec_DecodeToDic(&lzma_decoder, dic_limit,
455+
input, &chunk_size, finish_mode, &status);
433456
#else
434-
rc = LzmaDec_DecodeToDic(&lzma_decoder, MAX_LZMA_DICT_SIZE, input, &chunk_size,
435-
(last_part ? LZMA_FINISH_END : LZMA_FINISH_ANY), &status);
457+
rc = LzmaDec_DecodeToDic(&lzma_decoder, dic_limit,
458+
input, &chunk_size, finish_mode, &status);
436459
#endif
437460

438-
if (rc) {
439-
rc = -EINVAL;
440-
goto done;
461+
if (rc || chunk_size == 0) {
462+
return -EINVAL;
441463
}
442464

443465
*offset = chunk_size;
466+
lzma_output_limit -= (*dic_pos - curr_dic_pos);
444467

445-
if (last_part && (status == LZMA_STATUS_FINISHED_WITH_MARK ||
446-
status == LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) &&
468+
if (last_part && status == LZMA_STATUS_FINISHED_WITH_MARK &&
447469
*offset < input_size) {
448470
/* If last block, ensure offset matches complete file size */
449471
*offset = input_size;
450472
}
451473

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

468-
done:
469497
return rc;
470498
}
471499
#else
@@ -476,6 +504,9 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
476504
ELzmaStatus status;
477505
size_t chunk_size = input_size;
478506
CLzmaDec *decoder;
507+
ELzmaFinishMode finish_mode = LZMA_FINISH_ANY;
508+
SizeT dic_limit;
509+
SizeT curr_dic_pos;
479510

480511
rc = check_inst(inst);
481512

@@ -496,6 +527,7 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
496527
#else
497528
decoder = &lzma_decoder;
498529
#endif
530+
curr_dic_pos = decoder->dicPos;
499531

500532
if (!allocated_probs) {
501533
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2
@@ -538,27 +570,50 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
538570
return 0;
539571
}
540572

573+
if (decoder->dicHandle->dicBufSize - curr_dic_pos >= lzma_output_limit) {
574+
/* Limit the output size because we are reaching
575+
* the limit of expected decompressed data size.
576+
*/
577+
finish_mode = LZMA_FINISH_END;
578+
dic_limit = lzma_output_limit + curr_dic_pos;
579+
} else {
580+
dic_limit = decoder->dicHandle->dicBufSize;
581+
}
582+
541583
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2
542-
rc = Lzma2Dec_DecodeToDic(&lzma_decoder, decoder->dicHandle->dicBufSize, input, &chunk_size,
543-
LZMA_FINISH_ANY, &status);
584+
rc = Lzma2Dec_DecodeToDic(&lzma_decoder, dic_limit,
585+
input, &chunk_size, finish_mode, &status);
544586
#else
545-
rc = LzmaDec_DecodeToDic(&lzma_decoder, decoder->dicHandle->dicBufSize, input, &chunk_size,
546-
LZMA_FINISH_ANY, &status);
587+
rc = LzmaDec_DecodeToDic(&lzma_decoder, dic_limit,
588+
input, &chunk_size, finish_mode, &status);
547589
#endif
548-
if (rc) {
590+
if (rc || chunk_size == 0) {
549591
return -EINVAL;
550592
}
551593

552594
*offset = chunk_size;
595+
lzma_output_limit -= (decoder->dicPos - curr_dic_pos);
553596

554-
if (last_part && (status == LZMA_STATUS_FINISHED_WITH_MARK) &&
597+
if (last_part && status == LZMA_STATUS_FINISHED_WITH_MARK &&
555598
*offset < input_size) {
556599
/* If last block, ensure offset matches complete file size. */
557600
*offset = input_size;
558601
}
559602

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

0 commit comments

Comments
 (0)