Compare commits

..

1 Commits

Author SHA1 Message Date
rustdesk
40da387b16 temporary password: rotate when a peer is let in, not when it leaves
The one-time password was regenerated after the connection loop exited,
so a remote desktop session kept it valid for hours and a port-forward
tunnel for as long as its mapping lived. It now rotates the moment a
connection becomes authorized. Reconnects and windows opened from a
live session are unaffected: they log in on the password the session
remembers, for 30 seconds past its last activity.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab
2026-09-17 12:12:54 +08:00
2 changed files with 4 additions and 16 deletions

View File

@@ -221,8 +221,6 @@ pub async fn create_tcp_connection(
let Some(unauthorized) = admit_unauthorized(id, addr.ip()) else {
bail!("too many unauthenticated connections from {}", addr.ip());
};
// Before the handshake, so its read is bounded too; lifted again at authorization.
stream.set_max_packet_length(MAX_UNAUTHORIZED_MESSAGE);
tokio::select! {
handshake = identity_handshake(&mut stream, secure) => handshake?,
_ = unauthorized.evicted() => {

View File

@@ -93,13 +93,6 @@ const MAX_UNAUTHORIZED_CONNS: usize = 64;
/// of addresses passes it, and the bound above is what holds. Meaningful only while the
/// address is the controller's own, which punch and relay messages carry today.
const MAX_UNAUTHORIZED_CONNS_PER_ADDR: usize = 16;
/// The largest message a connection may send before it authorizes. Until then a peer sends only
/// a public key, a login request, a test delay and a close reason, none of which carries an
/// unbounded field - the login request's avatar is a URL. Sized to the read buffer tungstenite
/// allocates per WebSocket connection regardless, so there the cap costs nothing beyond a floor
/// already paid; with MAX_UNAUTHORIZED_CONNS it holds them to 8 MiB in all, against the 1 GiB a
/// single one could make us hold before.
pub const MAX_UNAUTHORIZED_MESSAGE: usize = 128 * 1024;
/// A place among the unauthorized connections, taken before the identity handshake and given
/// back on drop: at authorization, or when the connection ends first. The count of live
@@ -1213,9 +1206,6 @@ impl Connection {
}
}
video_service::notify_video_frame_fetched_by_conn_id(id, None);
if conn.authorized {
password::update_temporary_password();
}
if let Err(err) = conn.try_port_forward_loop(&mut rx_from_cm).await {
conn.on_close(&err.to_string(), false).await;
raii::AuthedConnID::check_remove_session(conn.inner.id(), conn.session_key());
@@ -1878,15 +1868,15 @@ impl Connection {
if let Some(keep_alive) = self.prepare_terminal_login_for_authorization().await {
return keep_alive;
}
// Lifted here rather than below with the rest of authorization: a multiplexed tunnel
// narrows it again for its own framing (`port_forward_mux::cap_packet_size`), so that
// call has to come after this one, not before.
self.stream.set_max_packet_length(usize::MAX);
if !self.connect_port_forward_if_needed().await {
return false;
}
self.authorized = true;
self.unauthorized_id = None;
// One-time means gone once it has let a peer in, not once that peer
// leaves. This session's later logins come in on the password the
// session remembers, so they are not affected.
password::update_temporary_password();
// Releases the budget `check_id_whitelist` charges against this address: only a peer
// that got this far proved more than a self-reported id.
self.clear_id_whitelist_failures();