CVE-2018-3759: Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition') (CWE-362)
value: private_address_check ruby gem before 0.5.0 is vulnerable to a time-of-check time-of-use (TOCTOU) race condition due to the address the socket uses not being checked. DNS entries with a TTL of 0 can trigger this case where the initial resolution is a public address but the subsequent resolution is a private address.
CWE description: The product contains a code sequence that can run concurrently with other code, and the code sequence requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence that is operating concurrently.
CVSS v3 base score: 3.7
Published: 2018-06-13T15:29Z
Repository: https://github.com/jtdowney/private_address_check
Language: Ruby
Files changed: README.md, lib/private_address_check/tcpsocket_ext.rb, test/private_address_check/tcpsocket_ext_test.rb
Commit date: 2018-05-03 20:05:19 -0500
Commit message: Fix TOCTOU bug

This fixes the time of check to time of use (TOCTOU) bug reported by Sajjad
Hashemian.

Diff: diff --git a/README.md b/README.md
index 8120d4c..4b77c91 100644
--- a/README.md
+++ b/README.md
@@ -59,10 +59,20 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
 
 ## Contributing
 
 Bug reports and pull requests are welcome on GitHub at https://github.com/jtdowney/private_address_check. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
 
+## Security
+
+If you've found a security issue in `private_address_check`, please reach out to @jtdowney via email to report.
+
+### Time of check to time of use
+
+A library like `private_address_check` is going to be easily susceptible to attacks like [time of check to time of use](https://en.wikipedia.org/wiki/Time_of_check_to_time_of_use). DNS entries with a TTL of 0 can trigger this case where the initial resolution is a public address by the subsequent resolution is a private address. There are some possible defenses and workarounds:
+
+- Use the TCPSocket extension in this library which checks the address the socket uses. This is most useful if your system is built on native Ruby like Net::HTTP.
+- Use a feature like the `resolve` capability in curl and [curb](https://www.rubydoc.info/github/taf2/curb/Curl/Easy#resolve=-instance_method) to force the resolution to a pre-checked IP address.
+- Implement your own caching DNS resolver with something like dnsmasq or unbound. These tools let you set a minimum cache time that can override the TTL of 0.
 
 ## License
 
 The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
-
diff --git a/lib/private_address_check/tcpsocket_ext.rb b/lib/private_address_check/tcpsocket_ext.rb
index 1ec54c0..83d018a 100644
--- a/lib/private_address_check/tcpsocket_ext.rb
+++ b/lib/private_address_check/tcpsocket_ext.rb
@@ -12,13 +12,12 @@ module PrivateAddressCheck
 end
 
 TCPSocket.class_eval do
   alias initialize_without_private_address_check initialize
 
-  def initialize(remote_host, remote_port, local_host = nil, local_port = nil)
-    if Thread.current[:private_address_check] && PrivateAddressCheck.resolves_to_private_address?(remote_host)
+  def initialize(*args)
+    initialize_without_private_address_check(*args)
+    if Thread.current[:private_address_check] && PrivateAddressCheck.resolves_to_private_address?(remote_address.ip_address)
       raise PrivateAddressCheck::PrivateConnectionAttemptedError
     end
-
-    initialize_without_private_address_check(remote_host, remote_port, local_host, local_port)
   end
 end
diff --git a/test/private_address_check/tcpsocket_ext_test.rb b/test/private_address_check/tcpsocket_ext_test.rb
index 7064bdf..6b0f91d 100644
--- a/test/private_address_check/tcpsocket_ext_test.rb
+++ b/test/private_address_check/tcpsocket_ext_test.rb
@@ -1,15 +1,19 @@
 require 'test_helper'
 require 'private_address_check/tcpsocket_ext'
 
 class TCPSocketExtTest < Minitest::Test
   def test_private_address
+    server = TCPServer.new(63453)
+    thread = Thread.start { server.accept }
     assert_raises PrivateAddressCheck::PrivateConnectionAttemptedError do
       PrivateAddressCheck.only_public_connections do
-        TCPSocket.new("localhost", 80)
+        TCPSocket.new("localhost", 63453)
       end
     end
+  ensure
+    thread.exit if thread
   end
 
   def test_public_address
     connected = false
     PrivateAddressCheck.only_public_connections do

Vulnerable code: 
  def initialize(remote_host, remote_port, local_host = nil, local_port = nil)
    if Thread.current[:private_address_check] && PrivateAddressCheck.resolves_to_private_address?(remote_host)

    initialize_without_private_address_check(remote_host, remote_port, local_host, local_port)
        TCPSocket.new("localhost", 80)
Fixed code: ## Security

If you've found a security issue in `private_address_check`, please reach out to @jtdowney via email to report.

### Time of check to time of use

A library like `private_address_check` is going to be easily susceptible to attacks like [time of check to time of use](https://en.wikipedia.org/wiki/Time_of_check_to_time_of_use). DNS entries with a TTL of 0 can trigger this case where the initial resolution is a public address by the subsequent resolution is a private address. There are some possible defenses and workarounds:

- Use the TCPSocket extension in this library which checks the address the socket uses. This is most useful if your system is built on native Ruby like Net::HTTP.
- Use a feature like the `resolve` capability in curl and [curb](https://www.rubydoc.info/github/taf2/curb/Curl/Easy#resolve=-instance_method) to force the resolution to a pre-checked IP address.
- Implement your own caching DNS resolver with something like dnsmasq or unbound. These tools let you set a minimum cache time that can override the TTL of 0.
  def initialize(*args)
    initialize_without_private_address_check(*args)
    if Thread.current[:private_address_check] && PrivateAddressCheck.resolves_to_private_address?(remote_address.ip_address)
    server = TCPServer.new(63453)
    thread = Thread.start { server.accept }
        TCPSocket.new("localhost", 63453)
  ensure
    thread.exit if thread