diff --git a/bad-data/libmaxminddb/libmaxminddb-metadata-marker-only.mmdb b/bad-data/libmaxminddb/libmaxminddb-metadata-marker-only.mmdb new file mode 100644 index 0000000..e37ad45 --- /dev/null +++ b/bad-data/libmaxminddb/libmaxminddb-metadata-marker-only.mmdb @@ -0,0 +1 @@ +«ÍïMaxMind.com \ No newline at end of file diff --git a/bad-data/libmaxminddb/libmaxminddb-separator-record-max-left.mmdb b/bad-data/libmaxminddb/libmaxminddb-separator-record-max-left.mmdb new file mode 100644 index 0000000..b503bcd Binary files /dev/null and b/bad-data/libmaxminddb/libmaxminddb-separator-record-max-left.mmdb differ diff --git a/bad-data/libmaxminddb/libmaxminddb-separator-record-min-left.mmdb b/bad-data/libmaxminddb/libmaxminddb-separator-record-min-left.mmdb new file mode 100644 index 0000000..4a09726 Binary files /dev/null and b/bad-data/libmaxminddb/libmaxminddb-separator-record-min-left.mmdb differ diff --git a/bad-data/libmaxminddb/libmaxminddb-separator-record-min-right.mmdb b/bad-data/libmaxminddb/libmaxminddb-separator-record-min-right.mmdb new file mode 100644 index 0000000..06a180b Binary files /dev/null and b/bad-data/libmaxminddb/libmaxminddb-separator-record-min-right.mmdb differ diff --git a/pkg/writer/baddata.go b/pkg/writer/baddata.go index 088d602..1da599b 100644 --- a/pkg/writer/baddata.go +++ b/pkg/writer/baddata.go @@ -31,6 +31,10 @@ func (w *Writer) WriteBadDataDBs(target string) error { {"libmaxminddb-corrupt-search-tree.mmdb", buildCorruptSearchTreeDB()}, {"libmaxminddb-empty-map-last-in-metadata.mmdb", buildEmptyMapLastInMetadataDB()}, {"libmaxminddb-empty-array-last-in-metadata.mmdb", buildEmptyArrayLastInMetadataDB()}, + {"libmaxminddb-metadata-marker-only.mmdb", buildMetadataMarkerOnlyDB()}, + {"libmaxminddb-separator-record-min-left.mmdb", buildSeparatorRecordMinLeftDB()}, + {"libmaxminddb-separator-record-min-right.mmdb", buildSeparatorRecordMinRightDB()}, + {"libmaxminddb-separator-record-max-left.mmdb", buildSeparatorRecordMaxLeftDB()}, } { if err := writeRawDB(target, db.name, db.data); err != nil { return fmt.Errorf("writing %s: %w", db.name, err) diff --git a/pkg/writer/rawmmdb.go b/pkg/writer/rawmmdb.go index 5936b79..b91b34c 100644 --- a/pkg/writer/rawmmdb.go +++ b/pkg/writer/rawmmdb.go @@ -122,12 +122,18 @@ func writeEmptyArray(buf []byte) int { // writeSearchTree writes a 1-node search tree with 24-bit records, // both pointing to the data section. func writeSearchTree(buf []byte, recordValue uint32) int { - buf[0] = byte((recordValue >> 16) & 0xFF) - buf[1] = byte((recordValue >> 8) & 0xFF) - buf[2] = byte(recordValue & 0xFF) - buf[3] = byte((recordValue >> 16) & 0xFF) - buf[4] = byte((recordValue >> 8) & 0xFF) - buf[5] = byte(recordValue & 0xFF) + return writeSearchTreeRecords(buf, recordValue, recordValue) +} + +// writeSearchTreeRecords writes a 1-node search tree with 24-bit records +// where the left and right records can hold different values. +func writeSearchTreeRecords(buf []byte, leftRecord, rightRecord uint32) int { + buf[0] = byte((leftRecord >> 16) & 0xFF) + buf[1] = byte((leftRecord >> 8) & 0xFF) + buf[2] = byte(leftRecord & 0xFF) + buf[3] = byte((rightRecord >> 16) & 0xFF) + buf[4] = byte((rightRecord >> 8) & 0xFF) + buf[5] = byte(rightRecord & 0xFF) return 6 } @@ -285,6 +291,69 @@ func buildEmptyMapLastInMetadataDB() []byte { return buildSimpleDB(writeMetadataBlockEmptyMapLast) } +// buildMetadataMarkerOnlyDB returns a file that contains only the metadata +// marker (\xab\xcd\xefMaxMind.com) with no metadata bytes following. +// Readers should reject this as invalid metadata rather than allowing a +// zero-length metadata section to reach the decoder. +func buildMetadataMarkerOnlyDB() []byte { + return []byte(metadataMarker) +} + +// buildSeparatorRecordDB creates a complete 1-node MMDB where the left and +// right records of node 0 hold the given values. With nodeCount = 1, any +// record value in the half-open range [2, 17) points into the 16-byte +// separator between the search tree and data section. Readers should reject +// such records as a corrupt search tree rather than exposing them as data +// entries with underflowed offsets. +func buildSeparatorRecordDB(leftRecord, rightRecord uint32) []byte { + const nodeCount = 1 + const buildEpoch = 1_000_000_000 + + buf := make([]byte, 1024) + pos := 0 + + pos += writeSearchTreeRecords(buf[pos:], leftRecord, rightRecord) + + // 16-byte null separator + pos += dataSeparatorSize + + // Data section: a simple map so a valid record (nodeCount + 16) can + // resolve to a real entry. + pos += writeMap(buf[pos:], 1) + pos += writeString(buf[pos:], "ip") + pos += writeString(buf[pos:], "test") + + pos += writeMetadataBlock(buf[pos:], nodeCount, buildEpoch) + + return buf[:pos] +} + +// buildSeparatorRecordMinLeftDB creates an MMDB whose node 0 left record +// equals nodeCount + 1 (the first byte of the data section separator). +func buildSeparatorRecordMinLeftDB() []byte { + const nodeCount = 1 + const validRecord = nodeCount + dataSeparatorSize + return buildSeparatorRecordDB(nodeCount+1, validRecord) +} + +// buildSeparatorRecordMinRightDB creates an MMDB whose node 0 right record +// equals nodeCount + 1. The left record is valid, so this exercises the +// right-record-corruption path independently. +func buildSeparatorRecordMinRightDB() []byte { + const nodeCount = 1 + const validRecord = nodeCount + dataSeparatorSize + return buildSeparatorRecordDB(validRecord, nodeCount+1) +} + +// buildSeparatorRecordMaxLeftDB creates an MMDB whose node 0 left record +// equals nodeCount + 15 (the last byte of the data section separator), +// exercising the upper boundary of the invalid range. +func buildSeparatorRecordMaxLeftDB() []byte { + const nodeCount = 1 + const validRecord = nodeCount + dataSeparatorSize + return buildSeparatorRecordDB(nodeCount+dataSeparatorSize-1, validRecord) +} + // buildEmptyArrayLastInMetadataDB creates a valid MMDB where the metadata // map's last field is "languages" (an empty array []). Tests the array // validation path of the same off-by-one bug.