-
Notifications
You must be signed in to change notification settings - Fork 13
Bound decoder work to prevent a pointer fan-out DoS (STF-1488) #235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2,6 +2,20 @@ | |||||
|
|
||||||
| ## 1.5.0 | ||||||
|
|
||||||
| * Fixed a denial-of-service issue in the decoder. A crafted database could nest | ||||||
| data-section pointers to shared targets so that decoding one record cost | ||||||
| exponential time and memory from a small file. The decoder now limits the | ||||||
| number of values it decodes for a single record and rejects a database that | ||||||
| exceeds it, along with pointer cycles and over-deep data, with an | ||||||
| `InvalidDatabaseError`. See GHSA-hj94-g986-h9r7. | ||||||
| * Fixed a related payload-amplification denial of service. A crafted database | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Use a compound modifier for the issue type. Change Proposed fix-* Fixed a related payload-amplification denial of service. A crafted database
+* Fixed a related payload-amplification denial-of-service issue. A crafted database📝 Committable suggestion
Suggested change
🧰 Tools🪛 LanguageTool[grammar] ~11-~11: Use a hyphen to join words. (QB_NEW_EN_HYPHEN) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||
| could point many times at one large string or bytes value, so a record with | ||||||
| few values still materialized gigabytes. The decoder now also limits the | ||||||
| total string and bytes payload it decodes for a single record to 2 MiB, | ||||||
| charged as each value is decoded so a re-decoded target recharges, and | ||||||
| rejects an excess with the same `InvalidDatabaseError`. Both limits also | ||||||
| guard the metadata decoded when a database is opened. See | ||||||
| GHSA-hj94-g986-h9r7. | ||||||
| * Unnecessary files were removed from the published .gem. | ||||||
|
|
||||||
| ## 1.4.0 (2025-11-20) | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -241,6 +241,67 @@ def test_broken_database | |
| reader.close | ||
| end | ||
|
|
||
| def test_payload_amplification_is_bounded | ||
| # Each database resolves every lookup to a record that points many times at | ||
| # one large string or bytes value. Following each pointer would copy the | ||
| # target again, so a reader that materializes every occurrence produces far | ||
| # more data than the file holds. The decoder rejects each one instead. The | ||
| # -dos and -string fixtures stay under the value count and are stopped by | ||
| # the payload byte budget; the -worst-case fixture holds enough pointers | ||
| # that this reader's value accounting stops it first. Both outcomes reject | ||
| # the record rather than expand it. | ||
| names = %w[ | ||
| MaxMind-DB-test-payload-amplification-dos | ||
| MaxMind-DB-test-payload-amplification-dos-string | ||
| MaxMind-DB-test-payload-amplification-dos-worst-case | ||
| ] | ||
| modes = [MaxMind::DB::MODE_FILE, MaxMind::DB::MODE_MEMORY] | ||
| names.each do |name| | ||
| modes.each do |mode| | ||
| reader = MaxMind::DB.new("test/data/test-data/#{name}.mmdb", mode: mode) | ||
| assert_raises(MaxMind::DB::InvalidDatabaseError, "#{name} (#{mode})") do | ||
| reader.get('1.1.1.1') | ||
| end | ||
| reader.close | ||
| end | ||
| end | ||
| end | ||
|
|
||
| def test_payload_byte_budget_boundary | ||
| # The at-limit fixture materializes exactly 2 MiB of payload and must | ||
| # decode. The over-limit fixture holds one byte more and must be rejected, | ||
| # so an off-by-one in the byte budget is caught. | ||
| reader = MaxMind::DB.new( | ||
| 'test/data/test-data/MaxMind-DB-test-decoder-payload-limit.mmdb' | ||
| ) | ||
|
|
||
| refute_nil(reader.get('1.1.1.1')) | ||
| reader.close | ||
|
|
||
| reader = MaxMind::DB.new( | ||
| 'test/data/test-data/MaxMind-DB-test-decoder-payload-limit-over.mmdb' | ||
| ) | ||
| e = assert_raises MaxMind::DB::InvalidDatabaseError do | ||
| reader.get('1.1.1.1') | ||
| end | ||
| assert_equal( | ||
| 'The MaxMind DB file\'s data section exceeds the maximum number of bytes', | ||
| e.message, | ||
| ) | ||
| reader.close | ||
| end | ||
|
Comment on lines
+270
to
+292
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win Exercise these payload cases with
📍 Affects 1 file
🤖 Prompt for AI Agents |
||
|
|
||
| def test_metadata_payload_amplification_is_bounded | ||
| # The languages metadata array points many times at one large string. | ||
| # Opening the database decodes the metadata, so the same budget must reject | ||
| # it there rather than materialize the amplified payload. | ||
| assert_raises MaxMind::DB::InvalidDatabaseError do | ||
| MaxMind::DB.new( | ||
| 'test/data/test-data/MaxMind-DB-test-metadata-payload-limit.mmdb' | ||
| ) | ||
| end | ||
| end | ||
|
|
||
| def test_ip_validation | ||
| reader = MaxMind::DB.new( | ||
| 'test/data/test-data/MaxMind-DB-test-decoder.mmdb' | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.