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

sfnt table-directory bounds validator run before stbtt_InitFont(). More...

#include "ra8_stbtt_guard.h"
#include <stddef.h>
#include <stdint.h>
#include "ra8_attributes.h"
Include dependency graph for ra8_stbtt_guard.c:

Go to the source code of this file.

Enumerations

enum  sfnt_layout_t : uint8_t {
  k_sfnt_offset_table_bytes = 12U ,
  k_sfnt_num_tables_off = 4U ,
  k_sfnt_table_record_bytes = 16U ,
  k_sfnt_record_tag_off = 0U ,
  k_sfnt_record_offset_off = 8U ,
  k_sfnt_record_length_off = 12U
}
 Byte offsets and sizes of the sfnt offset table and table records. More...
enum  sfnt_tag_t : uint32_t {
  k_sfnt_tag_cmap = 0x636D6170U ,
  k_sfnt_tag_head = 0x68656164U ,
  k_sfnt_tag_maxp = 0x6D617870U
}
 Big-endian sfnt table tags whose internal layout stbtt_InitFont() reads with an attacker-controlled count or a fixed offset. More...
enum  sfnt_internal_layout_t : uint8_t {
  k_cmap_header_bytes = 4U ,
  k_cmap_num_tables_off = 2U ,
  k_cmap_record_bytes = 8U ,
  k_head_loc_format_end = 52U ,
  k_maxp_num_glyphs_end = 6U
}
 Byte offsets stbtt_InitFont() reads inside cmap / head / maxp. More...
enum  sfnt_shift_t : uint8_t {
  k_sfnt_shift_8 = 8U ,
  k_sfnt_shift_16 = 16U ,
  k_sfnt_shift_24 = 24U
}
 Bit-shift distances for assembling big-endian scalars. More...

Functions

static uint16_t internal_rd_be_u16 (const uint8_t *p)
 Read a big-endian uint16 from a two-byte, in-bounds location.
static uint32_t internal_rd_be_u32 (const uint8_t *p)
 Read a big-endian uint32 from a four-byte, in-bounds location.
static bool internal_table_internal_in_bounds (const uint8_t *data, uint64_t buf_len, uint32_t tag, uint64_t t_off)
 Prove the reads stbtt_InitFont() makes inside a known table stay in-bounds, given the table's already-validated file offset.
bool ra8_stbtt_sfnt_dir_in_bounds (const uint8_t *data, size_t len, uint32_t fontstart)
 Verify that a font's sfnt table directory lies within its buffer.

Detailed Description

sfnt table-directory bounds validator run before stbtt_InitFont().

See ra8_stbtt_guard.h for the threat model. This translation unit is a pure, heap-free big-endian sfnt parser: it reads only the offset table and the table directory, proving every access stays inside the caller-supplied buffer, and returns a verdict without ever calling into stb_truetype.

[Ring 4 / Reflow] {World: NS}

Since
0.1.0

Definition in file ra8_stbtt_guard.c.

Enumeration Type Documentation

◆ sfnt_internal_layout_t

enum sfnt_internal_layout_t : uint8_t

Byte offsets stbtt_InitFont() reads inside cmap / head / maxp.

The top-level directory guard proves each table's declared [offset, offset + length) fits, but stbtt_InitFont_internal() then reads fields inside those tables using values it trusts from the table itself – most dangerously the cmap encoding-record count, which it multiplies by 8 to stride the sub-directory with no length check. These constants name the exact extents those reads require.

Enumerator
k_cmap_header_bytes 

cmap: version(2) + numTables(2) before records.

k_cmap_num_tables_off 

cmap: uint16 numTables offset in the table.

k_cmap_record_bytes 

cmap: one encoding-record size, bytes.

k_head_loc_format_end 

head: InitFont reads uint16 at head+50 -> +52.

k_maxp_num_glyphs_end 

maxp: InitFont reads uint16 at maxp+4 -> +6.

Definition at line 72 of file ra8_stbtt_guard.c.

◆ sfnt_layout_t

enum sfnt_layout_t : uint8_t

Byte offsets and sizes of the sfnt offset table and table records.

All values are fixed by the OpenType/TrueType sfnt container format and are big-endian on disk. Named here so the parser carries no bare structural literals.

See also
ra8_stbtt_sfnt_dir_in_bounds
Enumerator
k_sfnt_offset_table_bytes 

sfnt offset table (header) size, bytes.

k_sfnt_num_tables_off 

uint16 numTables offset in the offset table.

k_sfnt_table_record_bytes 

Size of one table directory record, bytes.

k_sfnt_record_tag_off 

4-byte table tag offset in a record.

k_sfnt_record_offset_off 

uint32 table-offset field offset in a record.

k_sfnt_record_length_off 

uint32 table-length field offset in a record.

Definition at line 36 of file ra8_stbtt_guard.c.

◆ sfnt_shift_t

