| index 1f2082dc0..61098f3ab 100644 |
| --- a/internal/console/geoippublish_test.go |
| +++ b/internal/console/geoippublish_test.go |
| @@ -515,3 +515,83 @@ func TestPublishSurvivesDatabasesThatHangOrPanicTheParser(t *testing.T) { |
| } |
| } |
| } |
| + |
| +// junkRecordMMDB writes an IPv4 database whose lower half answers GB and |
| +// whose upper half carries a 6M-element ignored array beside its country |
| +// (code review R11-1): generic verification walks the array, but the |
| +// production schema's skip recurses once per element. |
| +func junkRecordMMDB(t *testing.T) string { |
| + t.Helper() |
| + var buf bytes.Buffer |
| + str := func(s string) { buf.WriteByte(0x40 | byte(len(s))); buf.WriteString(s) } |
| + u16 := func(v uint16) { buf.WriteByte(0xA2); buf.WriteByte(byte(v >> 8)); buf.WriteByte(byte(v)) } |
| + // One node: left -> data at 0 (A), right -> data at offsetB (B). |
| + recA := []byte{0xE1, 0x47, 'c', 'o', 'u', 'n', 't', 'r', 'y', 0xE1, 0x48, 'i', 's', 'o', '_', 'c', 'o', 'd', 'e', 0x42, 'G', 'B'} |
| + offsetB := len(recA) |
| + ptr := func(dataOff int) []byte { v := 1 + 16 + dataOff; return []byte{byte(v >> 16), byte(v >> 8), byte(v)} } |
| + buf.Write(ptr(0)) |
| + buf.Write(ptr(offsetB)) |
| + buf.Write(make([]byte, 16)) |
| + buf.Write(recA) |
| + // B: {country: {iso_code: FR}, junk: [16,000,000 x false]} |
| + buf.Write([]byte{0xE2, 0x47, 'c', 'o', 'u', 'n', 't', 'r', 'y', 0xE1, 0x48, 'i', 's', 'o', '_', 'c', 'o', 'd', 'e', 0x42, 'F', 'R', 0x44, 'j', 'u', 'n', 'k'}) |
| + const n = 6_000_000 |
| + v := n - 65821 |
| + buf.Write([]byte{0x1F, 0x04, byte(v >> 16), byte(v >> 8), byte(v)}) // array, size 31 form |
| + falseBool := []byte{0x00, 0x07} |
| + for i := 0; i < n; i++ { |
| + buf.Write(falseBool) |
| + } |
| + buf.WriteString("\xAB\xCD\xEFMaxMind.com") |
| + buf.WriteByte(0xE0 | 9) |
| + str("node_count") |
| + buf.Write([]byte{0xC1, 1}) |
| + str("record_size") |
| + u16(24) |
| + str("ip_version") |
| + u16(4) |
| + str("database_type") |
| + str("Junk-Record") |
| + str("languages") |
| + buf.Write([]byte{0x01, 0x04}) |
| + str("en") |
| + str("binary_format_major_version") |
| + u16(2) |
| + str("binary_format_minor_version") |
| + u16(0) |
| + str("build_epoch") |
| + buf.Write([]byte{0x04, 0x02, 0x6a, 0xbc, 0x00, 0x00}) |
| + str("description") |
| + buf.WriteByte(0xE1) |
| + str("en") |
| + str("junk") |
| + p := filepath.Join(t.TempDir(), "junk.mmdb") |
| + if err := os.WriteFile(p, buf.Bytes(), 0o644); err != nil { |
| + t.Fatal(err) |
| + } |
| + return p |
| +} |
| + |
| +// Code review R11-1: a record that would overflow a serving lookup's stack |
| +// is refused by the verifier child, even when every probe address is fine. |
| +func TestPublishRefusesARecordThatWouldCrashALookup(t *testing.T) { |
| + prevProbes := geoipProbeAddrs |
| + geoipProbeAddrs = []string{"1.1.1.1", "8.8.8.8", "9.9.9.9"} // all in the clean half |
| + t.Cleanup(func() { geoipProbeAddrs = prevProbes }) |
| + prev := geoipVerifyTimeout |
| + geoipVerifyTimeout = 90 * time.Second |
| + t.Cleanup(func() { geoipVerifyTimeout = prev }) |
| + ctx := context.Background() |
| + p, _ := newTestPublisher(t) |
| + f, err := os.Open(junkRecordMMDB(t)) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + defer f.Close() |
| + if _, err := p.Publish(ctx, f, geoipPublishOpts{Origin: "manual"}); !errors.Is(err, errGeoipRefused) { |
| + t.Fatalf("a crash-on-lookup database = %v, want refused", err) |
| + } |
| + if _, _, present, _ := p.Current(ctx); present { |
| + t.Fatal("it was published") |
| + } |
| +} |