Skip to content

Latest commit

 

History

History
30 lines (28 loc) · 7.68 KB

File metadata and controls

30 lines (28 loc) · 7.68 KB

Was jetzt richtig ist (das Wichtigste zuerst) Die kritische Lücke aus der Format-Review ist geschlossen. Die Signatur deckt [0 .. total_len − 64) ab — also den festen Header und alle variablen Regionen. Damit sind Chunk-Hashes, PQC-Public-Key und Device-Binding jetzt authentifiziert. Die Hybrid-Vertrauenskette (Ed25519 authentifiziert den PQC-Schlüssel) ist wiederhergestellt, das Device-Binding lässt sich nicht mehr umschreiben. Das war der eine Punkt, der vor Feldeinsatz zwingend war, und er sitzt. Auch gut: svn/stage1_svn/key_epoch/key_index signiert (Anti-Rollback und Schlüssel-Revocation gebunden), vendor_id/product_id/hw_rev_* signiert (Manifest lässt sich nicht auf ein anderes Produkt umlenken), min_reader_* signiert (Reader-Downgrade blockiert). Das hartkodierte Ed25519 ohne Algorithmus-Feld ist bewusst ein Sicherheitsgewinn, nicht ein Mangel — es eliminiert die gesamte Algorithmus-Verwechslungs- und alg=none-Klasse, die COSE/JOSE plagt. Agilität läuft sauber über die Version. Sicherheits-Ebene Zwei Punkte, die echte Aufmerksamkeit brauchen, plus mehrere Reader-Pflichten. Die Länge, die den Hash-Loop steuert, ist angreiferkontrolliert. TBM1_SIGNED_LEN(hdr) liest total_len aus ungeprüftem Input, und dieser Wert bestimmt, über wie viele Bytes gehasht wird. Ein manipuliertes total_len lässt die Signatur zwar scheitern (falscher Bereich → falscher Hash) — aber bevor das passiert, hat der Verifier schon total_len − 64 Bytes gelesen. Ein riesiges total_len heißt Out-of-Bounds-Read über den Staging-Puffer hinaus, während gehasht wird. Der Reader muss total_len gegen die physische Staging-Kapazität beschränken, bevor er es benutzt, und total_len ≥ 512 + 64 prüfen (sonst unterläuft total_len − 64 im size_t zu einem gigantischen Wert). Das ist die schärfste Parser-Härtung hier. Der „reserved muss null sein"-Zero-Check steht im direkten Widerspruch zum additiven Wachstum. Der _reserved_tail-Kommentar sagt beides gleichzeitig: „Must be zero" und „future minor-version fields". Das kann nicht beides gelten. Wenn ein Reader Null erzwingt, lehnt er in dem Moment, in dem eine Minor-Version ein Feld in den Schwanz legt, ein Manifest ab, das er eigentlich akzeptieren müsste (Minor-Versionen sind per Definition abwärtskompatibel). Die Auflösung ist die klassische Reserved-Disziplin, getrennt nach Seite: Encoder-Pflicht = ungenutzten Schwanz nullen; Reader-Toleranz = auf Nicht-Null im Schwanz nicht ablehnen. Weil der Schwanz signiert ist, ist Toleranz auch sicher — nur der legitime Signierer füllt ihn, und semantisch wird ignoriert, was der Reader nicht kennt. Dasselbe gilt für _rsvd/_rsvd0 in Region- und Descriptor-Struct. Formulier die Spec entsprechend um, sonst ist additives Wachstum am Tag eins gebrochen. Der Header ist nur so stark wie sein Reader. Diese Prüfungen müssen im nicht gezeigten .c sitzen, sonst hebeln sie das Format aus:

fixed_len == 512 prüfen, bevor Offsets vertraut wird. image_count in [1..4] klemmen — sonst laufen Image-Schleifen über das Array hinaus in _reserved_tail und weiter. chunk_size != 0 (sonst Division-durch-null in der num_chunks-Kreuzprüfung) und num_chunks == ceil(installed_size / chunk_size) als Konsistenz-Gate. key_index gegen die Zahl der provisionierten eFuse-Slots beschränken (OOB-Read in den Schlüsselspeicher vermeiden). Überlaufsichere Region-Bounds: off <= total_len && len <= total_len − off, nie off + len <= total_len. (flags_critical & ~TBM1_CRIT_KNOWN_MASK) == 0, sonst reject — das ist der ganze Zweck des Critical-Flag-Worts.

