wolfBoot/include/disk_fs.h

239 lines
9.9 KiB
C

/* disk_fs.h
*
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
/* Read-only filesystem layer for wolfBoot disk boot.
*
* Sits between the partition layer (disk_part_read(), src/disk.c) and the
* disk loader (src/update_disk.c), so a boot slot can name a file instead
* of requiring the signed image at raw offset 0 of a partition.
*
* Supported: FAT32 (with VFAT long filenames) and ext4 (extent-mapped
* files only). A partition holding neither is presented as a single file
* spanning the whole partition (FS_TYPE_RAW), which is exactly the
* pre-existing behaviour.
*
* SECURITY: every byte this layer parses is attacker-controlled and is
* read BEFORE the image signature is verified. All metadata is validated
* before use, and every loop that follows on-media links is bounded. See
* the WOLFBOOT_FS_MAX_* knobs below.
*
* Compile with DISK_FS=fat32|ext4|both.
*/
#ifndef WOLFBOOT_DISK_FS_H
#define WOLFBOOT_DISK_FS_H
#include <stdint.h>
#include "gpt.h"
/* Per-filesystem on-disk layout and backend interfaces. Only the parsers
* selected by DISK_FS are compiled, so each is included on its own. */
#ifdef WOLFBOOT_FAT32
#include "fat32.h"
#endif
#ifdef WOLFBOOT_EXT4
#include "ext4.h"
#endif
/* error codes */
#define WOLFBOOT_FS_OK 0
#define WOLFBOOT_FS_E_PARAM -1 /* bad argument / path too long */
#define WOLFBOOT_FS_E_IO -2 /* disk read failed or came up short */
#define WOLFBOOT_FS_E_NOFS -3 /* nothing recognizable: use raw */
#define WOLFBOOT_FS_E_FORMAT -4 /* metadata failed validation */
#define WOLFBOOT_FS_E_UNSUPPORTED -5 /* valid filesystem, feature refused */
#define WOLFBOOT_FS_E_NOENT -6 /* path component not found */
#define WOLFBOOT_FS_E_NOTFILE -7 /* found, but not a regular file */
#define WOLFBOOT_FS_E_TOOBIG -8 /* size exceeds the caller's max */
#define WOLFBOOT_FS_E_CORRUPT -9 /* a security bound tripped */
#define WOLFBOOT_FS_E_RANGE -10 /* offset arithmetic out of range */
/* filesystem types */
#define FS_TYPE_NONE 0
#define FS_TYPE_RAW 1
#define FS_TYPE_FAT32 2
#define FS_TYPE_EXT4 3
/* Metadata cache size. Holds ONLY metadata (FAT entries, directory
* sectors, superblocks, group descriptors, inodes, extent nodes); bulk file
* data streams straight to the caller's buffer. Raising it cuts disk reads
* on a heavily fragmented file. */
#ifndef WOLFBOOT_FS_CACHE_SIZE
#define WOLFBOOT_FS_CACHE_SIZE 512
#endif
#if (WOLFBOOT_FS_CACHE_SIZE < 512) || ((WOLFBOOT_FS_CACHE_SIZE % 512) != 0)
#error "WOLFBOOT_FS_CACHE_SIZE must be a multiple of 512, minimum 512"
#endif
/* Security bounds. Each of these exists to make an on-media structure that
* can reference other on-media structures terminate in bounded time even
* when it is crafted. Each has a dedicated negative unit test. */
/* Longest absolute path accepted by fs_open(). */
#ifndef WOLFBOOT_FS_MAX_PATH
#define WOLFBOOT_FS_MAX_PATH 128
#endif
/* Maximum number of '/'-separated components in a path. */
#ifndef WOLFBOOT_FS_MAX_PATH_DEPTH
#define WOLFBOOT_FS_MAX_PATH_DEPTH 8
#endif
/* Longest single filename accepted. Names longer than this can never match
* a configured path component, so they are skipped rather than assembled. */
#ifndef WOLFBOOT_FS_MAX_NAME
#define WOLFBOOT_FS_MAX_NAME 64
#endif
/* Maximum directory entries scanned before declaring the directory bad. */
#ifndef WOLFBOOT_FS_MAX_DIR_ENTRIES
#define WOLFBOOT_FS_MAX_DIR_ENTRIES 4096
#endif
/* Maximum blocks/clusters walked while scanning one directory. */
#ifndef WOLFBOOT_FS_MAX_DIR_BLOCKS
#define WOLFBOOT_FS_MAX_DIR_BLOCKS 1024
#endif
/* Absolute stop for a FAT cluster chain walk, independent of file size and
* of volume geometry. */
#ifndef WOLFBOOT_FS_MAX_CHAIN
#define WOLFBOOT_FS_MAX_CHAIN 1048576
#endif
/* Maximum ext4 extent tree depth. This is ext4's own architectural limit;
* the walk additionally requires depth to decrease by exactly one at every
* level, which is what makes the descent provably finite. */
#ifndef WOLFBOOT_FS_EXT4_MAX_DEPTH
#define WOLFBOOT_FS_EXT4_MAX_DEPTH 5
#endif
/* A mounted partition. FS_TYPE_RAW is the identity filesystem: the whole
* partition is one file and fs_read() is a direct disk_part_read(), which is
* how a DISK_FS build stays byte-identical to one without it on a partition
* holding no filesystem. */
struct fs_volume {
uint64_t part_sz; /* partition size in bytes; bounds all arithmetic */
int drv;
int part;
int type; /* FS_TYPE_* */
union {
#ifdef WOLFBOOT_FAT32
struct fat32_volume fat;
#endif
#ifdef WOLFBOOT_EXT4
struct ext4_volume ext;
#endif
/* Keeps the union non-empty for a RAW-only build. */
uint8_t raw;
} u;
};
/* An open file. The forward-only cursor makes a sequential load one linear
* metadata walk rather than one walk per read. */
struct fs_file {
struct fs_volume *vol;
uint64_t size; /* validated <= partition size and <= max_size */
uint64_t cur_off; /* file offset at which the cursor position begins */
uint32_t first_clus; /* FAT32: first cluster of the file */
uint32_t cur_clus; /* FAT32: cluster covering cur_off */
uint8_t iblock[60]; /* ext4: raw i_block[], the extent tree root node */
};
/**
* @brief Probe a partition and populate a volume descriptor.
*
* Order: wolfBoot magic at offset 0 (short-circuits to RAW), FAT32, ext4,
* else RAW. An UNRECOGNIZED partition is not an error - it becomes RAW, so
* raw boot behaviour is preserved. A partition that IS a filesystem we
* refuse (FAT12/16, ext2/3, an unsupported ext4 feature) returns
* WOLFBOOT_FS_E_UNSUPPORTED. The distinction is deliberate: falling back to
* raw on a real filesystem would parse a boot sector as an image header.
*/
int fs_mount(struct fs_volume *vol, int drv, int part);
/**
* @brief Resolve an absolute path to a regular file. RAW ignores the path
* and opens the whole partition.
*
* @param max_size Upper bound on file size. The filesystem-reported size is
* attacker-controlled and is checked against this before it
* drives any I/O.
*/
int fs_open(struct fs_volume *vol, struct fs_file *f, const char *path,
uint64_t max_size);
/* Read from an open file. Matches disk_part_read()'s contract - returns
* bytes read (may be short at EOF), or negative WOLFBOOT_FS_E_* - so callers
* can switch between a file and a raw partition unchanged. */
int fs_read(struct fs_file *f, uint64_t off, uint64_t len, uint8_t *buf);
/* Size in bytes of an open file. */
uint64_t fs_size(const struct fs_file *f);
/* Human-readable volume type, for boot logs. */
const char *fs_type_name(const struct fs_volume *vol);
/* Compare a volume's label (FAT32 BS_VolLab / ext4 s_volume_name) against
* an ASCII string, so a partition can be selected by name on MBR disks where
* GPT names do not exist. Case-insensitive for FAT32, case-sensitive for
* ext4, matching each filesystem's convention.
* Returns 1 match, 0 no match, negative WOLFBOOT_FS_E_* on error. */
int fs_label_eq(struct fs_volume *vol, const char *label);
/* ---------------------------------------------------------------------
* Internal API. Shared between src/disk_fs.c and the filesystem backends
* (src/fat32.c, src/ext4.c). Not intended for use outside this layer.
* --------------------------------------------------------------------- */
/* Byte-wise little-endian accessors. Used in preference to casting packed
* structs onto a sector buffer, which is only correct on little-endian
* targets and trips -Wextra on packed-member address-taking. */
int fs_is_pow2(uint32_t v);
/* Deliberately out-of-line, unlike the inline readers in gpt.h. The parsers
* call these about fifty times between them, and inlining every site costs
* more code than the calls do. gpt.c and disk.c have only a handful of sites
* each, so the inline form is the right trade there. */
uint16_t fs_le16(const uint8_t *p);
uint32_t fs_le32(const uint8_t *p);
/* Read exactly len bytes at a partition-relative offset, or fail.
* disk_part_read() silently CLAMPS to the partition end and returns the
* clamped count; treating that as success would turn a truncation into a
* silent wrong read, so every read in this layer goes through here. */
int fs_disk_read_exact(struct fs_volume *v, uint64_t off, uint32_t len,
uint8_t *buf);
/* Read metadata through the shared cache, handling straddled windows so
* callers never deal with alignment. Bulk file data must NOT come through
* here: it would evict the metadata window and gain nothing. */
int fs_meta_read(struct fs_volume *v, uint64_t off, uint32_t len,
uint8_t *buf);
/* Drop the metadata cache. Called on mount, and whenever the underlying
* media may have changed. */
void fs_cache_invalidate(void);
/* Split the next component off an absolute path: advances *p, writes the
* component NUL-terminated to out (WOLFBOOT_FS_MAX_NAME + 1 bytes), bumps
* *depth. Rejects "." and ".." outright - neither is needed to name a boot
* image, and ".." is the only way a crafted tree could build a traversal
* cycle. Returns 1 for a component, 0 at end of path, negative on error. */
int fs_path_next(const char **p, char *out, uint32_t *out_len, int *depth);
#endif /* WOLFBOOT_DISK_FS_H */