Skip to content

TDirectory.get_KeyList: fSeekKey is 0 0 (bad key but KeyList is valid, e.g. uproot-issue261.root) #17

Open

Description

@nsmith-
No description provided.

Activity

  1. nsmith- commented on Sep 18, 2026

    @nsmith-
    OwnerAuthor

    🤖 AI generated content

    Findings below come from the root-io-spec reference now vendored in this repo at reference/root-io-spec (pinned at 9c3f776). Short version: the key on a key-list record is decorative — ROOT never validates it — and both files are third-party output with that key filled in wrongly. Dropping our check makes both files read completely; I tested it.

    What is actually in the bytes

    uproot-issue261.root: directory has fSeekKeys = 10048, fNbytesKeys = 106. The key at 10048 is

    00 00 00 3a 03 ec 00 00 00 34 68 92 ea f9 00 36 00 01
     fNbytes=58  v=1004  fObjlen=52    fDatime   klen=54 cyc=1
    00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00   05 "TFile" ...
     fSeekKey = 0             fSeekPdir = 0
    

    Three fields are wrong: fNbytes is 58 (= fKeylen + 4) where it should be 106, fSeekKey should be 10048, fSeekPdir should be 100. The payload after it is fine — count 1, then a valid TTree/events entry with fSeekKey 10176, fSeekPdir 100. uproot-issue412.root is byte-for-byte the same pattern (key at 10042, fNbytes 55 vs fNbytesKeys 101, both seeks 0, one valid TTree/test entry).

    What the spec says

    Directories §6.2: "The only supported way to find it is fSeekKeys from the directory record." §4.1: fNbytesKeys is used as a raw read length. And ROOT's own TDirectoryFile::ReadKeys builds the header key, reads it, and deletes it without checking anything (root/io/io/src/TDirectoryFile.cxx:1444-1468); the >= 64 && <= fsize sanity check is applied only to the entries. TKey::Streamer itself rejects only negative fKeylen/fObjlen/fNbytes (root/io/io/src/TKey.cxx:1423-1438), so zeros pass.

    Records §3.5 does say a reader SHOULD check fSeekKey — but that is for records reached by walking the chain, which is where the redundancy is a useful corruption check. This record is reached through fSeekKeys.

    The file, and the writer

    The spec's foreign-corpus notes (gen/foreign/IGNORE.toml) diagnose uproot-issue261.root for a different defect: "The record chain has a 70-byte hole: at 10427, where a record header should be, the first word is 0." I confirmed that too. It does not affect us — we never walk the chain — and the spec concludes ROOT "opens it and reads its TTree without a word" (Records §1). Also: both files set the >2 GB header flag (fVersion 1061800) on files of 10 kB and 2.3 MB, which per Directory.md §3 should be set only when fEND > 2000000000. Writer attribution is inference, but this is clearly not ROOT.

    Code

    src/rootfilespec/bootstrap/TDirectory.py:152-161 (KeyListLocator.read_from) and :113-124 (get_KeyList). Patching KeyListLocator.read_from to skip the fSeekKey/fSeekPdir checks makes both files pass the read test in full (uproot-issue412.root walks /test/b1, /test/b2). get_KeyList additionally raises on fNbytes != fNbytesKeys, which this file also violates; trust fNbytesKeys per §4.1. The in-code comment at TDirectory.py:154 ("this is when there is no streamerinfo for the file") is not right — both files have a StreamerInfo record (at 228 and 222 respectively).

    Verdict: file at fault, handle leniently. Size: S — relax two checks (optionally warn), delete the NotImplementedError. No dependencies. Closes 2 of the 22 current xfails.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions