ruby / debug

Debugging functionality for Ruby
BSD 2-Clause "Simplified" License
1.14k stars 127 forks source link

Add option to try to connect to a range of TCP ports #1119

Open gerymate opened 1 month ago

gerymate commented 1 month ago

Add option to try to connect to a range of TCP ports

Fixes: #368 Fixes: #1117

Description

We can set an integer value with --port-range or RUBY_DEBUG_PORT_RANGE in addition to setting a TCP port for remote debugging. When port is unavailable, rdbg will try to open port + 1, port + 2, etc. until it finds a suitable port, or reaches the length of the range. This makes it possible to remote debug multiple debuggees in a multithreaded environment, but still use TCP ports in a determined way.

gerymate commented 2 weeks ago

@ko1 sorry for disturbing, I'm new here. Who and how can I ask for some feedback?

ko1 commented 2 weeks ago

Thank you for pinging me.

I'm not a network and security expert, but is it safe to try to connect not specific port?

My malicious scenario is:

  1. Attacker opens a port X
  2. Debuggee uses port range X to X+10, and choose X+1 (because X is used by attacker's process)
  3. Debugger connect with port range X to X+10 and connect to attacker's process (port X)
  4. Attacker can snoop any messages by proxying to debuggee port (X+1).
gerymate commented 1 week ago

I'm not a security expert either, however, I don't see additional security risk from trying a port range.

Unless there are security flaws in DAP or CDP, I don't see how could the attacker proxy traffic to another port. However, I consider the first reason to be the strongest: the developer's responsibility is to secure the dev box, it's not the task of the debug tool.

duerst commented 1 week ago

I'm not really a network expert, nor a debugging expert, but something like --port-range 10000 doesn't look good to me. It may be seen as an attack (port scanning). I'd suggest to limit --port-range to some maximum (e.g. 100 or 256 or so).

ko1 commented 1 week ago

I'm not a security expert either, however, I don't see additional security risk from trying a port range.

  • The attacker can't open a port X without already being able to execute arbitrary code on the target machine with rights to open a port (as occupying a port can not be initiated from "outside", just from inside).

For example, on a shared machine (many users share a one machine. not seen now a day), attacker can open the port.

  • When the above fails and the debugger connects to the port the attacker opened, it is still used as a one-to-one connection. I don't know the internals there, but I think the debugger has the responsibility to initiate the connection (at least in DAP), issues commands, and the attacker's process can only mimic a debuggee and reply according to the DAP, but can't query the debugger process, the flow of the communication is the other way around.

attacker's process can search correct port (X+1 for example) and it can be a proxy to X+1. Debugging protocol is simple text messaging and easy to scan.

Anyway, we are not expert, could you find other examples such mechanism?

gerymate commented 4 days ago

I've updated the PR based on @duerst's feedback. The use case for this feature is to be able to reliably debug in a container where multiple worker threads are created. In our case, we use it for a Rails app running in a container, and we want to be able to debug both the main thread, the worker threads, and also to be able to run Rails console in the the context of the container (which, when debug is enabled by an ENV var, already starts two threads) or RSpec. Currently we could set PORT to 0 and thus have random ports opened, but it makes unnecessary hard to find those ports.

There are many examples of using similar mechanism for reliably connecting to applications. For example KDE Connect uses dynamic ports in a port range (see docs). Discord is using a port range for local RPC server. Microsoft and Apple uses it in various communication software (Teams, Silverlight and RTP/RTPC protocols), but those are mostly encrypted, so it's not that relevant here. Many multiplayer games use port ranges to find available ones.

Sorry, I still don't understand the proxy argument, as even when using port ranges there is only one port open during debugging by one debugger process. The port range just means that when X is already used, the debuggee will be listening on X+1 instead of X. The user has to choose one port for debugging (unless using a debugger tool that supports multiple remote debug sessions at the same time - I don't know if such system exists today or not). So a debug session either connects to the attacker's process, or one of the debuggees, but not both.