Skip to content

Commit 4e991b4

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 fd612d5 commit 4e991b4

File tree

8 files changed

+242
-95
lines changed

8 files changed

+242
-95
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 0 if the size is not known;
41+
* there will be no output 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, 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 0 if the size is not known;
70+
* there will be no output 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, 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, size_t decompressed_size);
29+
30+
static int arm_thumb_init(void *inst, size_t decompressed_size)
2831
{
32+
output_limit = decompressed_size != 0 ? 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, size_t decompressed_size)
4550
{
51+
output_limit = decompressed_size != 0 ? 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 -= *offset;
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

+90-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, size_t decompressed_size);
224225

225-
static int lzma_init(void *inst)
226+
static int lzma_init(void *inst, 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
}
@@ -261,6 +262,8 @@ static int lzma_init(void *inst)
261262
}
262263
#endif
263264

265+
lzma_output_limit = decompressed_size != 0 ? decompressed_size : SIZE_MAX;
266+
264267
return rc;
265268
}
266269

@@ -283,8 +286,7 @@ static int lzma_deinit(void *inst)
283286
lzma_dict = NULL;
284287
}
285288
#endif
286-
287-
rc = lzma_reset(inst);
289+
rc = lzma_reset(inst, 0);
288290

289291
#if defined(CONFIG_NRF_COMPRESS_EXTERNAL_DICTIONARY)
290292
ext_dict = NULL;
@@ -293,7 +295,7 @@ static int lzma_deinit(void *inst)
293295
return rc;
294296
}
295297

296-
static int lzma_reset(void *inst)
298+
static int lzma_reset(void *inst, size_t decompressed_size)
297299
{
298300
int rc = check_inst(inst);
299301

@@ -330,6 +332,8 @@ static int lzma_reset(void *inst)
330332
#endif
331333
}
332334

335+
lzma_output_limit = decompressed_size != 0 ? decompressed_size : SIZE_MAX;
336+
333337
return rc;
334338
}
335339

@@ -362,6 +366,15 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
362366
int rc;
363367
ELzmaStatus status;
364368
size_t chunk_size = input_size;
369+
ELzmaFinishMode finish_mode = LZMA_FINISH_ANY;
370+
SizeT dic_limit = MAX_LZMA_DICT_SIZE;
371+
372+
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2 /* For convenience */
373+
SizeT *dic_pos = &lzma_decoder.decoder.dicPos;
374+
#else
375+
SizeT *dic_pos = &lzma_decoder.dicPos;
376+
#endif
377+
SizeT curr_dic_pos = *dic_pos;
365378

366379
ARG_UNUSED(inst);
367380

@@ -388,22 +401,20 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
388401
#endif
389402

390403
if (rc) {
391-
rc = -EINVAL;
392-
goto done;
404+
return -EINVAL;
393405
}
394406

395407
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2
396408
if (lzma_decoder.decoder.prop.dicSize > MAX_LZMA_DICT_SIZE) {
397409
#else
398410
if (lzma_decoder.prop.dicSize > MAX_LZMA_DICT_SIZE) {
399411
#endif
400-
rc = -EINVAL;
401412
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2
402413
Lzma2Dec_FreeProbs(&lzma_decoder, &lzma_probs_allocator);
403414
#else
404415
LzmaDec_FreeProbs(&lzma_decoder, &lzma_probs_allocator);
405416
#endif
406-
goto done;
417+
return -EINVAL;
407418
}
408419

409420
allocated_probs = true;
@@ -427,45 +438,58 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
427438
return 0;
428439
}
429440

441+
if (MAX_LZMA_DICT_SIZE - curr_dic_pos >= lzma_output_limit) {
442+
/* Limit the output size because we are reaching
443+
* the limit of expected decompressed data size.
444+
*/
445+
finish_mode = LZMA_FINISH_END;
446+
dic_limit = lzma_output_limit + curr_dic_pos;
447+
}
448+
430449
#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);
450+
rc = Lzma2Dec_DecodeToDic(&lzma_decoder, dic_limit,
451+
input, &chunk_size, finish_mode, &status);
433452
#else
434-
rc = LzmaDec_DecodeToDic(&lzma_decoder, MAX_LZMA_DICT_SIZE, input, &chunk_size,
435-
(last_part ? LZMA_FINISH_END : LZMA_FINISH_ANY), &status);
453+
rc = LzmaDec_DecodeToDic(&lzma_decoder, dic_limit,
454+
input, &chunk_size, finish_mode, &status);
436455
#endif
437456

