ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_decomp_limits.h File Reference

Unified decompression-limits policy: one bound set for every decoder. More...

#include <stddef.h>
#include <stdint.h>
#include "ra8_err.h"
Include dependency graph for ra8_decomp_limits.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  ra8_decomp_limits_t
 One decompression policy: the five resource bounds decoders enforce. More...
struct  ra8_decomp_budget_t
 Running consumption tracker charged by a decoder against its policy. More...

Typedefs

typedef size_t(* ra8_decomp_read_fn) (void *ctx, uint64_t offset, void *buf, size_t len)
 Positioned reader used by bounded container preflights.

Enumerations

enum  ra8_decomp_defaults_t : uint64_t {
  k_ra8_decomp_def_output_bytes = 64ULL * 1024ULL * 1024ULL ,
  k_ra8_decomp_def_max_ratio = 1024U ,
  k_ra8_decomp_def_ratio_grace = 64U * 1024U ,
  k_ra8_decomp_def_max_entries = 4096U ,
  k_ra8_decomp_def_max_iters = 1048576U ,
  k_ra8_decomp_def_max_depth = 2U
}
 Owner-approved default values for ra8_decomp_limits_t. More...

Functions

ra8_decomp_limits_t ra8_decomp_limits_default (void)
 The owner-approved default decompression policy.
ra8_err_t ra8_decomp_budget_init (ra8_decomp_budget_t *b, const ra8_decomp_limits_t *limits)
 Bind a budget to a policy (or the default policy) and zero it.
ra8_err_t ra8_decomp_budget_charge_output (ra8_decomp_budget_t *b, uint64_t in_total, uint64_t out_delta)
 Charge decompressed output against the cap and ratio bounds.
ra8_err_t ra8_decomp_budget_charge_entry (ra8_decomp_budget_t *b)
 Charge one enumerated archive entry against the entry cap.
ra8_err_t ra8_decomp_budget_charge_iter (ra8_decomp_budget_t *b)
 Charge one decode-loop turn against the iteration budget.
ra8_err_t ra8_decomp_budget_enter (ra8_decomp_budget_t *b)
 Enter one stacked decode layer (nesting-depth guard).
void ra8_decomp_budget_leave (ra8_decomp_budget_t *b)
 Leave one stacked decode layer (balances ra8_decomp_budget_enter).
ra8_err_t ra8_decomp_check_declared (const ra8_decomp_limits_t *limits, uint64_t comp_size, uint64_t out_size)
 Header-level check of a member's declared sizes against a policy.
ra8_err_t ra8_decomp_zip_entry_preflight (ra8_decomp_read_fn read, void *ctx, uint64_t archive_size)
 Reject an over-cap ZIP from its EOCD before directory allocation.

Detailed Description

Unified decompression-limits policy: one bound set for every decoder.

Every archive / compressed-stream decoder in this firmware (ZIP-store and DEFLATE via miniz, RAR4/RAR5, gzip, XZ/LZMA2, tar, and the raw-DEFLATE buffer path in ra8_compress.h) consumes untrusted SD-card content and must be bounded and fail-closed on any hostile input. This header is the single enforcement seam they all share: one policy record (ra8_decomp_limits_t), one running-budget tracker (ra8_decomp_budget_t), and one header-level declared-size check (ra8_decomp_check_declared).

The threat model (owner-approved) is not remote code execution – it is a malicious or malformed archive crashing the reader or exhausting RAM/CPU. The policy therefore bounds the five resource axes a decompressor can be driven along:

Axis Field Breach error
Total output bytes max_output_bytes k_ra8_err_decomp_output_cap
Compression ratio max_ratio k_ra8_err_decomp_ratio
Archive entry count max_entries k_ra8_err_decomp_entries
Container nesting max_depth k_ra8_err_decomp_depth
Decode-loop turns max_iterations k_ra8_err_decomp_iterations
Semantics of each bound
  • max_output_bytes applies per decode unit: one archive member or one wrapped stream (a .tar.gz's whole decompressed tar is one unit). It is NOT a lifetime sum, so re-reading pages of a long comic never exhausts it.
  • max_ratio is enforced as output <= (input * max_ratio) + ratio_grace_bytes. The additive grace keeps tiny legitimate inputs (a 40-byte gzip of a 4 KiB file) from tripping a pure quotient, while a real decompression bomb blows through both terms immediately.
  • max_entries applies per archive enumeration (central directory walk, RAR block walk, tar header walk).
  • max_depth counts stacked decode layers (stream-in-stream). Readers never recurse into member archives by construction, so depth only guards the wrapper plumbing (e.g. gzip -> tar is depth 2).
  • max_iterations is a NASA P10 Rule 2 backstop on every decode loop whose trip count depends on untrusted bytes: each pass charges one iteration, so a stream engineered to make no progress terminates.

Header-declared sizes (a ZIP central-directory record, a RAR block header, a tar size field) are checked against the same policy before any decoding starts (ra8_decomp_check_declared) – a lying header is rejected as cheaply as an honest bomb.

Note
All checks fail closed: the first breach returns a specific k_ra8_err_decomp_* code and the decoder must stop. Reject and continue; never crash.
See also
ra8_unarch_gzip.h gzip member decoder built on this policy.
ra8_unarch_tar.h tar walker built on this policy.
ra8_unarch_xz.h XZ/LZMA2 decoder built on this policy.
ra8_comic.h Comic facade whose CBZ/CBR/CBT backends charge it.
Since
Version 0.1.0

Definition in file ra8_decomp_limits.h.

Typedef Documentation

◆ ra8_decomp_read_fn

typedef size_t(* ra8_decomp_read_fn) (void *ctx, uint64_t offset, void *buf, size_t len)

Positioned reader used by bounded container preflights.

Reads from an immutable seekable container without requiring a resident blob.

Parameters
[in]ctxOpaque backing context.
[in]offsetAbsolute byte offset in the container.
[out]bufDestination for len bytes.
[in]lenRequested byte count.
Returns
Bytes actually read; a short result leaves validation to the format decoder.
Note
The callback must not retain buf.
Since
Version 0.1.0

Definition at line 403 of file ra8_decomp_limits.h.

Enumeration Type Documentation

◆ ra8_decomp_defaults_t

enum ra8_decomp_defaults_t : uint64_t

Owner-approved default values for ra8_decomp_limits_t.

One 64-bit-typed enum so every default is a named constant (no magic numbers) in a single place. ra8_decomp_limits_default() returns a policy built from exactly these values. Rationale per value:

  • output cap 64 MiB: the SDRAM working-set ceiling – no single decode unit may exceed the board's big memory.
  • ratio 1024:1 (+64 KiB grace): above DEFLATE's theoretical ~1032:1 per-layer maximum lies only bomb territory; the grace admits small honest files whose fixed headers skew the ratio.
  • 4096 entries: an order of magnitude above the largest real comic volume / EPUB spine.
  • depth 2: wrapper stream + inner container (.tar.gz); nothing legitimate stacks deeper.
  • 1 Mi iterations: decode loops charge one turn per bounded work chunk (>= one input or output block), far below this for any in-cap stream.
    Since
    Version 0.1.0
Enumerator
k_ra8_decomp_def_output_bytes 

Per-unit output cap (64 MiB).

k_ra8_decomp_def_max_ratio 

Output:input ratio bound.

k_ra8_decomp_def_ratio_grace 

Additive ratio grace (64 KiB).

k_ra8_decomp_def_max_entries 

Per-archive entry cap.

k_ra8_decomp_def_max_iters 

Loop iteration budget (1 Mi).

k_ra8_decomp_def_max_depth 

Stacked decode-layer cap.

Definition at line 97 of file ra8_decomp_limits.h.

Function Documentation

◆ ra8_decomp_budget_charge_entry()

ra8_err_t ra8_decomp_budget_charge_entry ( ra8_decomp_budget_t * b)
nodiscard

Charge one enumerated archive entry against the entry cap.

Walkers call this once per member they index (ZIP central directory record, RAR file block, tar header). The many-tiny-entries bomb is rejected the moment the count crosses max_entries.

Parameters
[in,out]bInitialised budget (non-NULL).
Returns
ra8_err_t Error code.
Return values
k_ra8_okEntry recorded within the cap.
k_ra8_err_null_ptrb was NULL.
k_ra8_err_decomp_entriesThe entry count now exceeds max_entries.
Precondition
b was initialised by ra8_decomp_budget_init.
The caller charges exactly once per enumerated member.
Postcondition
On k_ra8_ok, b->entries grew by one.
On breach the walker must stop and reject the archive.
Note
Not thread-safe with respect to the same budget.
See also
ra8_decomp_budget_charge_iter()
Since
Version 0.1.0

Definition at line 176 of file ra8_decomp_limits.c.

References k_ra8_err_decomp_entries, k_ra8_ok, RA8_CHECK_NULL_PTR, and s_tag_decomp.

Referenced by internal_finish_member(), and priv_comic_cbr_open().

◆ ra8_decomp_budget_charge_iter()

ra8_err_t ra8_decomp_budget_charge_iter ( ra8_decomp_budget_t * b)
nodiscard

Charge one decode-loop turn against the iteration budget.

The NASA P10 Rule 2 backstop for loops whose trip count depends on untrusted bytes: each pass through such a loop charges one turn, so a stream engineered to stall (no input consumed, no output produced) still terminates within max_iterations.

Parameters
[in,out]bInitialised budget (non-NULL).
Returns
ra8_err_t Error code.
Return values
k_ra8_okTurn recorded within the budget.
k_ra8_err_null_ptrb was NULL.
k_ra8_err_decomp_iterationsThe turn count now exceeds max_iterations.
Precondition
b was initialised by ra8_decomp_budget_init.
The charging loop performs bounded work per turn.
Postcondition
On k_ra8_ok, b->iters grew by one.
On breach the decode loop must stop fail-closed.
Note
Not thread-safe with respect to the same budget.
See also
ra8_decomp_budget_charge_entry()
Since
Version 0.1.0

Definition at line 186 of file ra8_decomp_limits.c.

References k_ra8_err_decomp_iterations, k_ra8_ok, RA8_CHECK_NULL_PTR, and s_tag_decomp.

Referenced by internal_inflate_pass(), internal_unwrap_pass(), and unarch_tar_next().

◆ ra8_decomp_budget_charge_output()

ra8_err_t ra8_decomp_budget_charge_output ( ra8_decomp_budget_t * b,
uint64_t in_total,
uint64_t out_delta )
nodiscard

Charge decompressed output against the cap and ratio bounds.

Adds out_delta to the produced-bytes counter, records the input consumed so far, then enforces (1) the per-unit output cap and (2) the bomb bound out <= in * max_ratio + grace. Call it after every emitted chunk so a bomb is caught within one chunk of the bound, long before RAM or CPU is exhausted.

Parameters
[in,out]bInitialised budget (non-NULL).
[in]in_totalCompressed bytes consumed so far (monotonic).
[in]out_deltaNewly produced decompressed bytes (may be 0).
Returns
ra8_err_t Error code.
Return values
k_ra8_okCharge recorded; both bounds hold.
k_ra8_err_null_ptrb was NULL.
k_ra8_err_decomp_output_capOutput now exceeds max_output_bytes.
k_ra8_err_decomp_ratioOutput now exceeds the ratio bound.
Precondition
b was initialised by ra8_decomp_budget_init.
in_total is non-decreasing across calls on the same budget.
Postcondition
On k_ra8_ok, b->out_bytes includes out_delta.
On any breach the counters still reflect the attempted charge (the decoder must stop; the budget is not reusable after a breach).
Note
Not thread-safe with respect to the same budget.
See also
ra8_decomp_check_declared()
Since
Version 0.1.0

Definition at line 158 of file ra8_decomp_limits.c.

References internal_ratio_bound(), k_ra8_err_decomp_output_cap, k_ra8_err_decomp_ratio, k_ra8_ok, RA8_CHECK_NULL_PTR, and s_tag_decomp.

Referenced by internal_inflate_pass(), and internal_unwrap_pass().

◆ ra8_decomp_budget_enter()

ra8_err_t ra8_decomp_budget_enter ( ra8_decomp_budget_t * b)
nodiscard

Enter one stacked decode layer (nesting-depth guard).

Charge before constructing an inner decoder over an outer one (e.g. the tar walker over a gzip stream view). The archive-in-archive bomb is rejected before the inner layer does any work.

Parameters
[in,out]bInitialised budget (non-NULL).
Returns
ra8_err_t Error code.
Return values
k_ra8_okDepth recorded within max_depth.
k_ra8_err_null_ptrb was NULL.
k_ra8_err_decomp_depthThe depth now exceeds max_depth.
Precondition
b was initialised by ra8_decomp_budget_init.
Every successful enter is balanced by ra8_decomp_budget_leave.
Postcondition
On k_ra8_ok, b->depth grew by one.
On breach no inner decode may start.
Note
Not thread-safe with respect to the same budget.
See also
ra8_decomp_budget_leave()
Since
Version 0.1.0

Definition at line 196 of file ra8_decomp_limits.c.

References k_ra8_err_decomp_depth, k_ra8_ok, RA8_CHECK_NULL_PTR, and s_tag_decomp.

◆ ra8_decomp_budget_init()

ra8_err_t ra8_decomp_budget_init ( ra8_decomp_budget_t * b,
const ra8_decomp_limits_t * limits )
nodiscard

Bind a budget to a policy (or the default policy) and zero it.

Copies limits (or the default when NULL) into b and clears every counter. Rejects a policy with any zero field: a zero cap is always a configuration bug, never a meaningful bound.

Parameters
[out]bBudget to initialise (non-NULL).
[in]limitsPolicy to enforce, or NULL for the default policy.
Returns
ra8_err_t Error code.
Return values
k_ra8_okBudget zeroed and bound to a valid policy.
k_ra8_err_null_ptrb was NULL.
k_ra8_err_invalid_arglimits has a zero field.
Precondition
b addresses a writable ra8_decomp_budget_t.
limits, when non-NULL, has every field non-zero.
Postcondition
On k_ra8_ok all counters are zero and b->limits is the policy.
On any error b is left zeroed (unusable until re-initialised).
Note
Not thread-safe with respect to the same budget.
See also
ra8_decomp_limits_default()
Since
Version 0.1.0

Definition at line 142 of file ra8_decomp_limits.c.

References internal_limits_usable(), k_ra8_err_invalid_arg, k_ra8_ok, RA8_CHECK_NULL_PTR, ra8_decomp_limits_default(), and s_tag_decomp.

Referenced by unarch_gzip_unwrap(), unarch_tar_open(), and unarch_xz_unwrap().

◆ ra8_decomp_budget_leave()

void ra8_decomp_budget_leave ( ra8_decomp_budget_t * b)

Leave one stacked decode layer (balances ra8_decomp_budget_enter).

Decrements the depth counter; a NULL budget or an already-zero depth is ignored (teardown paths call this unconditionally).

Parameters
[in,out]bBudget to unwind (may be NULL).
Precondition
b, when non-NULL, was initialised by ra8_decomp_budget_init.
The call balances a successful ra8_decomp_budget_enter.
Postcondition
A non-zero depth decreased by one.
A NULL b or zero depth left everything unchanged.
Note
Not thread-safe with respect to the same budget.
See also
ra8_decomp_budget_enter()
Since
Version 0.1.0

Definition at line 206 of file ra8_decomp_limits.c.

◆ ra8_decomp_check_declared()

ra8_err_t ra8_decomp_check_declared ( const ra8_decomp_limits_t * limits,
uint64_t comp_size,
uint64_t out_size )
nodiscard

Header-level check of a member's declared sizes against a policy.

Rejects a lying or hostile header before any decoding: the declared decompressed size must fit the per-unit output cap and the ratio bound relative to the declared compressed size. Used by every walker at index time (ZIP central directory, RAR block header, tar size field) so hostile members cost O(1).

Parameters
[in]limitsPolicy to check against (non-NULL, validated fields).
[in]comp_sizeDeclared compressed (packed) size in bytes.
[in]out_sizeDeclared decompressed (unpacked) size in bytes.
Returns
ra8_err_t Error code.
Return values
k_ra8_okBoth declared sizes are within policy.
k_ra8_err_null_ptrlimits was NULL.
k_ra8_err_decomp_output_capout_size exceeds max_output_bytes.
k_ra8_err_decomp_ratioout_size exceeds the ratio bound.
Precondition
limits has every field non-zero (policy from ra8_decomp_limits_default or validated by ra8_decomp_budget_init).
Sizes are the raw header-declared values (untrusted).
Postcondition
No state is modified (pure check).
A k_ra8_ok result guarantees nothing about the actual stream – running enforcement still applies during decode.
Note
Thread-safe: pure read.
See also
ra8_decomp_budget_charge_output()
Since
Version 0.1.0

Definition at line 218 of file ra8_decomp_limits.c.

References internal_ratio_bound(), k_ra8_err_decomp_output_cap, k_ra8_err_decomp_ratio, k_ra8_ok, ra8_decomp_limits_t::max_output_bytes, RA8_CHECK_NULL_PTR, and s_tag_decomp.

Referenced by internal_add_entry(), internal_add_member(), internal_finish_member(), internal_read_page(), internal_require_jof(), priv_epub_zip_guard_entry(), and priv_viewer_open_jof().

◆ ra8_decomp_limits_default()

ra8_decomp_limits_t ra8_decomp_limits_default ( void )
nodiscard

The owner-approved default decompression policy.

Builds a ra8_decomp_limits_t from the ra8_decomp_defaults_t constants. This is the ONE policy every production decoder runs under; tests tighten copies of it to exercise breach paths with small fixtures.

Returns
The default policy by value (all fields non-zero).
Return values
ra8_decomp_limits_tPopulated from ra8_decomp_defaults_t.
Precondition
None – pure constant constructor.
No initialization is required before calling.
Postcondition
Every field of the result is non-zero.
The result validates under ra8_decomp_budget_init.
Note
Thread-safe: returns a value, touches no state.
See also
ra8_decomp_budget_init()
Since
Version 0.1.0

Definition at line 60 of file ra8_decomp_limits.c.

References k_ra8_decomp_def_max_depth, k_ra8_decomp_def_max_entries, k_ra8_decomp_def_max_iters, k_ra8_decomp_def_max_ratio, k_ra8_decomp_def_output_bytes, k_ra8_decomp_def_ratio_grace, ra8_decomp_limits_t::max_depth, ra8_decomp_limits_t::max_entries, ra8_decomp_limits_t::max_iterations, ra8_decomp_limits_t::max_output_bytes, ra8_decomp_limits_t::max_ratio, and ra8_decomp_limits_t::ratio_grace_bytes.

Referenced by internal_add_entry(), internal_add_member(), internal_require_jof(), priv_comic_cbr_open(), priv_comic_cbz_open(), priv_epub_zip_guard_archive(), priv_epub_zip_guard_entry(), priv_viewer_open_jof(), ra8_decomp_budget_init(), and ra8_viewer_reader_bind().

◆ ra8_decomp_zip_entry_preflight()

ra8_err_t ra8_decomp_zip_entry_preflight ( ra8_decomp_read_fn read,
void * ctx,
uint64_t archive_size )
nodiscard

Reject an over-cap ZIP from its EOCD before directory allocation.

Scans only the bounded classic-ZIP comment window in fixed chunks. A valid EOCD whose total-entry field exceeds the shared policy is rejected before a bounded format allocator can obscure the cause with a generic allocation or validation failure.

Parameters
[in]readSeekable container callback.
[in]ctxOpaque callback context.
[in]archive_sizeExact ZIP byte length.
Returns
Preflight status.
Return values
k_ra8_okNo valid over-cap EOCD was found; the ZIP decoder remains authoritative.
k_ra8_err_null_ptrread was NULL.
k_ra8_err_decomp_entriesThe EOCD declares more than 4096 entries.
Precondition
archive_size is the same size later supplied to the ZIP decoder.
A successful callback returns exactly the requested byte count.
Postcondition
No archive byte or callback context is modified by this function.
Read failures do not replace the ZIP decoder's final validation result.
Note
ZIP64's saturated 16-bit entry count is necessarily above this policy.
Since
Version 0.1.0

Definition at line 305 of file ra8_decomp_limits.c.

References internal_zip_scan_window_for_eocd(), k_priv_zip_comment_max, k_priv_zip_eocd_bytes, k_priv_zip_scan_chunk, k_priv_zip_sig_bytes, k_ra8_ok, RA8_CHECK_NULL_PTR, and s_tag_decomp.

Referenced by epub_open(), epub_open_streamed(), and priv_comic_cbz_open().