diff --git a/CHANGELOG.md b/CHANGELOG.md index 29d88bf..3d8ffce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Fixed + +- Fix critical thread-safety issues in locking mechanism by replacing object_id-based lock storage with proper thread-local storage. This resolves object ID reuse vulnerabilities, race conditions, and memory leaks ([#226]). + +### Changed + - Run the test suite against Rails 8.1, 8.0, 7.2, and Ruby 4.0, 3.4, 3.3, 3.2 ([#225]). [Unreleased]: https://github.com/envato/double_entry/compare/v2.0.1...HEAD [#225]: https://github.com/envato/double_entry/pull/225 +[#226]: https://github.com/envato/double_entry/pull/226 ## [2.0.1] - 2023-11-01 diff --git a/lib/double_entry/locking.rb b/lib/double_entry/locking.rb index 4d6b3f2..00fc250 100644 --- a/lib/double_entry/locking.rb +++ b/lib/double_entry/locking.rb @@ -57,8 +57,6 @@ def self.balance_for_locked_account(account) end class Lock - @@locks = {} - def initialize(accounts) # Make sure we always lock in the same order, to avoid deadlocks. @accounts = accounts.flatten.sort @@ -97,15 +95,15 @@ def balance_for(account) private def locks - @@locks[Thread.current.object_id] + Thread.current[:double_entry_locks] end def locks=(locks) - @@locks[Thread.current.object_id] = locks + Thread.current[:double_entry_locks] = locks end def remove_locks - @@locks.delete(Thread.current.object_id) + Thread.current[:double_entry_locks] = nil end # Return true if there's a lock on the given account.