438-
if (rc) {
439-
rc = -EINVAL;
440-
goto done;
457+
if (rc || chunk_size == 0) {
458+
return -EINVAL;
441459
}
442460

443461
*offset = chunk_size;
462+
lzma_output_limit -= (*dic_pos - curr_dic_pos);
444463

445-
if (last_part && (status == LZMA_STATUS_FINISHED_WITH_MARK ||
446-
status == LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) &&
464+
if (last_part && status == LZMA_STATUS_FINISHED_WITH_MARK &&
447465
*offset < input_size) {
448466
/* If last block, ensure offset matches complete file size */
449467
*offset = input_size;
450468
}
451469

470+
if (last_part && *offset == input_size) {
471+
/* Check status of decompression on end of input stream.
472+
* We accept LZMA_STATUS_NEEDS_MORE_INPUT if we reached
473+
* the output limit (and only then) because we don't enforce that
474+
* end-of-stream marker needs to be present in the compressed data.
475+
*/
476+
if (status != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK
477+
&& status != LZMA_STATUS_FINISHED_WITH_MARK
478+
&& (status != LZMA_STATUS_NEEDS_MORE_INPUT && lzma_output_limit == 0)) {
479+
return -EINVAL;
480+
}
481+
}
482+
483+
if (*dic_pos >= MAX_LZMA_DICT_SIZE || last_part) {
452484
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2
453-
if (lzma_decoder.decoder.dicPos >= lzma_decoder.decoder.dicBufSize ||
454-
(last_part && input_size == *offset)) {
455485
*output = lzma_decoder.decoder.dic;
456-
*output_size = lzma_decoder.decoder.dicPos;
457-
lzma_decoder.decoder.dicPos = 0;
458-
}
459486
#else
460-
if (lzma_decoder.dicPos >= lzma_decoder.dicBufSize ||
461-
(last_part && input_size == *offset)) {
462487
*output = lzma_decoder.dic;
463-
*output_size = lzma_decoder.dicPos;
464-
lzma_decoder.dicPos = 0;
465-
}
466488
#endif
489+
*output_size = *dic_pos;
490+
*dic_pos = 0;
491+
}
467492

468-
done:
469493
return rc;
470494
}
471495
#else
@@ -476,6 +500,9 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
476500
ELzmaStatus status;
477501
size_t chunk_size = input_size;
478502
CLzmaDec *decoder;
503+
ELzmaFinishMode finish_mode = LZMA_FINISH_ANY;
504+
SizeT dic_limit;
505+
SizeT curr_dic_pos;
479506

480507
rc = check_inst(inst);
481508

@@ -496,6 +523,7 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
496523
#else
497524
decoder = &lzma_decoder;
498525
#endif
526+
curr_dic_pos = decoder->dicPos;
499527

500528
if (!allocated_probs) {
501529
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2
@@ -538,27 +566,50 @@ static int lzma_decompress(void *inst, const uint8_t *input, size_t input_size,
538566
return 0;
539567
}
540568

569+
if (decoder->dicHandle->dicBufSize - curr_dic_pos >= lzma_output_limit) {
570+
/* Limit the output size because we are reaching
571+
* the limit of expected decompressed data size.
572+
*/
573+
finish_mode = LZMA_FINISH_END;
574+
dic_limit = lzma_output_limit + curr_dic_pos;
575+
} else {
576+
dic_limit = decoder->dicHandle->dicBufSize;
577+
}
578+
541579
#ifdef CONFIG_NRF_COMPRESS_LZMA_VERSION_LZMA2
542-
rc = Lzma2Dec_DecodeToDic(&lzma_decoder, decoder->dicHandle->dicBufSize, input, &chunk_size,
543-
LZMA_FINISH_ANY, &status);
580+
rc = Lzma2Dec_DecodeToDic(&lzma_decoder, dic_limit,
581+
input, &chunk_size, finish_mode, &status);
544582
#else
545-
rc = LzmaDec_DecodeToDic(&lzma_decoder, decoder->dicHandle->dicBufSize, input, &chunk_size,
546-
LZMA_FINISH_ANY, &status);
583+
rc = LzmaDec_DecodeToDic(&lzma_decoder, dic_limit,
584+
input, &chunk_size, finish_mode, &status);
547585
#endif
548-
if (rc) {
586+
if (rc || chunk_size == 0) {
549587
return -EINVAL;
550588
}
551589

552590
*offset = chunk_size;
591+
lzma_output_limit -= (decoder->dicPos - curr_dic_pos);
553592

554-
if (last_part && (status == LZMA_STATUS_FINISHED_WITH_MARK) &&
593+
if (last_part && status == LZMA_STATUS_FINISHED_WITH_MARK &&
555594
*offset < input_size) {
556595
/* If last block, ensure offset matches complete file size. */
557596
*offset = input_size;
558597
}
559598

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

0 commit comments

Comments
 (0)