diff --git a/rofs/Makefile b/rofs/Makefile index 9ea355f..5e50af9 100644 --- a/rofs/Makefile +++ b/rofs/Makefile @@ -7,12 +7,12 @@ NAME := librofs LOCAL_SRCS := rofs.c -LOCAL_HEADERS := rofs.h +LOCAL_HEADERS := rofs.h rofs_layout.h include $(static-lib.mk) NAME := rofs LOCAL_SRCS := srv.c -LIBS := librofs +LIBS := librofs libtinyaes include $(binary.mk) diff --git a/rofs/rofs.c b/rofs/rofs.c index 847b76b..15e7a97 100644 --- a/rofs/rofs.c +++ b/rofs/rofs.c @@ -23,49 +23,31 @@ #include #include +#include +#include + +#include "rofs_layout.h" #include "rofs.h" +_Static_assert(ROFS_BUFSZ >= sizeof(struct rofs_node), "buffer must fit rofs_node"); + #define LOG_PREFIX "rofs: " #define LOG(fmt, ...) printf(LOG_PREFIX fmt "\n", ##__VA_ARGS__) -#if 0 +#ifndef ROFS_DEBUG +#define ROFS_DEBUG 0 +#endif + +#if ROFS_DEBUG #define TRACE(fmt, ...) printf(LOG_PREFIX fmt "\n", ##__VA_ARGS__) #else #define TRACE(fmt, ...) #endif - /* * NOTE: Implementation assumes filesystem is prepared for target native endianness */ - -#define ROFS_SIGNATURE "ROFS" -#define ROFS_HDR_SIGNATURE 0 -#define ROFS_HDR_CHECKSUM 4 -#define ROFS_HDR_IMAGESIZE 8 -#define ROFS_HDR_INDEXOFFS 16 -#define ROFS_HDR_NODECOUNT 24 -#define ROFS_HDRSIZE 32 - - -struct rofs_node { - uint64_t timestamp; - uint32_t parent_id; - uint32_t id; - uint32_t mode; - uint32_t reserved0; - int32_t uid; - int32_t gid; - uint32_t offset; - uint32_t reserved1; - uint32_t size; - uint32_t reserved2; - char name[207]; - uint8_t zero; -} __attribute__((packed)); /* 256 bytes */ - - static uint32_t calc_crc32(const uint8_t *buf, uint32_t len, uint32_t base) { @@ -89,6 +71,65 @@ static uint32_t calc_crc32(const uint8_t *buf, uint32_t len, uint32_t base) } +static int calc_AESCMAC(struct rofs_ctx *ctx, uint32_t ofs, uint32_t len, uint8_t mac[AES_BLOCKLEN]) +{ + size_t todo = len; + struct CMAC_ctx cmacCtx; + + CMAC_init_ctx(&cmacCtx, ctx->key); + + while (todo > 0) { + size_t chunksz = (todo > sizeof(ctx->buf)) ? sizeof(ctx->buf) : todo; + ssize_t rlen = ctx->devRead(ctx, ctx->buf, chunksz, ofs + len - todo); + + if (chunksz != rlen) { + LOG("devRead failed: %zd", rlen); + return -1; + } + + CMAC_append(&cmacCtx, ctx->buf, rlen); + todo -= rlen; + } + + CMAC_calculate(&cmacCtx, mac); + + return 0; +} + + +static uint8_t *ivSerializeU32(uint8_t *buff, uint32_t d) +{ + buff[0] = (d >> 0) & 0xff; + buff[1] = (d >> 8) & 0xff; + buff[2] = (d >> 16) & 0xff; + buff[3] = (d >> 24) & 0xff; + + return &buff[4]; +} + + +static inline void constructIV(uint8_t *iv, const struct rofs_node *node) +{ + /* TODO: ESSIV? */ + uint8_t *tbuff = ivSerializeU32(iv, node->id); + tbuff = ivSerializeU32(tbuff, node->offset); + tbuff = ivSerializeU32(tbuff, node->uid); + uint32_t t = 0; + ivSerializeU32(tbuff, t); +} + + +static void xcrypt(void *buff, size_t bufflen, const uint8_t *key, const struct rofs_node *node) +{ + struct AES_ctx ctx; + uint8_t iv[AES_BLOCKLEN]; + + constructIV(iv, node); + AES_init_ctx_iv(&ctx, key, iv); + AES_CTR_xcrypt_buffer(&ctx, buff, bufflen); +} + + /* WARN: returned pointer valid until next nodeFromTree call */ static struct rofs_node *nodeFromTree(struct rofs_ctx *ctx, int id) { @@ -146,10 +187,8 @@ static int getNode(struct rofs_ctx *ctx, oid_t *oid, struct rofs_node **retNode) } -int rofs_init(struct rofs_ctx *ctx, rofs_devRead_t devRead, unsigned long imageAddr) +int rofs_init(struct rofs_ctx *ctx, rofs_devRead_t devRead, unsigned long imageAddr, const uint8_t *key) { - _Static_assert(ROFS_BUFSZ >= sizeof(struct rofs_node), "buffer must fit rofs_node"); - uint8_t *imagePtr = NULL; uint32_t crc = ~0; int ret; @@ -173,8 +212,8 @@ int rofs_init(struct rofs_ctx *ctx, rofs_devRead_t devRead, unsigned long imageA ctx->imgPtr = imagePtr; } - ret = ctx->devRead(ctx, ctx->buf, ROFS_HDRSIZE, 0); - if (ret != ROFS_HDRSIZE) { + ret = ctx->devRead(ctx, ctx->buf, ROFS_HEADER_SIZE, 0); + if (ret != ROFS_HEADER_SIZE) { ret = -EIO; /* defer return on failure to after munmap in case imageAddr != 0 */ } @@ -200,12 +239,26 @@ int rofs_init(struct rofs_ctx *ctx, rofs_devRead_t devRead, unsigned long imageA ctx->imgSize = *(uint32_t *)(ctx->buf + ROFS_HDR_IMAGESIZE); ctx->nodeCount = *(uint32_t *)(ctx->buf + ROFS_HDR_NODECOUNT); ctx->indexOffs = *(uint32_t *)(ctx->buf + ROFS_HDR_INDEXOFFS); + ctx->encryption = *(uint16_t *)(ctx->buf + ROFS_HDR_ENCRYPTION); + + uint8_t target_mac[AES_BLOCKLEN]; + memcpy(target_mac, ctx->buf + ROFS_HDR_CRYPT_SIG, AES_BLOCKLEN); if ((ctx->indexOffs & (sizeof(uint64_t) - 1)) != 0) { LOG("Image index offset is invalid"); return -EINVAL; } +#if ROFS_DEBUG + char buf[2 * AES_BLOCKLEN + 1]; + size_t ofs = 0; + for (int i = 0; i < AES_BLOCKLEN; i++) { + ofs += snprintf(buf + ofs, 2 * AES_BLOCKLEN - ofs, "%.2x", target_mac[i]); + } + buf[ofs] = '\0'; + TRACE("target AES-CMAC: %s", buf); +#endif + if (imageAddr != 0) { /* Map whole image */ ctx->imgAlignedSize = ((ctx->imgSize + _PAGE_SIZE - 1) & ~(_PAGE_SIZE - 1)); @@ -235,18 +288,48 @@ int rofs_init(struct rofs_ctx *ctx, rofs_devRead_t devRead, unsigned long imageA crc = ~crc; } - if (crc != ctx->checksum) { - LOG("invalid crc %08X vs %08X", crc, ctx->checksum); + do { + if (crc != ctx->checksum) { + LOG("invalid crc %08X vs %08X", crc, ctx->checksum); + ret = -EINVAL; + break; + } + + TRACE("SIG OK: crc32=%08X imgSize=%zu nodes=%d", crc, ctx->imgSize, ctx->nodeCount); + if (ctx->imgPtr != NULL) { - munmap(ctx->imgPtr, ctx->imgAlignedSize); + ctx->tree = (struct rofs_node *)(ctx->imgPtr + ctx->indexOffs); } - return -EINVAL; - } - TRACE("SIG OK: crc32=%08X imgSize=%zu nodes=%d", crc, ctx->imgSize, ctx->nodeCount); + if (key != NULL) { + if (ctx->encryption != rofs_encryption_aes) { + LOG("image encryption type mismatch: %d != %d", ctx->encryption, rofs_encryption_aes); + ret = -EINVAL; + break; + } - if (ctx->imgPtr != NULL) { - ctx->tree = (struct rofs_node *)(ctx->imgPtr + ctx->indexOffs); + ctx->key = key; + uint8_t actual_mac[AES_BLOCKLEN]; + ret = calc_AESCMAC(ctx, ROFS_HEADER_SIZE, ctx->imgSize - ROFS_HEADER_SIZE, actual_mac); + if (ret < 0) { + LOG("failed to calculate AES-CMAC: %d", ret); + ret = -EIO; + break; + } + + if (memcmp(actual_mac, target_mac, AES_BLOCKLEN) != 0) { + LOG("AES-CMAC mismatch"); + ret = -EINVAL; + break; + } + } + } while (0); + + if (ret < 0) { + if (ctx->imgPtr != NULL) { + munmap(ctx->imgPtr, ctx->imgAlignedSize); + } + return ret; } return 0; @@ -302,7 +385,16 @@ int rofs_read(struct rofs_ctx *ctx, oid_t *oid, off_t offs, char *buff, size_t l len = (size_t)node->size - offs; } - return ctx->devRead(ctx, buff, len, node->offset + offs); + ret = ctx->devRead(ctx, buff, len, node->offset + offs); + if (ret < 0) { + return ret; + } + + if (ctx->key != NULL) { + xcrypt(buff, (size_t)ret, ctx->key, node); + } + + return ret; } @@ -493,7 +585,7 @@ int rofs_getattrall(struct rofs_ctx *ctx, oid_t *oid, struct _attrAll *attrs, si } -static int dirfind(struct rofs_ctx *ctx, struct rofs_node **pNode, int parent_id, const char *name, oid_t *o) +static int dirfind(struct rofs_ctx *ctx, struct rofs_node **pNode, int parentId, const char *name, oid_t *o) { struct rofs_node *node; uint32_t i; @@ -514,7 +606,7 @@ static int dirfind(struct rofs_ctx *ctx, struct rofs_node **pNode, int parent_id return -EIO; } - if ((node->parent_id == parent_id) && (strnlen(node->name, len + 1) == len) && (strncmp(name, node->name, len) == 0)) { + if ((node->parentId == parentId) && (strnlen(node->name, len + 1) == len) && (strncmp(name, node->name, len) == 0)) { o->id = node->id; o->port = ctx->oid.port; *pNode = node; @@ -531,7 +623,7 @@ int rofs_lookup(struct rofs_ctx *ctx, oid_t *dir, const char *name, oid_t *fil, TRACE("lookup name='%s' oid=%u.%ju port=%d", name, dir->port, (uintmax_t)dir->id, ctx->oid.port); struct rofs_node *node = NULL; - int parent_id = 0; + int parentId = 0; int len = 0; int res = -ENOENT; @@ -541,7 +633,7 @@ int rofs_lookup(struct rofs_ctx *ctx, oid_t *dir, const char *name, oid_t *fil, } if ((dir != NULL) && (dir->port == ctx->oid.port)) { - parent_id = dir->id; + parentId = dir->id; } while (name[len] != '\0') { @@ -549,14 +641,14 @@ int rofs_lookup(struct rofs_ctx *ctx, oid_t *dir, const char *name, oid_t *fil, len++; } - res = dirfind(ctx, &node, parent_id, &name[len], fil); + res = dirfind(ctx, &node, parentId, &name[len], fil); if (res <= 0) { break; } else { len += res; } - parent_id = node->id; + parentId = node->id; } if (res < 0) { @@ -599,7 +691,7 @@ int rofs_readdir(struct rofs_ctx *ctx, oid_t *dir, off_t offs, struct dirent *de if (count > offs) { strcpy(dent->d_name, (offs == 0) ? "." : ".."); - dent->d_ino = (offs == 0) ? node->id : node->parent_id; + dent->d_ino = (offs == 0) ? node->id : node->parentId; dent->d_namlen = (offs == 0) ? 1 : 2; dent->d_reclen = 1; dent->d_type = DT_DIR; @@ -612,7 +704,7 @@ int rofs_readdir(struct rofs_ctx *ctx, oid_t *dir, off_t offs, struct dirent *de return -EIO; } - if (node->parent_id == dir->id) { + if (node->parentId == dir->id) { if (count++ < offs) { continue; } diff --git a/rofs/rofs.h b/rofs/rofs.h index 14cffb7..1289f92 100644 --- a/rofs/rofs.h +++ b/rofs/rofs.h @@ -1,10 +1,10 @@ /* * Phoenix-RTOS * - * ROFS - Read Only File System in AHB address space + * ROFS - Read Only File System * - * Copyright 2024 Phoenix Systems - * Author: Gerard Swiderski + * Copyright 2024, 2025 Phoenix Systems + * Author: Gerard Swiderski, Adam Greloch * * This file is part of Phoenix-RTOS. * @@ -34,16 +34,19 @@ struct rofs_ctx { struct rofs_node *tree; uint32_t nodeCount; uint32_t indexOffs; + uint16_t encryption; oid_t oid; rofs_devRead_t devRead; uint8_t buf[ROFS_BUFSZ]; + + const uint8_t *key; }; /* if imageAddr != 0, maps partition directly; assumes indirect access otherwise */ -int rofs_init(struct rofs_ctx *ctx, rofs_devRead_t devRead, unsigned long imageAddr); +int rofs_init(struct rofs_ctx *ctx, rofs_devRead_t devRead, unsigned long imageAddr, const uint8_t *key); void rofs_setdev(struct rofs_ctx *ctx, oid_t *oid); diff --git a/rofs/rofs_layout.h b/rofs/rofs_layout.h new file mode 100644 index 0000000..f0a7d39 --- /dev/null +++ b/rofs/rofs_layout.h @@ -0,0 +1,53 @@ +/* + * Phoenix-RTOS + * + * ROFS - Read Only File System + * + * Filesystem layout + * + * Copyright 2026 Phoenix Systems + * Author: Adam Greloch + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#ifndef ROFS_FORMAT_H +#define ROFS_FORMAT_H + +#include + +#define ROFS_SIGNATURE "ROFS" +#define ROFS_HDR_SIGNATURE 0 +#define ROFS_HDR_CHECKSUM 4 +#define ROFS_HDR_IMAGESIZE 8 +#define ROFS_HDR_INDEXOFFS 16 +#define ROFS_HDR_NODECOUNT 24 +#define ROFS_HDR_ENCRYPTION 32 +#define ROFS_HDR_CRYPT_SIG 34 /* let CRYPT_SIG to be at least 64-byte long to allow for future signatures schemes (e.g. ed25519) */ +#define ROFS_HEADER_SIZE 128 + +_Static_assert(AES_BLOCKLEN <= ROFS_HEADER_SIZE - ROFS_HDR_CRYPT_SIG, "AES_MAC does not fit into the rofs header"); + +/* clang-format off */ +enum { rofs_encryption_none, rofs_encryption_aes }; +/* clang-format on */ + +struct rofs_node { + uint64_t timestamp; + uint32_t parentId; + uint32_t id; + uint32_t mode; + uint32_t reserved0; + int32_t uid; + int32_t gid; + uint32_t offset; + uint32_t reserved1; + uint32_t size; + uint32_t reserved2; + char name[207]; + uint8_t zero; +} __attribute__((packed)); /* 256 bytes */ + +#endif diff --git a/rofs/srv.c b/rofs/srv.c index 06fd7ce..cea9165 100644 --- a/rofs/srv.c +++ b/rofs/srv.c @@ -112,7 +112,7 @@ int main(int argc, char **argv) /* address in AHB memory where whole ROFS image is loaded by other process */ - if (rofs_init(&ctx, rofs_ahbRead, imgAddr) < 0) { + if (rofs_init(&ctx, rofs_ahbRead, imgAddr, NULL) < 0) { LOG("error"); return EXIT_FAILURE; }