Region-Directory braucht kanonische Regeln. tbm1_find_region gibt den ersten Treffer zurück. Bei doppelten region_ids (buggy Encoder) nimmt der Reader stillschweigend den ersten und ignoriert einen zweiten, der andere Daten benennt. Der Validierungs-Loop sollte Duplikate ablehnen und die „aufsteigend sortiert, nicht überlappend"-Regel erzwingen. Und ein Footgun: tbm1_find_region(hdr, TBM1_REGION_NONE) matcht den ersten leeren Slot — mit id == 0 aufgerufen liefert es Unsinn. Ein id != 0-Guard oder eine Doku-Warnung. Korrektheit / Interop Chunk-Hash-Partitionierung ist unterspezifiziert — der konkreteste Interop-Landmine. Es gibt eine REGION_CHUNK_HASHES, aber bis zu vier Images mit je eigenem num_chunks. Wie schneidet der Reader die Region in Per-Image-Scheiben? Aktuell nur per Konvention „Hashes in Descriptor-Reihenfolge konkateniert". Das steht nirgends im Header und wird zwischen Encoder und Reader stillschweigend vorausgesetzt — genau die Sorte Annahme, an der zwei Implementierungen auseinanderlaufen. Schreib die Regel explizit hin (Reader bildet Per-Image-Offsets per Präfix-Summe über num_chunks_i × 32) und lass den Reader region.len == Σ(num_chunks_i) × 32 gegenprüfen. Alternativ ein chunk_hash_off relativ zum Regionsstart in den Descriptor — aber die Konkatenationsregel plus Längen-Kreuzprüfung ist billiger. base_fingerprint[8] — Zweck klarstellen. 64-Bit-Trunkierung von SHA-256 ist als Identifikation der Delta-Basis völlig in Ordnung (die echte Integrität des Patch-Ergebnisses fällt ohnehin über die signierten Chunk-Hashes). Als Sicherheits-Prüfung wäre 64 Bit gegen absichtliche Kollisionen schwach. Die Doku sollte sagen: Vorabprüfung, nicht Sicherheitsanker — base_svn macht das eigentliche Gating. Verständlichkeit / Bedienung Die Doxygen-Doku ist stark, die Sektions-Gruppierung mit Byte-Kommentaren macht das Layout auf einen Blick lesbar, und die abgeleiteten Konstanten (TBM1_SIGNED_LEN, TBM1_CRC_LEN) sind gut benannt. Zwei kleine Reibungspunkte: TBM1_SIGNED_LEN(hdr) als Makro, das hdr dereferenziert, hat den üblichen Doppel-Auswertungs-Footgun und unterläuft bei total_len < 64. Eine static inline-Funktion mit dem oben genannten Bounds-Guard ist sicherer und gleich schnell. Und eine kognitive Dissonanz fürs Onboarding: Die Magic buchstabiert „TBM1", aber version_major ist 2. Die „1" ist der Format-Familienname, version_major die Schema-Version darin — das ist eine legitime Trennung, aber jemand, der frisch draufschaut, stolpert. Ein Satz in der Doku („TBM1 = Formatfamilie, nicht Versionsnummer") räumt das aus. Zukunft Der additive Wachstumspfad ist grundsolide — sobald die Reserved-Toleranz oben geklärt ist. Zwei Grenzen ehrlich benennen, damit sie niemanden später überraschen: TBM1_MAX_IMAGES == 4 und TBM1_MAX_REGIONS == 8 sind die harten Decken des Formats. Weil images[] und regions[] feste Arrays an festen Offsets sind, kann der reserved_tail sie nicht erweitern — mehr als 4 Images oder 8 Regionen ist ein Major-Version-Ereignis mit neuem Reader. Das ist eine vertretbare Entscheidung (die festen Offsets sind mehr wert als Flexibilität), sollte aber als bewusste Obergrenze dokumentiert sein, nicht als stillschweigende Annahme. Und die Forward-Compat-Story ist kohärent: min_reader* als grobes versionsbasiertes Gate, flags_critical als feines feature-basiertes Gate, beide signiert — plus die Reader-Toleranz auf Reserved und flags_info. Diese drei Mechanismen zusammen ergeben ein sauberes „must-understand vs. may-ignore", das dem entspricht, was CBOR/COSE über Critical-Header-Parameter macht — nur ohne den Parser-Differential-Ballast. Verdikt Der Entwurf ist auf der kryptografischen Kern-Ebene jetzt gleichwertig zu SUIT und wegen des fehlenden Algorithmus-Felds und der einen kanonischen Lesart in der Angriffsfläche kleiner. Die offenen Punkte sind fast alle Reader-Vertrag, nicht Header-Layout: die angreiferkontrollierte total_len-Grenze, die Reserved-Toleranz-vs-Zero-Check-Klärung, die Chunk-Hash-Partitionierungsregel und der Katalog der Pflichtprüfungen. Der Header selbst braucht nur zwei Textänderungen (Reserved-Semantik trennen, TBM1_SIGNED_LEN zur bounds-prüfenden Funktion machen) und eine Doku-Zeile zu den harten Maxima.