Action not permitted
Modal body text goes here.
Modal Title
Modal Body
Vulnerability from cleanstart
Package logstash-fips version 9.4.3-r1 fixes 76 vulnerabilities: CVE-2026-55831, CVE-2026-55833, CVE-2026-56745, CVE-2026-50020, CVE-2026-56746...
| URL | Type | |
|---|---|---|
{
"affected": [
{
"package": {
"ecosystem": "Alpine",
"name": "logstash-fips"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "9.4.3-r1"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"9.4.3-r1"
]
}
],
"credits": [],
"database_specific": {},
"details": "Package logstash-fips version 9.4.3-r1 fixes 76 vulnerabilities: CVE-2026-55831, CVE-2026-55833, CVE-2026-56745, CVE-2026-50020, CVE-2026-56746...",
"id": "CLEANSTART-2026-VR87787",
"modified": "2026-08-14T05:57:01Z",
"published": "2026-08-13T12:10:09Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/elastic/logstash"
}
],
"related": [],
"schema_version": "1.7.3",
"summary": "Security fixes in logstash-fips 9.4.3-r1",
"upstream": [
"CVE-2026-55831",
"CVE-2026-55833",
"CVE-2026-56745",
"CVE-2026-50020",
"CVE-2026-56746",
"CVE-2026-59898",
"CVE-2026-59899",
"CVE-2026-59921",
"ghsa-jppx-w49h-x2qq",
"ghsa-6jqx-86gh-f27w",
"ghsa-mvh2-crg5-v77c",
"ghsa-6cqp-g7gg-8hr5",
"ghsa-4mp9-239f-g9hg",
"ghsa-gcjf-9mgh-3p7g",
"ghsa-q4f6-jm68-57ww",
"ghsa-hvcg-qmg6-jm4c",
"CVE-2026-59901",
"ghsa-558v-64gr-wgg4",
"CVE-2026-54512",
"CVE-2026-54513",
"CVE-2026-54514",
"CVE-2026-54515",
"CVE-2026-59888",
"CVE-2026-54516",
"CVE-2026-54517",
"CVE-2026-54518",
"CVE-2026-59889",
"ghsa-5gvw-p9qm-jgwh",
"ghsa-5hh8-q8hv-fr38",
"ghsa-rcqc-6cw3-h962",
"ghsa-9fxm-vc8v-hj55",
"ghsa-mhm7-754m-9p8w",
"ghsa-j3rv-43j4-c7qm",
"ghsa-rmj7-2vxq-3g9f",
"ghsa-5jmj-h7xm-6q6v",
"ghsa-3pjw-73gf-8qr5",
"ghsa-hgj6-7826-r7m5",
"CVE-2025-14813",
"CVE-2026-0636",
"CVE-2026-5598",
"ghsa-574f-3g2m-x479",
"ghsa-p93r-85wp-75v3",
"ghsa-c3fc-8qff-9hwx",
"CVE-2026-5588",
"ghsa-wg6q-6289-32hp",
"ghsa-r7wm-3cxj-wff9",
"ghsa-72hv-8253-57qq",
"CVE-2026-59949",
"ghsa-xx22-p4ch-683r",
"CVE-2026-47240",
"CVE-2026-47242",
"CVE-2026-47241",
"ghsa-8p34-64r3-mwg8",
"ghsa-46q3-7gv7-qmgg",
"ghsa-c4fp-cxrr-mj66",
"CVE-2026-47736",
"CVE-2026-47737",
"ghsa-qpgp-93vx-g8v8",
"ghsa-2vqw-3mp8-cgmx",
"CVE-2026-54906",
"CVE-2026-54904",
"CVE-2026-54905",
"ghsa-h8w8-99g7-qmvj",
"ghsa-6wx8-w4f5-wwcr",
"ghsa-wv3x-4vxv-whpp",
"CVE-2026-54696",
"ghsa-x2f5-4prf-w687",
"ghsa-5prr-v3j2-97mh",
"ghsa-5v8h-3h3q-446p",
"ghsa-8678-w3jw-xfc2",
"ghsa-9cv2-cfxc-v4v2",
"ghsa-p67v-3w7g-wjg7",
"ghsa-phwj-rprq-35pp",
"ghsa-wfpw-mmfh-qq69",
"ghsa-wjv4-x9w8-wm3h",
"ghsa-g9g8-vgvw-g3vf"
]
}
GHSA-WFPW-MMFH-QQ69
Vulnerability from github – Published: 2026-06-19 16:37 – Updated: 2026-06-19 16:37Summary
XInclude substitution performed by Nokogiri::XML::Node#do_xinclude replaced each <xi:include> in place, freeing the include node along with its children (such as <xi:fallback> and its descendants) and any namespaces declared on them. If an application had already exposed one of those nodes or namespaces to Ruby, the corresponding Ruby object was left pointing at freed memory. Using the object could result in invalid reads or writes to memory.
Nokogiri 1.19.4 substitutes each <xi:include> on a defensive copy by default, so the structures libxml2 frees are never the ones bound to live Ruby objects.
Only the CRuby implementation is affected; JRuby is not affected.
Severity
The Nokogiri maintainers have evaluated this as low severity. Reaching it requires an unusual API-usage pattern that does not arise during normal use. The application must parse a document without XInclude, traverse into an <xi:include> subtree to expose its nodes or namespaces to Ruby, and only then invoke XInclude processing. The common case, requesting XInclude at parse time, operates on a freshly parsed document whose nodes are not yet exposed to Ruby and is not affected. Nokogiri 1.19.4 makes this pattern safe by default and requires no change to application code.
Mitigation
Upgrade to Nokogiri 1.19.4 or later.
As a workaround for earlier versions, perform XInclude substitution at parse time (with the xinclude parse option) rather than calling #do_xinclude on a document that has already been traversed. A freshly parsed document has no nodes exposed to Ruby, so the substitution is safe.
Credit
This issue was responsibly reported by Zheng Yu from depthfirst.com.
{
"affected": [
{
"package": {
"ecosystem": "RubyGems",
"name": "nokogiri"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.19.4"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-416"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-19T16:37:25Z",
"nvd_published_at": null,
"severity": "LOW"
},
"details": "### Summary\n\nXInclude substitution performed by `Nokogiri::XML::Node#do_xinclude` replaced each `\u003cxi:include\u003e` in place, freeing the include node along with its children (such as `\u003cxi:fallback\u003e` and its descendants) and any namespaces declared on them. If an application had already exposed one of those nodes or namespaces to Ruby, the corresponding Ruby object was left pointing at freed memory. Using the object could result in invalid reads or writes to memory.\n\nNokogiri 1.19.4 substitutes each `\u003cxi:include\u003e` on a defensive copy by default, so the structures libxml2 frees are never the ones bound to live Ruby objects.\n\nOnly the CRuby implementation is affected; JRuby is not affected.\n\n### Severity\n\nThe Nokogiri maintainers have evaluated this as low severity. Reaching it requires an unusual API-usage pattern that does not arise during normal use. The application must parse a document without XInclude, traverse into an `\u003cxi:include\u003e` subtree to expose its nodes or namespaces to Ruby, and only then invoke XInclude processing. The common case, requesting XInclude at parse time, operates on a freshly parsed document whose nodes are not yet exposed to Ruby and is not affected. Nokogiri 1.19.4 makes this pattern safe by default and requires no change to application code.\n\n### Mitigation\n\nUpgrade to Nokogiri 1.19.4 or later.\n\nAs a workaround for earlier versions, perform XInclude substitution at parse time (with the `xinclude` parse option) rather than calling `#do_xinclude` on a document that has already been traversed. A freshly parsed document has no nodes exposed to Ruby, so the substitution is safe.\n\n### Credit\n\nThis issue was responsibly reported by Zheng Yu from depthfirst.com.",
"id": "GHSA-wfpw-mmfh-qq69",
"modified": "2026-06-19T16:37:25Z",
"published": "2026-06-19T16:37:25Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-wfpw-mmfh-qq69"
},
{
"type": "PACKAGE",
"url": "https://github.com/sparklemotion/nokogiri"
}
],
"schema_version": "1.4.0",
"severity": [],
"summary": "Nokogiri: Possible Use-After-Free in XInclude Processing"
}
GHSA-WG6Q-6289-32HP
Vulnerability from github – Published: 2026-04-15 18:31 – Updated: 2026-04-16 21:32: Use of a Broken or Risky Cryptographic Algorithm vulnerability in Legion of the Bouncy Castle Inc. BC-JAVA bcpkix on all (pkix modules).
PKIX draft CompositeVerifier accepts empty signature sequence as valid.
This issue affects BC-JAVA: from 1.49 before 1.84.
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "org.bouncycastle:bcpkix-jdk18on"
},
"ranges": [
{
"events": [
{
"introduced": "1.49"
},
{
"fixed": "1.84"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "org.bouncycastle:bcpkix-jdk15to18"
},
"ranges": [
{
"events": [
{
"introduced": "1.49"
},
{
"fixed": "1.84"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "org.bouncycastle:bcpkix-jdk15on"
},
"ranges": [
{
"events": [
{
"introduced": "1.49"
},
{
"fixed": "1.84"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "org.bouncycastle:bcpkix-jdk14"
},
"ranges": [
{
"events": [
{
"introduced": "1.49"
},
{
"fixed": "1.84"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "org.bouncycastle:bcpkix-debug-jdk18on"
},
"ranges": [
{
"events": [
{
"introduced": "1.49"
},
{
"fixed": "1.84"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "org.bouncycastle:bcpkix-debug-jdk15to18"
},
"ranges": [
{
"events": [
{
"introduced": "1.49"
},
{
"fixed": "1.84"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "org.bouncycastle:bcpkix-debug-jdk14"
},
"ranges": [
{
"events": [
{
"introduced": "1.49"
},
{
"fixed": "1.84"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-5588"
],
"database_specific": {
"cwe_ids": [
"CWE-327"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-16T21:32:20Z",
"nvd_published_at": "2026-04-15T10:16:49Z",
"severity": "MODERATE"
},
"details": ": Use of a Broken or Risky Cryptographic Algorithm vulnerability in Legion of the Bouncy Castle Inc. BC-JAVA bcpkix on all (pkix modules).\n\n\nPKIX draft CompositeVerifier accepts empty signature sequence as valid.\n\n\nThis issue affects BC-JAVA: from 1.49 before 1.84.",
"id": "GHSA-wg6q-6289-32hp",
"modified": "2026-04-16T21:32:20Z",
"published": "2026-04-15T18:31:54Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-5588"
},
{
"type": "WEB",
"url": "https://github.com/bcgit/bc-java/commit/656bae0dbd9b1521f840521ff786e78749fe3057"
},
{
"type": "PACKAGE",
"url": "https://github.com/bcgit/bc-java"
},
{
"type": "WEB",
"url": "https://github.com/bcgit/bc-java/wiki/CVE%E2%80%902026%E2%80%905588"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/U:Green",
"type": "CVSS_V4"
}
],
"summary": "Bouncy Castle Crypto Package For Java: Use of a Broken or Risky Cryptographic Algorithm vulnerability in bcpkix modules"
}
GHSA-WJV4-X9W8-WM3H
Vulnerability from github – Published: 2026-06-19 16:36 – Updated: 2026-06-19 16:36Summary
Nokogiri::XML::Document#root= validated only that the new root was a Nokogiri::XML::Node, allowing a DTD node to be set as the document root. The result is a heap use-after-free during garbage collection or finalization, leading to an invalid memory read or potentially a segfault.
Nokogiri 1.19.4 restricts Document#root= to element nodes, raising TypeError for any other node type.
This memory-safety issue affects only the CRuby implementation (libxml2). The JRuby implementation was not affected; the same input validation was added there for behavioral parity.
Severity
The Nokogiri maintainers have evaluated this as low severity. This is only triggered by a programming error. It requires application code to assign a non-element node such as a DTD as the document root via Document#root=. Nokogiri 1.19.4 now raises TypeError instead of allowing a use-after-free. It cannot be triggered by untrusted input or through normal use of the public API.
Mitigation
Upgrade to Nokogiri 1.19.4 or later.
As a workaround, applications that cannot upgrade should avoid assigning a DTD (or any non-element node) via Document#root=.
Credit
This issue was responsibly reported by Zheng Yu from depthfirst.com.
{
"affected": [
{
"package": {
"ecosystem": "RubyGems",
"name": "nokogiri"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.19.4"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-416"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-19T16:36:59Z",
"nvd_published_at": null,
"severity": "LOW"
},
"details": "### Summary\n\n`Nokogiri::XML::Document#root=` validated only that the new root was a `Nokogiri::XML::Node`, allowing a DTD node to be set as the document root. The result is a heap use-after-free during garbage collection or finalization, leading to an invalid memory read or potentially a segfault.\n\nNokogiri 1.19.4 restricts `Document#root=` to element nodes, raising `TypeError` for any other node type.\n\nThis memory-safety issue affects only the CRuby implementation (libxml2). The JRuby implementation was not affected; the same input validation was added there for behavioral parity.\n\n### Severity\n\nThe Nokogiri maintainers have evaluated this as low severity. This is only triggered by a programming error. It requires application code to assign a non-element node such as a DTD as the document root via `Document#root=`. Nokogiri 1.19.4 now raises `TypeError` instead of allowing a use-after-free. It cannot be triggered by untrusted input or through normal use of the public API.\n\n### Mitigation\n\nUpgrade to Nokogiri 1.19.4 or later.\n\nAs a workaround, applications that cannot upgrade should avoid assigning a DTD (or any non-element node) via `Document#root=`.\n\n### Credit\n\nThis issue was responsibly reported by Zheng Yu from depthfirst.com.",
"id": "GHSA-wjv4-x9w8-wm3h",
"modified": "2026-06-19T16:36:59Z",
"published": "2026-06-19T16:36:59Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-wjv4-x9w8-wm3h"
},
{
"type": "PACKAGE",
"url": "https://github.com/sparklemotion/nokogiri"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:U",
"type": "CVSS_V4"
}
],
"summary": "Nokogiri: Possible Use-After-Free when setting `Document#root=` to an invalid node type"
}
GHSA-WV3X-4VXV-WHPP
Vulnerability from github – Published: 2026-06-19 20:47 – Updated: 2026-07-20 13:36Summary
Concurrent::ReentrantReadWriteLock can incorrectly grant a write lock after one thread acquires the read lock 32,768 times.
The lock stores a thread's local read and write hold counts in one integer. The low 15 bits are used for the read hold count, and bit 15 is used as WRITE_LOCK_HELD. After 32,768 reentrant read acquisitions, the local read count crosses into the write-lock bit. try_write_lock then treats the thread as already holding a write lock and returns true without setting the global RUNNING_WRITER bit.
This breaks the core mutual-exclusion guarantee: the caller is told it has a write lock, but other threads can still hold or acquire read locks at the same time.
Version
Software: concurrent-ruby Version: 1.3.6 Commit: 7a1b78941c081106c20a9ca0144ac73a48d254ab
Details
The implementation uses a shared counter to track global readers/writers and a per-thread local counter to support reentrancy:
READER_BITS = 15
WRITER_BITS = 14
WAITING_WRITER = 1 << READER_BITS
RUNNING_WRITER = 1 << (READER_BITS + WRITER_BITS)
MAX_READERS = WAITING_WRITER - 1
MAX_WRITERS = RUNNING_WRITER - MAX_READERS - 1
WRITE_LOCK_HELD = 1 << READER_BITS
READ_LOCK_MASK = WRITE_LOCK_HELD - 1
WRITE_LOCK_MASK = MAX_WRITERS
When a thread already holds a lock, acquire_read_lock increments @HeldCount:
if (held = @HeldCount.value) > 0
if held & READ_LOCK_MASK == 0
@Counter.update { |c| c + 1 }
end
@HeldCount.value = held + 1
return true
end
After 32,768 read acquisitions, the per-thread held count becomes 32768, which is equal to WRITE_LOCK_HELD. Then try_write_lock returns success through its "already have a write lock" branch:
def try_write_lock
if (held = @HeldCount.value) >= WRITE_LOCK_HELD
@HeldCount.value = held + WRITE_LOCK_HELD
return true
else
# normal global writer acquisition path
end
end
This branch does not set the global RUNNING_WRITER bit. Other threads therefore do not observe an active writer and can continue holding or acquiring read locks while the caller believes it owns the write lock.
PoC
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'concurrent/atomic/reentrant_read_write_lock'
require 'concurrent/version'
require 'thread'
def wait_for_queue(queue, timeout_seconds)
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout_seconds
loop do
return queue.pop(true)
rescue ThreadError
return nil if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
sleep 0.001
end
end
puts "ruby=#{RUBY_DESCRIPTION}"
puts "concurrent_ruby_version=#{Concurrent::VERSION}"
puts "poc=ReentrantReadWriteLock read-depth overflow grants write lock without exclusivity"
lock = Concurrent::ReentrantReadWriteLock.new
other_reader_ready = Queue.new
other_reader_stop = Queue.new
other_reader = Thread.new do
lock.acquire_read_lock
other_reader_ready << :held
other_reader_stop.pop
end
wait_for_queue(other_reader_ready, 1)
puts "other_thread_holds_read_lock=true"
depth = Concurrent::ReentrantReadWriteLock::WRITE_LOCK_HELD
depth.times { lock.acquire_read_lock }
held_count = lock.instance_eval { @HeldCount.value }
counter_before = lock.instance_eval { @Counter.value }
puts "main_thread_read_acquisitions=#{depth}"
puts "main_thread_held_count=#{held_count}"
puts "counter_before_try_write=#{counter_before}"
puts "running_writer_bit_before=#{(counter_before & Concurrent::ReentrantReadWriteLock::RUNNING_WRITER) != 0}"
write_granted = lock.try_write_lock
counter_after = lock.instance_eval { @Counter.value }
puts "try_write_lock_returned=#{write_granted}"
puts "counter_after_try_write=#{counter_after}"
puts "running_writer_bit_after=#{(counter_after & Concurrent::ReentrantReadWriteLock::RUNNING_WRITER) != 0}"
third_reader_ready = Queue.new
third_reader = Thread.new do
lock.acquire_read_lock
third_reader_ready << :acquired
end
third_reader_acquired = wait_for_queue(third_reader_ready, 0.25) == :acquired
puts "new_reader_acquired_while_write_claimed=#{third_reader_acquired}"
if write_granted && third_reader_acquired && (counter_after & Concurrent::ReentrantReadWriteLock::RUNNING_WRITER).zero?
puts 'result=REPRODUCED write lock granted without setting global writer state'
else
puts 'result=NOT_REPRODUCED'
end
third_reader.kill
other_reader_stop << :stop
other_reader.kill
Log evidence
ruby=ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin25]
concurrent_ruby_version=1.3.6
poc=ReentrantReadWriteLock read-depth overflow grants write lock without exclusivity
other_thread_holds_read_lock=true
main_thread_read_acquisitions=32768
main_thread_held_count=32768
counter_before_try_write=2
running_writer_bit_before=false
try_write_lock_returned=true
counter_after_try_write=2
running_writer_bit_after=false
new_reader_acquired_while_write_claimed=true
result=REPRODUCED write lock granted without setting global writer state
Impact
This breaks the write-lock exclusivity guarantee. After the overflow, a thread can be told it has acquired the write lock while other threads can still hold or acquire read locks, allowing races and inconsistent reads of protected mutable state.
Credit
Pranjali Thakur - depthfirst (depthfirst.com)
{
"affected": [
{
"package": {
"ecosystem": "RubyGems",
"name": "concurrent-ruby"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.3.7"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-54905"
],
"database_specific": {
"cwe_ids": [
"CWE-128"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-19T20:47:38Z",
"nvd_published_at": "2026-06-24T17:17:29Z",
"severity": "LOW"
},
"details": "### Summary\n`Concurrent::ReentrantReadWriteLock` can incorrectly grant a write lock after one thread acquires the read lock 32,768 times.\n\nThe lock stores a thread\u0027s local read and write hold counts in one integer. The low 15 bits are used for the read hold count, and bit 15 is used as `WRITE_LOCK_HELD`. After 32,768 reentrant read acquisitions, the local read count crosses into the write-lock bit. `try_write_lock` then treats the thread as already holding a write lock and returns `true` without setting the global `RUNNING_WRITER` bit.\n\nThis breaks the core mutual-exclusion guarantee: the caller is told it has a write lock, but other threads can still hold or acquire read locks at the same time.\n\n### Version\nSoftware: concurrent-ruby\nVersion: 1.3.6\nCommit: 7a1b78941c081106c20a9ca0144ac73a48d254ab\n\n### Details\n\nThe implementation uses a shared counter to track global readers/writers and a per-thread local counter to support reentrancy:\n\n```ruby\nREADER_BITS = 15\nWRITER_BITS = 14\n\nWAITING_WRITER = 1 \u003c\u003c READER_BITS\nRUNNING_WRITER = 1 \u003c\u003c (READER_BITS + WRITER_BITS)\nMAX_READERS = WAITING_WRITER - 1\nMAX_WRITERS = RUNNING_WRITER - MAX_READERS - 1\n\nWRITE_LOCK_HELD = 1 \u003c\u003c READER_BITS\nREAD_LOCK_MASK = WRITE_LOCK_HELD - 1\nWRITE_LOCK_MASK = MAX_WRITERS\n```\n\nWhen a thread already holds a lock, `acquire_read_lock` increments `@HeldCount`:\n\n```ruby\nif (held = @HeldCount.value) \u003e 0\n if held \u0026 READ_LOCK_MASK == 0\n @Counter.update { |c| c + 1 }\n end\n @HeldCount.value = held + 1\n return true\nend\n```\n\nAfter 32,768 read acquisitions, the per-thread held count becomes `32768`, which is equal to `WRITE_LOCK_HELD`. Then `try_write_lock` returns success through its \"already have a write lock\" branch:\n\n```ruby\ndef try_write_lock\n if (held = @HeldCount.value) \u003e= WRITE_LOCK_HELD\n @HeldCount.value = held + WRITE_LOCK_HELD\n return true\n else\n # normal global writer acquisition path\n end\nend\n```\n\nThis branch does not set the global `RUNNING_WRITER` bit. Other threads therefore do not observe an active writer and can continue holding or acquiring read locks while the caller believes it owns the write lock.\n\n### PoC\n\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nrequire \u0027concurrent/atomic/reentrant_read_write_lock\u0027\nrequire \u0027concurrent/version\u0027\nrequire \u0027thread\u0027\n\ndef wait_for_queue(queue, timeout_seconds)\n deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout_seconds\n loop do\n return queue.pop(true)\n rescue ThreadError\n return nil if Process.clock_gettime(Process::CLOCK_MONOTONIC) \u003e= deadline\n\n sleep 0.001\n end\nend\n\nputs \"ruby=#{RUBY_DESCRIPTION}\"\nputs \"concurrent_ruby_version=#{Concurrent::VERSION}\"\nputs \"poc=ReentrantReadWriteLock read-depth overflow grants write lock without exclusivity\"\n\nlock = Concurrent::ReentrantReadWriteLock.new\nother_reader_ready = Queue.new\nother_reader_stop = Queue.new\n\nother_reader = Thread.new do\n lock.acquire_read_lock\n other_reader_ready \u003c\u003c :held\n other_reader_stop.pop\nend\n\nwait_for_queue(other_reader_ready, 1)\nputs \"other_thread_holds_read_lock=true\"\n\ndepth = Concurrent::ReentrantReadWriteLock::WRITE_LOCK_HELD\ndepth.times { lock.acquire_read_lock }\n\nheld_count = lock.instance_eval { @HeldCount.value }\ncounter_before = lock.instance_eval { @Counter.value }\n\nputs \"main_thread_read_acquisitions=#{depth}\"\nputs \"main_thread_held_count=#{held_count}\"\nputs \"counter_before_try_write=#{counter_before}\"\nputs \"running_writer_bit_before=#{(counter_before \u0026 Concurrent::ReentrantReadWriteLock::RUNNING_WRITER) != 0}\"\n\nwrite_granted = lock.try_write_lock\ncounter_after = lock.instance_eval { @Counter.value }\n\nputs \"try_write_lock_returned=#{write_granted}\"\nputs \"counter_after_try_write=#{counter_after}\"\nputs \"running_writer_bit_after=#{(counter_after \u0026 Concurrent::ReentrantReadWriteLock::RUNNING_WRITER) != 0}\"\n\nthird_reader_ready = Queue.new\nthird_reader = Thread.new do\n lock.acquire_read_lock\n third_reader_ready \u003c\u003c :acquired\nend\n\nthird_reader_acquired = wait_for_queue(third_reader_ready, 0.25) == :acquired\nputs \"new_reader_acquired_while_write_claimed=#{third_reader_acquired}\"\n\nif write_granted \u0026\u0026 third_reader_acquired \u0026\u0026 (counter_after \u0026 Concurrent::ReentrantReadWriteLock::RUNNING_WRITER).zero?\n puts \u0027result=REPRODUCED write lock granted without setting global writer state\u0027\nelse\n puts \u0027result=NOT_REPRODUCED\u0027\nend\n\nthird_reader.kill\nother_reader_stop \u003c\u003c :stop\nother_reader.kill\n```\n\n### Log evidence\n```text\nruby=ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin25]\nconcurrent_ruby_version=1.3.6\npoc=ReentrantReadWriteLock read-depth overflow grants write lock without exclusivity\nother_thread_holds_read_lock=true\nmain_thread_read_acquisitions=32768\nmain_thread_held_count=32768\ncounter_before_try_write=2\nrunning_writer_bit_before=false\ntry_write_lock_returned=true\ncounter_after_try_write=2\nrunning_writer_bit_after=false\nnew_reader_acquired_while_write_claimed=true\nresult=REPRODUCED write lock granted without setting global writer state\n```\n\n### Impact\nThis breaks the write-lock exclusivity guarantee. After the overflow, a thread can be told it has acquired the write lock while other threads can still hold or acquire read locks, allowing races and inconsistent reads of protected mutable state.\n\n### Credit\nPranjali Thakur - depthfirst ([depthfirst.com](\u003chttp://depthfirst.com\u003e))",
"id": "GHSA-wv3x-4vxv-whpp",
"modified": "2026-07-20T13:36:59Z",
"published": "2026-06-19T20:47:38Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/ruby-concurrency/concurrent-ruby/security/advisories/GHSA-wv3x-4vxv-whpp"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-54905"
},
{
"type": "PACKAGE",
"url": "https://github.com/ruby-concurrency/concurrent-ruby"
},
{
"type": "WEB",
"url": "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/concurrent-ruby/CVE-2026-54905.yml"
},
{
"type": "WEB",
"url": "https://www.cve.org/CVERecord/SearchResults?query=CVE-2026-54905"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Concurrent Ruby: `ReentrantReadWriteLock` read-count overflow grants a write lock without exclusivity"
}
GHSA-X2F5-4PRF-W687
Vulnerability from github – Published: 2026-07-23 19:48 – Updated: 2026-07-23 19:48Summary
JSON.dump(obj, io) and JSON::State#generate(obj, io) can write past the
internal JSON generator buffer when a streamed object contains an
attacker-controlled string near 16 KB. The issue is a heap out-of-bounds write
in the IO-streaming path and is demonstrated as a reliable process crash /
denial of service.
This was triaged on HackerOne as report #3785370. The issue was confirmed there and I was asked to open it here.
Details
Root cause is in ext/json/fbuffer/fbuffer.h, fbuffer_do_inc_capa().
On the IO path, the buffer is grown to FBUFFER_IO_BUFFER_SIZE (16383), but the
early return checks total capacity instead of remaining capacity:
if (RB_UNLIKELY(fb->io)) {
if (fb->capa < FBUFFER_IO_BUFFER_SIZE) {
fbuffer_realloc(fb, FBUFFER_IO_BUFFER_SIZE);
} else {
fbuffer_flush(fb);
}
if (RB_LIKELY(requested < fb->capa)) {
return;
}
}
If fb->len already contains JSON syntax bytes, and a string flush has
16383 - fb->len <= requested < 16383, this check returns even though there is
not enough space left. fbuffer_append_reserved() then writes past the buffer:
MEMCPY(fb->ptr + fb->len, newstr, char, len);
The minimal fix is to compare against the remaining capacity:
- if (RB_LIKELY(requested < fb->capa)) {
+ if (RB_LIKELY(requested <= fb->capa - fb->len)) {
return;
}
PoC
require "json"
require "stringio"
io = StringIO.new
big = "a" * 16385
big[16382] = '"' # escapable byte near the buffer boundary
JSON.dump([big], io)
Verified results:
Ruby 4.0.5 / bundled json 2.18.0:
malloc(): invalid size (unsorted)
.../json/common.rb:956: [BUG] Aborted
ruby/ruby master c78418b7a0 / json 2.19.8 / ASan:
heap-buffer-overflow WRITE of size 16382
fbuffer_append_reserved ext/json/fbuffer/fbuffer.h:145
search_flush ext/json/generator/generator.c:139
convert_UTF8_to_JSON ext/json/generator/generator.c:231
raw_generate_json_string ext/json/generator/generator.c:922
cState_m_generate ext/json/generator/generator.c:1891
Control: the same data through JSON.dump([big]) without an IO argument returns
normally. The bug is specific to the IO-streaming path.
Impact
A remote attacker can trigger a heap out-of-bounds write if they control a
string field that an application serializes through JSON.dump(obj, io) or
JSON::State#generate(obj, io). The demonstrated impact is reliable denial of
service. I am not claiming code execution or information disclosure.
{
"affected": [
{
"package": {
"ecosystem": "RubyGems",
"name": "json"
},
"ranges": [
{
"events": [
{
"introduced": "2.9.0"
},
{
"fixed": "2.19.9"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-54696"
],
"database_specific": {
"cwe_ids": [
"CWE-122",
"CWE-131",
"CWE-787"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-23T19:48:08Z",
"nvd_published_at": "2026-06-30T23:17:28Z",
"severity": "LOW"
},
"details": "### Summary\n\n`JSON.dump(obj, io)` and `JSON::State#generate(obj, io)` can write past the\ninternal JSON generator buffer when a streamed object contains an\nattacker-controlled string near 16 KB. The issue is a heap out-of-bounds write\nin the IO-streaming path and is demonstrated as a reliable process crash /\ndenial of service.\n\nThis was triaged on HackerOne as report #3785370. The issue was confirmed there\nand I was asked to open it here.\n\n### Details\n\nRoot cause is in `ext/json/fbuffer/fbuffer.h`, `fbuffer_do_inc_capa()`.\n\nOn the IO path, the buffer is grown to `FBUFFER_IO_BUFFER_SIZE` (16383), but the\nearly return checks total capacity instead of remaining capacity:\n\n```c\nif (RB_UNLIKELY(fb-\u003eio)) {\n if (fb-\u003ecapa \u003c FBUFFER_IO_BUFFER_SIZE) {\n fbuffer_realloc(fb, FBUFFER_IO_BUFFER_SIZE);\n } else {\n fbuffer_flush(fb);\n }\n\n if (RB_LIKELY(requested \u003c fb-\u003ecapa)) {\n return;\n }\n}\n```\n\nIf `fb-\u003elen` already contains JSON syntax bytes, and a string flush has\n`16383 - fb-\u003elen \u003c= requested \u003c 16383`, this check returns even though there is\nnot enough space left. `fbuffer_append_reserved()` then writes past the buffer:\n\n```c\nMEMCPY(fb-\u003eptr + fb-\u003elen, newstr, char, len);\n```\n\nThe minimal fix is to compare against the remaining capacity:\n\n```diff\n- if (RB_LIKELY(requested \u003c fb-\u003ecapa)) {\n+ if (RB_LIKELY(requested \u003c= fb-\u003ecapa - fb-\u003elen)) {\n return;\n }\n```\n\n### PoC\n\n```ruby\nrequire \"json\"\nrequire \"stringio\"\n\nio = StringIO.new\nbig = \"a\" * 16385\nbig[16382] = \u0027\"\u0027 # escapable byte near the buffer boundary\n\nJSON.dump([big], io)\n```\n\nVerified results:\n\n```text\nRuby 4.0.5 / bundled json 2.18.0:\nmalloc(): invalid size (unsorted)\n.../json/common.rb:956: [BUG] Aborted\n\nruby/ruby master c78418b7a0 / json 2.19.8 / ASan:\nheap-buffer-overflow WRITE of size 16382\n fbuffer_append_reserved ext/json/fbuffer/fbuffer.h:145\n search_flush ext/json/generator/generator.c:139\n convert_UTF8_to_JSON ext/json/generator/generator.c:231\n raw_generate_json_string ext/json/generator/generator.c:922\n cState_m_generate ext/json/generator/generator.c:1891\n```\n\nControl: the same data through `JSON.dump([big])` without an IO argument returns\nnormally. The bug is specific to the IO-streaming path.\n\n### Impact\n\nA remote attacker can trigger a heap out-of-bounds write if they control a\nstring field that an application serializes through `JSON.dump(obj, io)` or\n`JSON::State#generate(obj, io)`. The demonstrated impact is reliable denial of\nservice. I am not claiming code execution or information disclosure.",
"id": "GHSA-x2f5-4prf-w687",
"modified": "2026-07-23T19:48:08Z",
"published": "2026-07-23T19:48:08Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/ruby/json/security/advisories/GHSA-x2f5-4prf-w687"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-54696"
},
{
"type": "WEB",
"url": "https://github.com/ruby/json/commit/996bac686d64e4e3aaeae03b14a7f9ee9695ebdb"
},
{
"type": "PACKAGE",
"url": "https://github.com/ruby/json"
},
{
"type": "WEB",
"url": "https://github.com/ruby/json/releases/tag/v2.19.9"
},
{
"type": "WEB",
"url": "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/json/CVE-2026-54696.yml"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L",
"type": "CVSS_V3"
}
],
"summary": "Ruby json: JSON generator heap buffer overflow when streaming to an IO"
}
GHSA-XX22-P4CH-683R
Vulnerability from github – Published: 2026-07-24 16:13 – Updated: 2026-07-24 16:13Summary
Insufficient validation of byte array arguments in JNI-based XXHash implementations in lz4-java 1.11.0 and earlier allows callers to crash the JVM by passing an invalid array reference or invalid range to native XXHash methods.
This affects applications where an attacker can influence the byte array object or the off / len arguments passed to affected XXHash APIs. It does not affect the common case where only the contents of a valid byte array are attacker-controlled.
Java-based XXHash implementations are not affected.
Details
The JNI-backed XXHash implementations pass caller-provided byte array arguments to native code. The affected APIs are:
XXHashFactory.nativeInstance().hash32().hash(byte[] buf, int off, int len, int seed)XXHashFactory.nativeInstance().hash64().hash(byte[] buf, int off, int len, long seed)XXHashFactory.nativeInstance().newStreamingHash32(seed).update(byte[] bytes, int off, int len)XXHashFactory.nativeInstance().newStreamingHash64(seed).update(byte[] bytes, int off, int len)
Before the fix, the streaming JNI implementations did not validate bytes, off, or len before calling XXHashJNI.XXH32_update / XXHashJNI.XXH64_update. The non-streaming JNI implementations called SafeUtils.checkRange, but SafeUtils.checkRange(byte[], int, int) skipped all array access when len == 0, so a null byte array with a zero length could still reach JNI.
As a result:
hash(null, 0, 0, seed)andupdate(null, 0, 0)could pass a null array reference to JNI, causing a fatal JVM crash inGetPrimitiveArrayCritical.update(new byte[16], 0, Integer.MAX_VALUE)could cause native XXHash code to read far beyond the end of the Java array, causing a fatal JVM crash and potentially exposing in-process memory to the native routine before the crash.
The oversized-length non-streaming hash(new byte[16], 0, Integer.MAX_VALUE, seed) case was already rejected in Java before this fix. The missing validation affected the streaming oversized-length case and the zero-length null-array case for both streaming and non-streaming JNI XXHash APIs.
The impact of this vulnerability depends on how user code uses the XXHash API. Code that hashes attacker-controlled byte contents in a valid, correctly bounded array is not affected. Code may be affected if an attacker can cause the application to pass a null array, an attacker-controlled offset, or an attacker-controlled length to the native XXHash API. The primary impact is denial of service due to JVM termination. For oversized lengths, native code may also read outside the Java array before the process crashes.
Mitigation
lz4-java 1.11.1 fixes this issue without requiring changes in user code.
If you cannot upgrade, avoid passing attacker-controlled array references, offsets, or lengths to JNI-backed XXHash APIs. In particular, validate that arrays are non-null and that off and len describe a range fully contained in the array before calling native XXHash methods.
Using XXHashFactory.safeInstance() avoids the JNI boundary and is not affected by this native crash behavior.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.11.0"
},
"package": {
"ecosystem": "Maven",
"name": "at.yawk.lz4:lz4-java"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.11.1"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "org.lz4:lz4-java"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "1.8.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-59949"
],
"database_specific": {
"cwe_ids": [
"CWE-125",
"CWE-476"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-24T16:13:41Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\n\nInsufficient validation of byte array arguments in JNI-based XXHash implementations in lz4-java 1.11.0 and earlier allows callers to crash the JVM by passing an invalid array reference or invalid range to native XXHash methods.\n\nThis affects applications where an attacker can influence the byte array object or the `off` / `len` arguments passed to affected XXHash APIs. It does **not** affect the common case where only the contents of a valid byte array are attacker-controlled.\n\nJava-based XXHash implementations are *not* affected.\n\n### Details\n\nThe JNI-backed XXHash implementations pass caller-provided byte array arguments to native code. The affected APIs are:\n\n- `XXHashFactory.nativeInstance().hash32().hash(byte[] buf, int off, int len, int seed)`\n- `XXHashFactory.nativeInstance().hash64().hash(byte[] buf, int off, int len, long seed)`\n- `XXHashFactory.nativeInstance().newStreamingHash32(seed).update(byte[] bytes, int off, int len)`\n- `XXHashFactory.nativeInstance().newStreamingHash64(seed).update(byte[] bytes, int off, int len)`\n\nBefore the fix, the streaming JNI implementations did not validate `bytes`, `off`, or `len` before calling `XXHashJNI.XXH32_update` / `XXHashJNI.XXH64_update`. The non-streaming JNI implementations called `SafeUtils.checkRange`, but `SafeUtils.checkRange(byte[], int, int)` skipped all array access when `len == 0`, so a null byte array with a zero length could still reach JNI.\n\nAs a result:\n\n- `hash(null, 0, 0, seed)` and `update(null, 0, 0)` could pass a null array reference to JNI, causing a fatal JVM crash in `GetPrimitiveArrayCritical`.\n- `update(new byte[16], 0, Integer.MAX_VALUE)` could cause native XXHash code to read far beyond the end of the Java array, causing a fatal JVM crash and potentially exposing in-process memory to the native routine before the crash.\n\nThe oversized-length non-streaming `hash(new byte[16], 0, Integer.MAX_VALUE, seed)` case was already rejected in Java before this fix. The missing validation affected the streaming oversized-length case and the zero-length null-array case for both streaming and non-streaming JNI XXHash APIs.\n\nThe impact of this vulnerability depends on how user code uses the XXHash API. Code that hashes attacker-controlled byte contents in a valid, correctly bounded array is not affected. Code may be affected if an attacker can cause the application to pass a null array, an attacker-controlled offset, or an attacker-controlled length to the native XXHash API. The primary impact is denial of service due to JVM termination. For oversized lengths, native code may also read outside the Java array before the process crashes.\n\n### Mitigation\n\nlz4-java 1.11.1 fixes this issue without requiring changes in user code.\n\nIf you cannot upgrade, avoid passing attacker-controlled array references, offsets, or lengths to JNI-backed XXHash APIs. In particular, validate that arrays are non-null and that `off` and `len` describe a range fully contained in the array before calling native XXHash methods.\n\nUsing `XXHashFactory.safeInstance()` avoids the JNI boundary and is not affected by this native crash behavior.",
"id": "GHSA-xx22-p4ch-683r",
"modified": "2026-07-24T16:13:41Z",
"published": "2026-07-24T16:13:41Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/yawkat/lz4-java/security/advisories/GHSA-xx22-p4ch-683r"
},
{
"type": "WEB",
"url": "https://github.com/yawkat/lz4-java/commit/dbd86d04b8dd716e1c2bc626be54189997d910da"
},
{
"type": "PACKAGE",
"url": "https://github.com/yawkat/lz4-java"
},
{
"type": "WEB",
"url": "https://github.com/yawkat/lz4-java/releases/tag/v1.11.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "LZ4 Java: Native XXHash implementations can crash the JVM when passed invalid byte array ranges"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.