@@ -208,6 +208,140 @@ func buildUint64MaxEpochDB() []byte {
208208 return buf [:pos ]
209209}
210210
211+ // writeMetadataBlockEmptyMapLast writes a metadata block where the last field
212+ // is "description" (an empty map). This triggers the off-by-one bug where
213+ // offset_to_next == data_section_size for a 0-length container.
214+ func writeMetadataBlockEmptyMapLast (buf []byte , nodeCount uint32 , buildEpoch uint64 ) int {
215+ pos := 0
216+
217+ copy (buf [pos :], metadataMarker )
218+ pos += len (metadataMarker )
219+
220+ pos += writeMap (buf [pos :], 9 )
221+
222+ pos += writeMetaKey (buf [pos :], "binary_format_major_version" )
223+ pos += writeUint16 (buf [pos :], 2 )
224+
225+ pos += writeMetaKey (buf [pos :], "binary_format_minor_version" )
226+ pos += writeUint16 (buf [pos :], 0 )
227+
228+ pos += writeMetaKey (buf [pos :], "build_epoch" )
229+ pos += writeUint64 (buf [pos :], buildEpoch )
230+
231+ pos += writeMetaKey (buf [pos :], "database_type" )
232+ pos += writeString (buf [pos :], "Test" )
233+
234+ pos += writeMetaKey (buf [pos :], "ip_version" )
235+ pos += writeUint16 (buf [pos :], 4 )
236+
237+ pos += writeMetaKey (buf [pos :], "languages" )
238+ pos += writeEmptyArray (buf [pos :])
239+
240+ pos += writeMetaKey (buf [pos :], "node_count" )
241+ pos += writeUint32 (buf [pos :], nodeCount )
242+
243+ pos += writeMetaKey (buf [pos :], "record_size" )
244+ pos += writeUint16 (buf [pos :], 24 )
245+
246+ // description last — empty map at the very end of the data section
247+ pos += writeMetaKey (buf [pos :], "description" )
248+ pos += writeMap (buf [pos :], 0 )
249+
250+ return pos
251+ }
252+
253+ // writeMetadataBlockEmptyArrayLast writes a metadata block where the last
254+ // field is "languages" (an empty array).
255+ func writeMetadataBlockEmptyArrayLast (buf []byte , nodeCount uint32 , buildEpoch uint64 ) int {
256+ pos := 0
257+
258+ copy (buf [pos :], metadataMarker )
259+ pos += len (metadataMarker )
260+
261+ pos += writeMap (buf [pos :], 9 )
262+
263+ pos += writeMetaKey (buf [pos :], "binary_format_major_version" )
264+ pos += writeUint16 (buf [pos :], 2 )
265+
266+ pos += writeMetaKey (buf [pos :], "binary_format_minor_version" )
267+ pos += writeUint16 (buf [pos :], 0 )
268+
269+ pos += writeMetaKey (buf [pos :], "build_epoch" )
270+ pos += writeUint64 (buf [pos :], buildEpoch )
271+
272+ pos += writeMetaKey (buf [pos :], "database_type" )
273+ pos += writeString (buf [pos :], "Test" )
274+
275+ pos += writeMetaKey (buf [pos :], "description" )
276+ pos += writeMap (buf [pos :], 0 )
277+
278+ pos += writeMetaKey (buf [pos :], "ip_version" )
279+ pos += writeUint16 (buf [pos :], 4 )
280+
281+ pos += writeMetaKey (buf [pos :], "node_count" )
282+ pos += writeUint32 (buf [pos :], nodeCount )
283+
284+ pos += writeMetaKey (buf [pos :], "record_size" )
285+ pos += writeUint16 (buf [pos :], 24 )
286+
287+ // languages last — empty array at the very end of the data section
288+ pos += writeMetaKey (buf [pos :], "languages" )
289+ pos += writeEmptyArray (buf [pos :])
290+
291+ return pos
292+ }
293+
294+ // buildEmptyMapLastInMetadataDB creates a valid MMDB where the metadata
295+ // map's last field is "description" (an empty map {}). This reproduces the
296+ // off-by-one bug in get_entry_data_list() where offset == data_section_size
297+ // is incorrectly rejected for 0-length containers.
298+ func buildEmptyMapLastInMetadataDB () []byte {
299+ const nodeCount = 1
300+ const recordValue = nodeCount + 16
301+
302+ buf := make ([]byte , 1024 )
303+ pos := 0
304+
305+ pos += writeSearchTree (buf [pos :], recordValue )
306+
307+ // 16-byte null separator
308+ pos += dataSeparatorSize
309+
310+ // Data: a simple map with one string entry
311+ pos += writeMap (buf [pos :], 1 )
312+ pos += writeString (buf [pos :], "ip" )
313+ pos += writeString (buf [pos :], "test" )
314+
315+ pos += writeMetadataBlockEmptyMapLast (buf [pos :], nodeCount , 1_000_000_000 )
316+
317+ return buf [:pos ]
318+ }
319+
320+ // buildEmptyArrayLastInMetadataDB creates a valid MMDB where the metadata
321+ // map's last field is "languages" (an empty array []). Tests the array
322+ // validation path of the same off-by-one bug.
323+ func buildEmptyArrayLastInMetadataDB () []byte {
324+ const nodeCount = 1
325+ const recordValue = nodeCount + 16
326+
327+ buf := make ([]byte , 1024 )
328+ pos := 0
329+
330+ pos += writeSearchTree (buf [pos :], recordValue )
331+
332+ // 16-byte null separator
333+ pos += dataSeparatorSize
334+
335+ // Data: a simple map with one string entry
336+ pos += writeMap (buf [pos :], 1 )
337+ pos += writeString (buf [pos :], "ip" )
338+ pos += writeString (buf [pos :], "test" )
339+
340+ pos += writeMetadataBlockEmptyArrayLast (buf [pos :], nodeCount , 1_000_000_000 )
341+
342+ return buf [:pos ]
343+ }
344+
211345// buildCorruptSearchTreeDB creates a complete MMDB where the metadata claims
212346// node_count = 100 but the actual search tree has only 1 node worth of real
213347// data (6 bytes for 24-bit records). The file is padded so MMDB_open
0 commit comments