10 ms·
Recommendations for designing magic numbers of binary file formats
- weinzierl 2y agoWhy is ELF a good example? 7F 45 4C 46 - MUST be the very first N bytes in the file -> check - MUST be at least four bytes long, eight is better -> check, but only four - MUST include at least one byte with the high bit set -> nope - MUST include a byte sequence that is invalid UTF-8 -> nope - SHOULD include a zero byte -> nope So, just 1.5 out of 5. Not good. By the way, does anyone know the reason it starts with DEL (7F) specifically?
- 0xFF0123 2y agoIt's (7F) ELF
- indigoabstract 2y agoHmm, I would expect that to be 31F, if it stood for "ELF" in correct Hexspeak.
- rickdeckard 2y agoI think what the author likes is the fact that the first 4 bytes are defined as 0x7F followed by the file extension "ELF" in ASCII, which makes it a quite robust identifier. And to be fair, including the 4 byte following the magic number make the ELF-format qualify at least 3 out of the 4 'MUST' requirements: _ 7F 45 4C 46 - 0x04: Either 01 or 02 (defines 32bit or 64bit) - 0x05: Either 01 or 02 (defines Little Endian or Big Endian) - 0x06: Set to 01 (ELF-version) - 0x07: 00~12 (Target OS ABI) Still not a shiny example though...
- weinzierl 2y agoMaybe, yes. There are certainly worse offenders than ELF, but I still don't see how it satisfies 3 out of the 4 MUSTs. There is no byte with the high bit set and it is a valid ASCII sequence and therefore also valid UTF-8. When it comes to the "eight is better" requirement, at least Linux does not care what comes after the fourth byte for identification purposes, so I think that does not count either.
- deleted 2y ago[deleted]
- layer8 2y agoI agree that it isn’t a particularly good example, especially with reference to the stated rules. Many binary-detection routines will treat DEL as a regular ASCII character.
- conaclos 2y agoSHOULD include a zero byte I guess it is expected to be at the end of the magic number to act as a null-termibated string? MUST include a byte sequence that is invalid UTF-8 I guess it is to differentiate a text file from a specific format? MUST include at least one byte with the high bit set Any reason?
- wjholden 2y agoThe author explains their reasoning in the next post: https://hackers.town/@zwol/114155807716413069 https://hackers.town/@zwol/114155807716413069
- robinhouston 2y agoI think the idea of all these is to make the file not be recognised as text (which doesn't allow nulls), ASCII (which doesn't use the high bit), UTF-8 (which doesn't allow invalid UTF-8 sequences). Basically so that no valid file in this binary format will be incorrectly misidentified as a text file.
- CrossVR 2y agoI think preventing the opposite is more pressing. Imagine creating a text file and it just so happens that the first 8 characters match a magic number of an image format. Now when you go back to edit your text file it is suddenly recognized as an image file by your file browser.
- rcxdude 2y agoWell, the 3rd point follows from the second: all sequences without the high bit set are valid ASCII, and all valid ASCII sequences are valid UTF-8.
- dark-star 2y agothe high bit one is pretty ancient by now. I don't think we have transmission methods that are not 8-bit-clean anymore. And if your file detector detects "generic text" before any more specialized detections (like "GIF87a"), and thus treats everything that starts with ASCII bytes as "generic text", then sorry, but your detector is badly broken There's no reason for the high-bit "rule" in 2025. I would argue the same goes for the 0-byte rule. If you use strcmp() in your magic byte detector, then you're doing it wrong
- hgomersall 2y agoAs anyone able to break down why those requirements are desirable?
- rickdeckard 2y agoFrom the top of my head, most are to make it as clear as possible that the file is binary and NOT text: > MUST be the very first N bytes in the file For every system to be able to parse it without loading the entire file > MUST be at least four bytes long, eight is better To reduce risk of two different binary files on the same system having the same magic number > MUST include at least one byte with the high bit set To avoid wrongful identification as an ASCII file (ASCII doesn't use the high bit) > MUST include a byte sequence that is invalid UTF-8 To avoid wrongful identification as UTF-8 text file > SHOULD include a zero byte To avoid wrongful identification as ANY text file
- gus_massa 2y ago>> MUST be the very first N bytes in the file > For every system to be able to parse it without loading the entire file It also solves the ambiguity problem, zip files have the magic numbers at the end, and most other files like pdf have the magic numbers at the beginning, so you can have a file that is both a pdf and a zip file.
- account42 2y agoFor ZIP files this is a design GOAL to a allow things like self-extracting archives.
- silvestrov 2y agoIt is a security nightmare.
- bongodongobob 2y agoCan you explain why?
- baggy_trough 2y agoSolve this forever by choosing a header that adheres to these properties, then add a UUID for the actual format.
- masfuerte 2y agoYou could call it the Compound Document Format.
- badmintonbaseba 2y agoThen there is mkv/webm, where strictly speaking you need to implement at least part of an EBML parser to distinguish them. Possibly why no other file format adopts EBML, everything just recognizes it as either of mkv or matroska based on dodgy heuristics.
- gardaani 2y agoMany modern file formats are based on generic container formats (zip, riff, json, toml, xml without namespaces, ..). Identifying those files requires reading the entire file and then guessing the format from the contents. Magic numbers are becoming rare, which is a shame.
- gardaani 2y agoWikipedia has a good explanation why the PNG magic number is 89 50 4e 47 0d 0a 1a 0a. It has some good features, such as the end-of-file character for DOS and detection of line ending conversions. https://en.wikipedia.org/wiki/PNG#File_header https://en.wikipedia.org/wiki/PNG#File_header
- CrossVR 2y agoAt first I wasn't sure why it contained a separate Unix line feed when you would already be able to detect a Unix to DOS conversion from the DOS line ending: 0D 0A 1A 0A -> 0D 0D 0A 1A 0D 0A But of course this isn't to try and detect a Unix-to-DOS conversion, it's to detect a roundtrip DOS-to-Unix-to-DOS conversion: 0D 0A 1A 0A -> 0A 1A 0A -> 0D 0A 1A 0D 0A Certainly a very well thought-out magic number.
- layer8 2y agoUnix2dos is idempotent on CRLF, it doesn’t change it to CRCRLF. Therefore converted singular LFs elsewhere in the file wouldn’t be recognized by the magic-number check if it only contained CRLF. This isn’t about roundtrip conversion.
- Dwedit 2y agoIt's also detecting when a file on DOS/Windows is opened in "ASCII mode" rather than binary mode. When opened in ASCII mode, "\r\n" is automatically converted to "\n" upon reading the data.
- nayuki 2y agoThe old PNG specification also explained the rationale: http://www.libpng.org/pub/png/spec/1.2/PNG-Rationale.html#R.PNG-file-signature http://www.libpng.org/pub/png/spec/1.2/PNG-Rationale.html#R.... But the new spec doesn't explain: https://www.w3.org/TR/2003/REC-PNG-20031110/ https://www.w3.org/TR/2003/REC-PNG-20031110/
- somat 2y agoThat is unfortunate. Not enough standards have rationale or intent sections. On the one hand I sort of understand why they don't "If it is not critical and load-bearing to the standard. Why is it in there? it is just noise that will confuse the issue." On the other hand, it can provide very important clues as to the why of the standard, not just the what. While the standards authors understood why they did things the way they did, many years later when we read it often we are left with more questions than answers.
- ajross 2y agoUnpopular opinion: this is all needless pedantry. At best this gives parsers like file managers a cleaner path to recognizing the specific version of the specific format you're designing. Your successors won't evolve the format with the same rigor you think you're applying now. They just won't. They'll make a "compatible" change at some point in the future which will (1) be actually backwards compatible! yet (2) need to be detected in some affirmative way. Which it won't be. And your magic number will just end up being a wart like all the rest. This isn't a solvable problem. File formats evolve in messy ways, they always have and always will, and "magic numbers" just aren't an important enough part of the solution to be worth freaking out about. Just make it unique; read some bytes out of /dev/random, whatever. Arguments like the one here about making them a safe nul-terminated string that is guaranteed to be utf-8 invalid are not going to help anyone in the long term.
- CrossVR 2y agoThe magic number isn't about recognizing specific versions. That's just an added benefit if you choose to add that to the magic number. It is to solve the problem of how to build a file manager that can efficiently recognize all the file types in a large folder without relying on file name extensions. If you don't include a magic number a file manager would need to attempt to parse the file format before it can determine which file type it is.
- CamperBob2 2y agoFilename extensions are pretty useful. They were adopted for very good reasons, and every attempt to hide them, pretend they don't matter, or otherwise make them go away has only made things worse. You still need a way to make it hard to fool people with deceptive extensions, though, and that's where the magic numbers come in.
- ajross 2y ago> The magic number isn't about recognizing specific versions Yes it is, though. Does your file manager want to display Excel files differently from .jar files? They're both different "versions" of the same file format! Phil Katz in 1988 or whatever could have followed the pedantry in the linked article to the letter (he didn't). And it wouldn't have helped the problem at hand one bit.
- shagie 2y agoThe magic file (man magic / man file) is a neat one to read. On my Mac, this is located in /usr/share/file/magic/ while I recall on a unix distribution I worked on it was /etc/magic The file itself has a format that can test a file and identify it (and possibly more useful information) that is read by the file command. # Various dictionary images used by OpenFirware FORTH environment 0 lelong 0xe1a00000 >8 lelong 0xe1a00000 # skip raspberry pi kernel image kernel7.img by checking for positive text length >>24 lelong >0 ARM OpenFirmware FORTH Dictionary, >>>24 lelong x Text length: %d bytes, >>>28 lelong x Data length: %d bytes, >>>32 lelong x Text Relocation Table length: %d bytes, >>>36 lelong x Data Relocation Table length: %d bytes, >>>40 lelong x Entry Point: %#08X, >>>44 lelong x BSS length: %d bytes
- chrismorgan 2y agoOn Arch, per `file`: /usr/share/file/misc/magic.mgc: magic binary file for file(1) cmd (version 20) (little endian) Per magic(5), it could also be “a directory of source text magic pattern fragment files in /usr/share/file/misc/magic”. The original sources are mirrored in <https://github.com/file/file/tree/master/magic/Magdir https://github.com/file/file/tree/master/magic/Magdir>. The identification of the magic.mgc file itself comes from <https://github.com/file/file/blob/0fa0ffd15ff17d798e2f985447af47ab8c3440fb/magic/Magdir/magic#L84-L91 https://github.com/file/file/blob/0fa0ffd15ff17d798e2f985447...>.
- petertodd 2y agoThat's basically how I designed the magic bytes for the OpenTimestamps proof files: $ hexdump -C foo.ots 00000000 00 4f 70 65 6e 54 69 6d 65 73 74 61 6d 70 73 00 |.OpenTimestamps.| 00000010 00 50 72 6f 6f 66 00 bf 89 e2 e8 84 e8 92 94 01 |.Proof..........| 0) Magic is at the beginning of the file. 1) Starts with a null-byte to make it clear this is binary, not text. 2) Includes a human-readable part to make it easy to figure out what the file is in hex dumps. 3) 8 bytes of randomly chosen bytes, all of which greater than 0x7F to ensure they're not ASCII. 3) Finally, a one-byte major version number. 4) Total length (including major version) is 32 bytes to fit nicely in a hex dump.
- RustyRussell 2y agoThese days I generally advise that you interpret the version number as odd and even bits: odd means it's compatible with readers, even means it isn't.
- quag 2y agoThat sounds interesting. Can you say a little more about how this works?
- RustyRussell 2y agoIt's a trick I stole from ext2, and simplified. In that filesystem there are three bitsets: one for reading, one for writing, one for fsck. If you don't understand a bit you can't do that action. For most protocols there's only reading and writing, so you can use odd bits to mean "backwards compatible features, you can read even if you don't understand" and even for "stop, we broke compat".
- petertodd 2y agoThat's a good idea for filesystems. But OpenTimestamps Proofs aren't really "written to". They're created, and then later validated. Also, being cryptographic proofs, my philosophy is the validator should almost always understand them 100%, or not at all, to avoid any false proofs. That's also why I picked a binary encoding: it's difficult to parse an OTS proof incorrectly. An incorrect implementation will almost always fail to parse the proof at all, with a clear error, rather than silently parse the proof incorrectly.
- _ce5e 2y agoHonestly I just do any arbitrary uint64, it's good enough for a majority of usecases. Sometimes I like to have fun and encode a 1337-code easter egg in the hexadecimal representation
- eternityforest 2y agoWhy not just a zero followed by a UUID? UUIDs are the obvious standard everyone knows for identifying stuff. Maybe a zero, the UUID as ASCII, then another zero, then a human readable description for debugging and search, or a structured metadata header. But first, ask yourself why you are designing a binary format, unless maybe it's a new media container. When would someone ever want a binary file that's not zip, SQLite, or version controllable text?
- addaon 2y ago> When would someone ever want a binary file that's not zip, SQLite, or version controllable text? It feels like there’s an infinite number of answers to this, but to choose one: when choosing the format to allow memory mapping makes some operations simpler or more performant?
- Dwedit 2y agoTagged files are too useful. 4 byte tag name, 4 byte length of the object, then the binary data of the object. You see these all the time. Sometimes you see the size before the tag name. Occasionally, you also see a file header, followed by a size, and an "x", that often indicates a block of ZLIB compressed data.
- lelanthran 2y agoThey're called TLV (Tag, Length, Value) and are used extensively in payment transaction systems.
- lelanthran 2y ago> But first, ask yourself why you are designing a binary format, unless maybe it's a new media container. > When would someone ever want a binary file that's not zip, SQLite, or version controllable text? Maybe I'm not getting the humour here, but in case you are being serious binary files do have a few advantages over text formats. 1. Quick detection (say, for dispatching to a handler) 2. Rapid serialisation, both into and out of a running program (program state, in-memory data, etc) 3. Better and safer handling of binary data (no clunky roundtrips of binary blobs to text and back again) 4. Much better checksumming.
- secondcoming 2y ago`0xcafebabe` is the ultimate winner and follows none of these rules.
- ks2048 2y agoDo his "good examples" even follow his recommendations? e.g. I think they don't contain a 0x00 byte.
- cytocync 2y ago[dead]
- xg15 2y agoMost of those make intuitive sense, except this one: > MUST include a byte sequence that is invalid UTF-8 Making the magic number UTF-8 (or ASCII, which would still break the rule) would effectively turn it into a "magic string". Isn't that the better method for distinguishability? It's easier to pick unique memorable strings than unique memorable numbers, and you can also read it in a hex editor. What would be the downsides? Or is the idea of the requirement to distinguish the format from plaintext files? I'd think that the version number or the rest of the format already likely contained some invalid UTF-8 to ensure that.
- kbolino 2y agoThe key part of magic numbers is that they appear early in the file. You shouldn't rely on something that will probably appear at some point because that requires reading the entire file to detect its type. A single 0x00 byte, ideally the first byte, should be enough to indicate the file is binary and thus make the question of encoding moot. However, 0x00 is technically valid UTF-8 corresponding to U+0000 and ASCII NUL. So, throwing something like 0xFF in there also helps to throw off UTF-8 detection as well as adding high-bit-stripping detection. If you really wanted to go the extra mile, you could also include an impossible sequence of UTF-16 code units, but I think you'd need to dedicate 8 bytes to that: two invalid surrogate sequences, one in little-endian and the other in big-endian. You could possibly get by with just 6 bytes if you used a BOM in place of one of the surrogate pairs, or even just 4 with a BOM and an isolated surrogate if you can guarantee that nothing after it can be confused for the other half of a surrogate pair. However, throwing off UTF-16 detection doesn't seem that common or useful; many UTF-16 decoders don't even reject these invalid sequences.
- xg15 2y agoAh, so it's really distinguishing the file from plaintext then. Thanks!
- kazinator 2y agoIf there is any foreseeable need that the format will benefit from being executable, I would make the magic bytes looks like this: #!/usr/bin/whatever^@^@^@^@^@[HDR] A hash bang path terminated by a null, followed by some (aligned) binary material with version information and whatnot, all fitting into around 32 bytes. The header format could allow for variability in the path; the #! and [HDR] part could be enough to give it identify it.