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

Unified decompression-limits policy – budget charging and checks. More...

#include "ra8_decomp_limits.h"
#include <string.h>
#include "ra8_attributes.h"
#include "ra8_check.h"
Include dependency graph for ra8_decomp_limits.c:

Go to the source code of this file.

Enumerations

enum  priv_zip_preflight_t : uint32_t {
  k_priv_zip_eocd_bytes = 22U ,
  k_priv_zip_comment_max = 65535U ,
  k_priv_zip_scan_chunk = 512U ,
  k_priv_zip_sig_bytes = 4U ,
  k_priv_zip_entries_offset = 10U ,
  k_priv_zip_comment_offset = 20U
}
 Fixed classic-ZIP EOCD geometry used by the entry-cap preflight. More...
enum  priv_zip_signature_t : uint8_t {
  k_priv_zip_sig_p = 0x50U ,
  k_priv_zip_sig_k = 0x4BU ,
  k_priv_zip_sig_eocd = 0x05U ,
  k_priv_zip_sig_fixed = 0x06U
}
 Classic ZIP EOCD signature bytes. More...

Functions

ra8_decomp_limits_t ra8_decomp_limits_default (void)
 The owner-approved default decompression policy.
static bool internal_limits_usable (const ra8_decomp_limits_t *lim)
 Whether every field of a policy is non-zero (usable as a bound).
static uint64_t internal_ratio_bound (const ra8_decomp_limits_t *lim, uint64_t in_total)
 The saturating ratio bound in * max_ratio + grace for a 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.
static uint16_t internal_zip_u16 (const uint8_t *bytes)
 Decode one little-endian 16-bit ZIP field.
static bool internal_zip_scan_window_for_eocd (ra8_decomp_read_fn read, void *ctx, const uint8_t *chunk, uint64_t start, size_t count, uint64_t archive_size, ra8_err_t *out_status)
 Search one loaded scan window backward for a verified ZIP EOCD record.
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.

Variables

static const char *const s_tag_decomp = "ra8_decomp"
 Log tag for decompression-policy diagnostics.
static const uint8_t s_zip_eocd_signature [k_priv_zip_sig_bytes]
 Classic ZIP end-of-central-directory signature bytes.

Detailed Description

Unified decompression-limits policy – budget charging and checks.

Implementation of the one enforcement seam every archive / stream decoder shares (see ra8_decomp_limits.h). Charging arithmetic is saturating and overflow-checked so hostile 64-bit header values cannot wrap a bound: the ratio limit in * max_ratio + grace is computed with an explicit divide-guard and saturates to UINT64_MAX, at which point only the absolute output cap governs (which is exactly the intent: a huge honest input is bounded by the cap, not the quotient).

Every decision in this file is a single condition on purpose: the policy is the most security-load-bearing code in the content path, so each bound is tested independently (and carries no compound-decision MC/DC burden).

Since
Version 0.1.0

Definition in file ra8_decomp_limits.c.

Enumeration Type Documentation

◆ priv_zip_preflight_t

enum priv_zip_preflight_t : uint32_t

Fixed classic-ZIP EOCD geometry used by the entry-cap preflight.

Enumerator
k_priv_zip_eocd_bytes 

Fixed EOCD bytes before its comment.

k_priv_zip_comment_max 

Maximum classic ZIP comment bytes.

k_priv_zip_scan_chunk 

Candidate offsets inspected per read.

k_priv_zip_sig_bytes 

Bytes in the EOCD signature.

k_priv_zip_entries_offset 

Total-entry field offset in EOCD.

k_priv_zip_comment_offset 

Comment-length field offset in EOCD.

Definition at line 37 of file ra8_decomp_limits.c.

◆ priv_zip_signature_t

enum priv_zip_signature_t : uint8_t

Classic ZIP EOCD signature bytes.

Enumerator
k_priv_zip_sig_p 

ASCII P.

k_priv_zip_sig_k 

ASCII K.

k_priv_zip_sig_eocd 

EOCD record identifier.

k_priv_zip_sig_fixed 

EOCD fixed suffix.

Definition at line 47 of file ra8_decomp_limits.c.

Function Documentation

◆ internal_limits_usable()

bool internal_limits_usable ( const ra8_decomp_limits_t * lim)
static

Whether every field of a policy is non-zero (usable as a bound).

A zero cap is always a configuration bug: it would reject all input (caps) or create ambiguity (grace), so binding such a policy is refused outright.

Parameters
[in]limPolicy to inspect (non-NULL).
Returns
Whether all six fields are non-zero.
Return values
trueThe policy is usable.
falseAt least one field is zero.
Precondition
lim is non-NULL (caller-guarded).
lim is fully initialised (no indeterminate fields).
Postcondition
No state is modified (pure read).
The result depends only on lim.
Note
Thread-safe: pure read.
Since
Version 0.1.0

Definition at line 88 of file ra8_decomp_limits.c.

References 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, RA8_INTERNAL, and ra8_decomp_limits_t::ratio_grace_bytes.

Referenced by ra8_decomp_budget_init().

◆ internal_ratio_bound()

uint64_t internal_ratio_bound ( const ra8_decomp_limits_t * lim,
uint64_t in_total )
static

The saturating ratio bound in * max_ratio + grace for a policy.

Guards both the multiply and the add against 64-bit wrap: a product or sum that would overflow saturates to UINT64_MAX, deliberately disabling the ratio test for astronomically large honest inputs (the absolute output cap still governs those).

Parameters
[in]limPolicy supplying max_ratio / ratio_grace_bytes.
[in]in_totalCompressed bytes consumed (or declared).
Returns
The largest output byte count the ratio bound admits.
Return values
UINT64_MAXWhen the bound saturates (ratio test disabled).
Precondition
lim is non-NULL with non-zero max_ratio (caller-guarded).
in_total is the untrusted input size; any value is safe.
Postcondition
No state is modified (pure computation).
The result never wraps below its true mathematical value.
Note
Thread-safe: pure computation.
Since
Version 0.1.0

Definition at line 128 of file ra8_decomp_limits.c.

References ra8_decomp_limits_t::max_ratio, RA8_INTERNAL, and ra8_decomp_limits_t::ratio_grace_bytes.

Referenced by ra8_decomp_budget_charge_output(), and ra8_decomp_check_declared().

◆ internal_zip_scan_window_for_eocd()

bool internal_zip_scan_window_for_eocd ( ra8_decomp_read_fn read,
void * ctx,
const uint8_t * chunk,
uint64_t start,
size_t count,
uint64_t archive_size,
ra8_err_t * out_status )
static

Search one loaded scan window backward for a verified ZIP EOCD record.

Walks chunk from its high end looking for the EOCD signature, re-reads the full record at each candidate, and accepts the first one whose declared comment length lands exactly on archive_size. A signature match that fails re-read or comment-length validation is a hash collision in the scan window, not the true record, so the scan continues past it.

Parameters
[in]readCaller-owned positioned-read callback.
[in,out]ctxOpaque context forwarded to read.
[in]chunkBytes already loaded for the current scan window.
[in]startAbsolute archive offset of chunk[0].
[in]countNumber of leading bytes of chunk to search.
[in]archive_sizeTotal archive size in bytes.
[out]out_statusPreflight result, valid only when this returns true.
Returns
Whether a verified EOCD record was found in this window.
Return values
trueA verified record was found; out_status holds its result.
falseNo verified record in this window; the caller should rescan an earlier window.
Precondition
chunk holds at least count plus the signature length bytes.
out_status is non-NULL.
Postcondition
out_status is written if and only if this returns true.
chunk is not modified; each candidate is re-read into a private buffer.
Note
Not thread-safe if read mutates shared state; the reader owns that.
Since
Version 0.1.0

Definition at line 274 of file ra8_decomp_limits.c.

References internal_zip_u16(), k_priv_zip_comment_offset, k_priv_zip_entries_offset, k_priv_zip_eocd_bytes, k_priv_zip_sig_bytes, k_ra8_decomp_def_max_entries, k_ra8_err_decomp_entries, k_ra8_ok, memcmp(), RA8_INTERNAL, and s_zip_eocd_signature.

Referenced by ra8_decomp_zip_entry_preflight().

◆ internal_zip_u16()

uint16_t internal_zip_u16 ( const uint8_t * bytes)
static

Decode one little-endian 16-bit ZIP field.

Combines two already-bounds-checked bytes without an unaligned load.

Parameters
[in]bytesAddress of two readable bytes.
Returns
Decoded unsigned value.
Return values
0The encoded field is zero.
Precondition
bytes is non-NULL.
At least two bytes are readable at bytes.
Postcondition
No source byte is modified.
The result depends only on the two source bytes.
Note
ZIP metadata is always little-endian.
Since
Version 0.1.0

Definition at line 243 of file ra8_decomp_limits.c.

References RA8_INTERNAL.

Referenced by internal_zip_scan_window_for_eocd().

◆ 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().

Variable Documentation

◆ s_tag_decomp

const char* const s_tag_decomp = "ra8_decomp"
static

◆ s_zip_eocd_signature

const uint8_t s_zip_eocd_signature[k_priv_zip_sig_bytes]
static
Initial value:
@ k_priv_zip_sig_p
ASCII P.
@ k_priv_zip_sig_k
ASCII K.
@ k_priv_zip_sig_eocd
EOCD record identifier.
@ k_priv_zip_sig_fixed
EOCD fixed suffix.

Classic ZIP end-of-central-directory signature bytes.

Definition at line 55 of file ra8_decomp_limits.c.

Referenced by internal_zip_scan_window_for_eocd().