FABRIC-3.md §I.4: magic-number content-type detection, closing Milestone 6
Closes both remaining Milestone 6 (PKI) punch-list items. Signature-status column: doc-only closure -- the underlying need was already redirected to capsules/BLOCK_MAP.md's real Signed column (2026-08-26); checking the box off as moot-as-worded rather than leaving an accurate-but-permanently-unchecked marker. Magic-number content-type detection (Section U item 14): built in tools/mkcapsule.c. detect_content_type() classifies a file's actual leading bytes (TTF/OpenType sfnt tags, DER's 0x30 SEQUENCE tag, or a printable-ASCII/TAB/CR/LF heuristic for text) against expected_type_from_ext()'s .4th/.md/.der/.ttf mapping; process_file() warns on mismatch, never refuses -- same WARN-first rollout this project already used for capsule signing. Verified against every real capsule in the repo (38 files) with zero false positives. This is the shared primitive Milestone 7's contrib-capsule validation can reuse next. Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the foreground); logs and DoE CSVs from this session's verification runs included per this repo's own audit-artifact convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
4018fe8b04
commit
5567d03c12
@@ -233,6 +233,75 @@ static uint32_t flags_from_name(const char *name) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
/*
|
||||
* Magic-number content-type detection (FABRIC-3.md §I.4, FABRIC-2.md
|
||||
* Section U item 14). mkcapsule's own header comment has long said "the
|
||||
* file extension is preserved verbatim and is meaningful only at load
|
||||
* time" -- nothing has ever checked that a file's actual bytes match
|
||||
* what its extension claims. This is a cross-check, not a new content
|
||||
* pipeline: process_file() below calls it once per file and WARNS (never
|
||||
* refuses -- same "land WARN-only first" rollout this project already
|
||||
* used for capsule signing) on a mismatch. Deliberately narrow: this
|
||||
* project's own capsule universe is exactly four content shapes today
|
||||
* (FORTH source, markdown, DER binary, TTF/OpenType font), not a general
|
||||
* multimedia magic-number library.
|
||||
*/
|
||||
typedef enum {
|
||||
CONTENT_TYPE_UNKNOWN = 0, /* no expectation, or couldn't classify -- not an error */
|
||||
CONTENT_TYPE_TEXT, /* .4th (FORTH source), .md (markdown) */
|
||||
CONTENT_TYPE_DER, /* .der (X.509 cert / PKCS8 key, both ASN.1 DER) */
|
||||
CONTENT_TYPE_TTF /* .ttf (TrueType) or OpenType sharing the same extension */
|
||||
} content_type_t;
|
||||
|
||||
static const char *content_type_name(content_type_t t) {
|
||||
switch (t) {
|
||||
case CONTENT_TYPE_TEXT: return "text";
|
||||
case CONTENT_TYPE_DER: return "DER binary";
|
||||
case CONTENT_TYPE_TTF: return "TTF/OpenType font";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/* Detects by leading bytes, not extension. TTF/OpenType: the four
|
||||
* well-known sfnt version tags. DER: ASN.1 SEQUENCE tag (0x30) -- every
|
||||
* DER-encoded cert or PKCS8 key in this codebase's own universe (X.509
|
||||
* certs, PKCS8 private keys) opens with one, per the existing
|
||||
* x509_ed25519.c/pkcs8_ed25519.c parsers this mirrors. Text has no fixed
|
||||
* magic -- heuristic: every byte is printable ASCII or TAB/CR/LF; a
|
||||
* capsule embeds source/markdown, never arbitrary binary under those two
|
||||
* extensions, so this heuristic doesn't need to be general-purpose. */
|
||||
static content_type_t detect_content_type(const uint8_t *data, size_t len) {
|
||||
if (len >= 4 &&
|
||||
((data[0] == 0x00 && data[1] == 0x01 && data[2] == 0x00 && data[3] == 0x00) ||
|
||||
memcmp(data, "OTTO", 4) == 0 || memcmp(data, "true", 4) == 0 ||
|
||||
memcmp(data, "ttcf", 4) == 0)) {
|
||||
return CONTENT_TYPE_TTF;
|
||||
}
|
||||
if (len >= 1 && data[0] == 0x30) {
|
||||
return CONTENT_TYPE_DER;
|
||||
}
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
uint8_t c = data[i];
|
||||
if (c == '\t' || c == '\r' || c == '\n') continue;
|
||||
if (c < 0x20 || c > 0x7E) return CONTENT_TYPE_UNKNOWN;
|
||||
}
|
||||
return CONTENT_TYPE_TEXT;
|
||||
}
|
||||
|
||||
/* What a file's extension implies its content should be. UNKNOWN means
|
||||
* "no expectation" (e.g. fonts:README.md's sibling files, or anything
|
||||
* outside this project's four known shapes) -- process_file() skips the
|
||||
* cross-check entirely in that case, never warns on an extension it has
|
||||
* no opinion about. */
|
||||
static content_type_t expected_type_from_ext(const char *fpath) {
|
||||
size_t n = strlen(fpath);
|
||||
if (n >= 4 && strcmp(fpath + n - 4, ".der") == 0) return CONTENT_TYPE_DER;
|
||||
if (n >= 4 && strcmp(fpath + n - 4, ".ttf") == 0) return CONTENT_TYPE_TTF;
|
||||
if (n >= 4 && strcmp(fpath + n - 4, ".4th") == 0) return CONTENT_TYPE_TEXT;
|
||||
if (n >= 3 && strcmp(fpath + n - 3, ".md") == 0) return CONTENT_TYPE_TEXT;
|
||||
return CONTENT_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build the colon-separated capsule name from a relative path.
|
||||
* Returns 1 on success, 0 if the name would exceed CAPSULE_NAME_MAX-1 bytes.
|
||||
@@ -436,6 +505,20 @@ static int process_file(const char *fpath, const struct stat *sb,
|
||||
}
|
||||
}
|
||||
|
||||
/* Magic-number content-type cross-check (FABRIC-3.md §I.4) --
|
||||
* WARN-only, never refuses; UNKNOWN on either side means "no
|
||||
* expectation," not a mismatch. */
|
||||
{
|
||||
content_type_t want = expected_type_from_ext(fpath);
|
||||
content_type_t got = detect_content_type(data, (size_t)size);
|
||||
if (want != CONTENT_TYPE_UNKNOWN && got != CONTENT_TYPE_UNKNOWN && want != got) {
|
||||
fprintf(stderr,
|
||||
"mkcapsule: WARNING: %s: content doesn't match its extension "
|
||||
"(expected %s, detected %s)\n",
|
||||
fpath, content_type_name(want), content_type_name(got));
|
||||
}
|
||||
}
|
||||
|
||||
/* Fill entry */
|
||||
CapsuleEntry *e = &capsules[capsule_count];
|
||||
snprintf(e->path, MAX_PATH_LEN, "%s", fpath);
|
||||
|
||||
Reference in New Issue
Block a user