Skip to content

Commit 3e267fc

Browse files
fonts: harden TTF and OTF detection (#750)
* ttf: make detection stronger Checking just the first 4 bytes is not enough, many false positives occur. This commit makes detection inspect the first TrueType table name against a pre-defined list. * otf: make detection stronger Previously, it was only bytes.HasPrefix(raw, "OTTO\x00"). This commit makes it stronger by checking for the first sfnt table being "CFF ". * golangci: fixes
1 parent 789eb1d commit 3e267fc

File tree

4 files changed

+44
-7
lines changed

4 files changed

+44
-7
lines changed

internal/json/parser_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,7 @@ func TestStack(t *testing.T) {
738738
}
739739

740740
join := func(bs [][]byte) string {
741-
ret := []string{}
741+
ret := make([]string, 0, len(bs))
742742
for _, b := range bs {
743743
ret = append(ret, string(b))
744744
}

internal/magic/font.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package magic
22

33
import (
44
"bytes"
5+
"encoding/binary"
56
)
67

78
// Woff matches a Web Open Font Format file.
@@ -16,15 +17,51 @@ func Woff2(raw []byte, _ uint32) bool {
1617

1718
// Otf matches an OpenType font file.
1819
func Otf(raw []byte, _ uint32) bool {
19-
return bytes.HasPrefix(raw, []byte{0x4F, 0x54, 0x54, 0x4F, 0x00})
20+
// After OTTO an little endian int16 specifies the number of tables.
21+
// Since the number of tables cannot exceed 256, the first byte of the
22+
// int16 is always 0. PUID: fmt/520
23+
return len(raw) > 48 && bytes.HasPrefix(raw, []byte("OTTO\x00")) &&
24+
bytes.Contains(raw[12:48], []byte("CFF "))
2025
}
2126

2227
// Ttf matches a TrueType font file.
2328
func Ttf(raw []byte, limit uint32) bool {
2429
if !bytes.HasPrefix(raw, []byte{0x00, 0x01, 0x00, 0x00}) {
2530
return false
2631
}
27-
return !MsAccessAce(raw, limit) && !MsAccessMdb(raw, limit)
32+
return hasSFNTTable(raw)
33+
}
34+
35+
func hasSFNTTable(raw []byte) bool {
36+
// 49 possible tables as explained below
37+
if len(raw) < 16 || binary.BigEndian.Uint16(raw[4:]) >= 49 {
38+
return false
39+
}
40+
41+
// libmagic says there are 47 table names in specification, but it seems
42+
// they reached 49 in the meantime.
43+
// https://github.com/file/file/blob/5184ca2471c0e801c156ee120a90e669fe27b31d/magic/Magdir/fonts#L279
44+
// At the same time, the TrueType docs seem misleading:
45+
// 1. https://developer.apple.com/fonts/TrueType-Reference-Manual/index.html
46+
// 2. https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html
47+
// Page 1. has 48 tables. Page 2. has 49 tables. The diff is the gcid table.
48+
// Take a permissive approach,
49+
possibleTables := []string{
50+
"acnt", "ankr", "avar", "bdat", "bhed", "bloc", "bsln", "cmap", "cvar",
51+
"cvt ", "EBSC", "fdsc", "feat", "fmtx", "fond", "fpgm", "fvar", "gasp",
52+
"gcid", "glyf", "gvar", "hdmx", "head", "hhea", "hmtx", "hvgl", "hvpm",
53+
"just", "kern", "kerx", "lcar", "loca", "ltag", "maxp", "meta", "mort",
54+
"morx", "name", "opbd", "OS/2", "post", "prep", "prop", "sbix", "trak",
55+
"vhea", "vmtx", "xref", "Zapf", "DSIG",
56+
}
57+
// TODO: benchmark these strings comparisons. They are 4 bytes, so another
58+
// option is to compare them as ints. Probably less readable that way.
59+
for _, t := range possibleTables {
60+
if string(raw[12:16]) == t {
61+
return true
62+
}
63+
}
64+
return false
2865
}
2966

3067
// Eot matches an Embedded OpenType font file.

mime.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func (m *MIME) match(in []byte, readLimit uint32) *MIME {
118118

119119
// flatten transforms an hierarchy of MIMEs into a slice of MIMEs.
120120
func (m *MIME) flatten() []*MIME {
121-
out := []*MIME{m}
121+
out := []*MIME{m} //nolint:prealloc
122122
for _, c := range m.children {
123123
out = append(out, c.flatten()...)
124124
}

mimetype_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ a,"b`,
250250
{"ogg", "OggS\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x80\xbc\x81_\x00\x00\x00\x00\xd0\xfbP\x84\x01@fishead\x00\x03", "video/ogg", one},
251251
{"ogg spx oga", "OggS\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\xc7w\xaa\x15\x00\x00\x00\x00V&\x88\x89\x01PSpeex 1", "audio/ogg", one},
252252
{"one", "\xe4\x52\x5c\x7b\x8c\xd8\xa7\x4d\xae\xb1\x53\x78\xd0\x29\x96\xd3", "application/onenote", one},
253-
{"otf", "OTTO\x00", "font/otf", one},
253+
{"otf", "OTTO\x00\x0c\x00\x80\x00\x03\x00\x40\x43\x46\x46 " + offset(36, ""), "font/otf", one},
254254
{"otg", "PK\x03\x04\x14\x00\x00\x08\x00\x00\xd1Y\xa8N\xdf%\xad\xe94\x00\x00\x004\x00\x00\x00\x08\x00\x00\x00mimetypeapplication/vnd.oasis.opendocument.graphics-template", "application/vnd.oasis.opendocument.graphics-template", one},
255255
{"otp", "PK\x03\x04\x14\x00\x00\x08\x00\x00\xc4X\xa8N\xef\n\x14:8\x00\x00\x008\x00\x00\x00\x08\x00\x00\x00mimetypeapplication/vnd.oasis.opendocument.presentation-template", "application/vnd.oasis.opendocument.presentation-template", one},
256256
{"ots", "PK\x03\x04\x14\x00\x00\x08\x00\x00\x1bV\xa8N{\x96\xa3N7\x00\x00\x007\x00\x00\x00\x08\x00\x00\x00mimetypeapplication/vnd.oasis.opendocument.spreadsheet-template", "application/vnd.oasis.opendocument.spreadsheet-template", one},
@@ -328,7 +328,7 @@ ENDHDR`,
328328
{"tiff", "II*\x00", "image/tiff", one},
329329
{"tsv", "a\t\"b\"\tc\n1\t2\t3", "text/tab-separated-values", all},
330330
{"ttc", "ttcf\x00\x01\x00\x00", "font/collection", one},
331-
{"ttf", "\x00\x01\x00\x00", "font/ttf", one},
331+
{"ttf", "\x00\x01\x00\x00\x00\x0f\x00\x80\x00\x03\x00\x70\x4f\x53\x2f\x32", "font/ttf", one},
332332
{"tzfile", fromDisk("tzfile"), "application/tzif", one},
333333
{"utf16bebom txt", "\xfe\xff\x00\x74\x00\x68\x00\x69\x00\x73", "text/plain; charset=utf-16be", none},
334334
{"utf16lebom txt", "\xff\xfe\x74\x00\x68\x00\x69\x00\x73\x00", "text/plain; charset=utf-16le", none},
@@ -651,7 +651,7 @@ func BenchmarkAllTogether(b *testing.B) {
651651
if _, err := io.ReadFull(r, randData); err != io.ErrUnexpectedEOF && err != nil {
652652
b.Fatal(err)
653653
}
654-
datas := [][]byte{}
654+
datas := make([][]byte, 0, len(testcases))
655655
for _, tc := range testcases {
656656
datas = append(datas, []byte(tc.data))
657657
}

0 commit comments

Comments
 (0)