enum sfnt_shift_t : uint8_t

Bit-shift distances for assembling big-endian scalars.

Named to keep the byte-swap helpers free of magic shift counts.

Enumerator
k_sfnt_shift_8 

One-byte shift.

k_sfnt_shift_16 

Two-byte shift.

k_sfnt_shift_24 

Three-byte shift.

Definition at line 86 of file ra8_stbtt_guard.c.

◆ sfnt_tag_t

enum sfnt_tag_t : uint32_t

Big-endian sfnt table tags whose internal layout stbtt_InitFont() reads with an attacker-controlled count or a fixed offset.

Each value is the four ASCII tag bytes assembled most-significant first, i.e. exactly what internal_rd_be_u32 returns for the record's tag field.

See also
internal_table_internal_in_bounds
Enumerator
k_sfnt_tag_cmap 

'cmap' – character-to-glyph mapping.

k_sfnt_tag_head 

'head' – font header (indexToLocFormat).

k_sfnt_tag_maxp 

'maxp' – maximum profile (numGlyphs).

Definition at line 55 of file ra8_stbtt_guard.c.

Function Documentation

◆ internal_rd_be_u16()

uint16_t internal_rd_be_u16 ( const uint8_t * p)
static

Read a big-endian uint16 from a two-byte, in-bounds location.

Assembles p[0] (most-significant) and p[1] into a host-order 16-bit value via a shift-and-or. Pure computation on two bytes the caller has already proven readable and in-bounds; performs no bound check itself.

Parameters
[in]pPointer to the first (most-significant) byte to read.
Returns
The assembled 16-bit value.
Return values
0Both source bytes were zero.
Precondition
p is non-null and points at two readable bytes proven in-bounds by the caller.
The caller has range-checked p[0] and p[1] against the buffer.
Postcondition
No byte outside p[0..1] is accessed.
*p and p[1] are unmodified (pure read).
Note
Thread-safe: no shared state.
Since
0.1.0

Definition at line 111 of file ra8_stbtt_guard.c.

References k_sfnt_shift_8.

Referenced by internal_table_internal_in_bounds(), and ra8_stbtt_sfnt_dir_in_bounds().

◆ internal_rd_be_u32()

uint32_t internal_rd_be_u32 ( const uint8_t * p)
static

Read a big-endian uint32 from a four-byte, in-bounds location.

Assembles p[0..3] most-significant-byte-first into a host-order 32-bit value via shift-and-or. Pure computation on four bytes the caller has already proven readable and in-bounds; performs no bound check itself.

Parameters
[in]pPointer to the first (most-significant) byte to read.
Returns
The assembled 32-bit value.
Return values
0All four source bytes were zero.
Precondition
p is non-null and points at four readable bytes proven in-bounds by the caller.
The caller has range-checked p[0..3] against the buffer.
Postcondition
No byte outside p[0..3] is accessed.
p[0..3] are unmodified (pure read).
Note
Thread-safe: no shared state.
Since
0.1.0

Definition at line 135 of file ra8_stbtt_guard.c.

References k_sfnt_shift_16, k_sfnt_shift_24, and k_sfnt_shift_8.

Referenced by ra8_stbtt_sfnt_dir_in_bounds().

◆ internal_table_internal_in_bounds()

bool internal_table_internal_in_bounds ( const uint8_t * data,
uint64_t buf_len,
uint32_t tag,
uint64_t t_off )
static

Prove the reads stbtt_InitFont() makes inside a known table stay in-bounds, given the table's already-validated file offset.

The top-level directory guard only proves each table's declared extent fits. stbtt_InitFont_internal() then reads fields inside three tables using values it trusts from the table's own bytes – none of which the declared length constrains:

  • cmap: numTables = ttUSHORT(cmap + 2), then strides the encoding-record sub-directory as cmap + 4 + 8 * i for i in [0, numTables). A crafted numTables (up to 65535) walks ~512 KiB past the table – the reliable out-of-bounds read this validator closes. Requires t_off + 4 + 8 * numTables <= len.
  • head: ttUSHORT(head + 50) (indexToLocFormat). Requires t_off + 52 <= len.
  • maxp: ttUSHORT(maxp + 4) (numGlyphs). Requires t_off + 6 <= len. Any other tag needs no internal check here (its contents are read later, in the glyph path, not by stbtt_InitFont).
Parameters
[in]dataFirst byte of the font buffer (caller-proven non-null).
[in]buf_lenBuffer length in bytes.
[in]tagBig-endian 4-byte table tag (see sfnt_tag_t).
[in]t_offTable file offset, already proven < buf_len by the per-record extent check in the caller.
Returns
true if every InitFont read inside this table lies within the buffer (or the tag has no InitFont-time internal read).
Return values
trueTable-internal reads are in-bounds, or tag is not cmap/head/maxp.
falseA cmap/head/maxp internal read would escape [data, data+len).
Precondition
data references at least buf_len readable bytes.
t_off is < buf_len (the record's declared offset fits).
Postcondition
data and its buffer are unmodified (pure read).
No byte outside [data, data + buf_len) is accessed.
Note
Thread-safe and re-entrant: no shared or static state.
Since
0.1.0

Definition at line 180 of file ra8_stbtt_guard.c.

References internal_rd_be_u16(), k_cmap_header_bytes, k_cmap_num_tables_off, k_cmap_record_bytes, k_head_loc_format_end, k_maxp_num_glyphs_end, k_sfnt_tag_cmap, k_sfnt_tag_head, and k_sfnt_tag_maxp.

Referenced by ra8_stbtt_sfnt_dir_in_bounds().

◆ ra8_stbtt_sfnt_dir_in_bounds()

bool ra8_stbtt_sfnt_dir_in_bounds ( const uint8_t * data,
size_t len,
uint32_t fontstart )

Verify that a font's sfnt table directory lies within its buffer.

Mirrors exactly the reads stbtt_InitFont() / stbtt__find_table() make while walking the sfnt offset table, table directory, and the table-internal fields InitFont reads, and confirms each one stays inside [0, len):

  1. The 12-byte offset table (sfnt version, numTables, and the three binary-search hint fields) must fit: fontstart + 12 <= len.
  2. The whole table directory must fit: fontstart + 12 + numTables * 16 <= len.
  3. Every table record's declared extent must fit: for each record, tableOffset + tableLength <= len.
  4. The fields stbtt_InitFont() reads inside cmap / head / maxp must fit. Most importantly, InitFont strides the cmap encoding-record sub-directory as cmap + 4 + 8 * numTables using a numTables it trusts from inside the cmap table – a value the table's declared length does not bound, so a crafted count (up to 65535) drives a ~512 KiB out-of-bounds read. This check requires cmap + 4 + 8 * numTables <= len, plus the fixed head + 52 <= len and maxp + 6 <= len extents InitFont's other table-internal reads need.

All arithmetic is performed in uint64_t so that a hostile fontstart (up to UINT32_MAX, e.g. a crafted TrueType-collection sub-font offset) or a hostile per-table offset/length cannot overflow the bound computation. The function bounds every read stbtt_InitFont() itself issues. It does NOT bound the deeper reads the glyph-lookup / rasterisation path makes after init (stbtt_FindGlyphIndex following an attacker-controlled cmap subtable offset, then the loca / glyf outline walk); stb_truetype does not bound those against the buffer either, so a crafted font can still drive an out-of-bounds read there. Hardening that deeper path is tracked separately as parser hardening (issue #179); it is a much larger change than this directory pre-check.

Parameters
[in]dataPointer to the first byte of the font buffer.
[in]lenLength of the font buffer, bytes.
[in]fontstartByte offset of this font's sfnt offset table within data (0 for a bare TTF/OTF; the sub-font offset for a TrueType collection). This is the same value passed as the offset argument to stbtt_InitFont().
Returns
true if the offset table, every table record, and every cmap / head / maxp field InitFont reads lie fully within the buffer; false otherwise.
Return values
trueThe directory is in bounds; stbtt_InitFont() may safely walk it.
falsedata is nullptr, or the offset table / directory / any table record / a cmap-head-maxp internal read extends past len – reject the font.
Precondition
data references at least len readable bytes (or is nullptr).
len is the true byte length of the buffer data points to.
Postcondition
data and the buffer it points to are unmodified (pure read).
No read stbtt_InitFont() issues lands outside [data, data + len).
Note
Thread-safe and re-entrant: no shared or static state, no allocation.
Example:
const int32_t off = stbtt_GetFontOffsetForIndex(blob, 0);
if (off >= 0 && !ra8_stbtt_sfnt_dir_in_bounds(blob, blob_len, (uint32_t)off)) {
return k_ra8_err_not_supported; // malformed directory -- reject
}
if (off < 0 || stbtt_InitFont(&info, blob, off) == 0) {
}
@ k_ra8_err_not_supported
Requested feature not compiled in, not wired, or not supported by this MCU variant.
Definition ra8_err.h:180
bool ra8_stbtt_sfnt_dir_in_bounds(const uint8_t *data, size_t len, uint32_t fontstart)
Verify that a font's sfnt table directory lies within its buffer.
Since
0.1.0

Definition at line 204 of file ra8_stbtt_guard.c.

References internal_rd_be_u16(), internal_rd_be_u32(), internal_table_internal_in_bounds(), k_sfnt_num_tables_off, k_sfnt_offset_table_bytes, k_sfnt_record_length_off, k_sfnt_record_offset_off, k_sfnt_record_tag_off, and k_sfnt_table_record_bytes.

Referenced by internal_font_init(), reflow_bind_font(), and reflow_register_face().