-
Notifications
You must be signed in to change notification settings - Fork 948
fix: TOCTOU issue in network config #8551
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: unstable
Are you sure you want to change the base?
Conversation
8897c84 to
529da42
Compare
jxs
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, thanks for this. Left some comments
| /// - QUIC port defaults to TCP port + 1 (to avoid conflict with discovery UDP) | ||
| pub fn compute_listen_ports( | ||
| use_zero_ports: bool, | ||
| port: u16, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| port: u16, | |
| tcp_port: u16, |
| return (0, 0, 0); | ||
| } | ||
|
|
||
| let tcp_port = port; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| let tcp_port = port; |
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need all these tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i can lighten them up a bit
| pub fn bind_tcp4_any() -> Result<TcpListener, String> { | ||
| let addr = std::net::SocketAddr::new(std::net::Ipv4Addr::LOCALHOST.into(), 0); | ||
| TcpListener::bind(addr).map_err(|e| format!("Failed to bind TCPv4 listener: {:?}", e)) | ||
| } | ||
|
|
||
| /// Bind a TCPv6 listener on localhost with an ephemeral port (port 0) and return it. | ||
| /// Safe against TOCTOU: the socket remains open and reserved by the OS. | ||
| pub fn bind_tcp6_any() -> Result<TcpListener, String> { | ||
| let addr = std::net::SocketAddr::new(std::net::Ipv6Addr::LOCALHOST.into(), 0); | ||
| TcpListener::bind(addr).map_err(|e| format!("Failed to bind TCPv6 listener: {:?}", e)) | ||
| } | ||
|
|
||
| /// Bind a UDPv4 socket on localhost with an ephemeral port (port 0) and return it. | ||
| /// Safe against TOCTOU: the socket remains open and reserved by the OS. | ||
| pub fn bind_udp4_any() -> Result<UdpSocket, String> { | ||
| let addr = std::net::SocketAddr::new(std::net::Ipv4Addr::LOCALHOST.into(), 0); | ||
| UdpSocket::bind(addr).map_err(|e| format!("Failed to bind UDPv4 socket: {:?}", e)) | ||
| } | ||
|
|
||
| /// Bind a UDPv6 socket on localhost with an ephemeral port (port 0) and return it. | ||
| /// Safe against TOCTOU: the socket remains open and reserved by the OS. | ||
| pub fn bind_udp6_any() -> Result<UdpSocket, String> { | ||
| let addr = std::net::SocketAddr::new(std::net::Ipv6Addr::LOCALHOST.into(), 0); | ||
| UdpSocket::bind(addr).map_err(|e| format!("Failed to bind UDPv6 socket: {:?}", e)) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are these functions used anywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those methods are not used, there there as part of requirements from issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC the issue mentions
PR #8016 by @sashaodessa proposed a fix by replacing these functions with secure APIs that return already-bound sockets:
bind_tcp4_any() / bind_tcp6_any() → returns TcpListener
bind_udp4_any() / bind_udp6_any() → returns UdpSocket
not that they are required right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so to get rid of theoretical toctou when actually running the node, you just need to set port to zero when your setting up config (you dont need those methods). as for the integ tests that is a separate issue, please read next step section because that will influence if these methods stay or not. They were not used in original CR from @sashaodessa either
Eliminates TOCTOU vulnerability in beacon node by replacing unsafe port-finding with OS-assigned ephemeral ports (port 0), ensuring atomic port allocation without race conditions. Production beacon client is fully fixed with new compute_listen_ports() helper; integration tests retain deprecated approach temporarily pending maintainer input. This is related to issue 8490
529da42 to
6b94f6b
Compare
|
Should be good for another review |
Fixes TOCTOU issue in beacon node by replacing unsafe port-finding with OS-assigned ports (port 0), ensuring atomic port allocation without race conditions. (Basicly fixing a theoritcal race condition that would prevent the node to start up) Production beacon client is fully fixed with new compute_listen_ports() helper; integration tests retain deprecated approach temporarily pending maintainer input.
Issue Addressed
Which issue # does this PR address? - #8490
Proposed Changes
Changes Introduced
compute_listen_ports()helper function inlisten_addr.rsto centralize port computation logic for TCP, discovery UDP, and QUIC UDP portsbeacon_node/src/config.rsto use new helper for IPv4, IPv6, and dual-stack configurationslighthouse/tests/beacon_node.rsto verify--zero-portsflag behaviorbind_*_any()APIs for future migration of test code (currently unused)execution_engine_integrationtests pending maintainer guidance - the rest of the old methods were removedWhat's Fixed / Additional Info
Next steps
I think this PR should be merged as is, but we need to address the CL/EL integration test issues. Here I need help from maintainers. Basically, the EL/CL integration tests are still using the old approach that has the TOCTOU issue because after we start the EL process, we need to pass the ports chosen by the EL downstream, which is not easily possible without parsing stdout of the child process—a fragile approach I'm not sure you want to take. That being said, outside of trying to pass a file descriptor into the CLI of EL, this is the only approach I can think of.
Let me know your thoughts. The relevant code is here: https://github.com/sigp/lighthouse/blob/stable/testing/execution_engine_integration/src/execution_engine.rs#L55 If you folks want me to go down the path of parsing the stdout of the EL CLI, I will make an additional PR after this is merged.
Testing
ran
cargo nextest run -p lighthouse --releaseand passed all tests, see belowran
cargo test -p network_utils --liband passed all tests, see below:ran
cargo test -p lighthouse_networkand passed all tests, see below: