Fix nested runtime panic during SSH key authentication
Dependency security audit / rustsec (push) Successful in 23s

This commit is contained in:
Georg Bauer committed 2026-09-12 18:08:23 +02:00
1 parent aec3f237a9
commit 71a4f289cf
2 files changed
+36 -14

No files matched your search

+14 -1
View File
@@ -755,8 +755,21 @@ fn non_fast_forward_and_cancellation_fail_without_blind_replay() {
local.add_remote("origin", remote.url()).expect("remote"); local.add_remote("origin", remote.url()).expect("remote");
let control = GitOperationControl::default(); let control = GitOperationControl::default();
let cancel = control.clone(); let cancel = control.clone();
let observed = server.observed.clone();
let cancellation = thread::spawn(move || { let cancellation = thread::spawn(move || {
thread::sleep(Duration::from_millis(100)); let deadline = std::time::Instant::now() + Duration::from_secs(5);
while observed
.lock()
.expect("observed")
.receive_requests
.is_empty()
{
assert!(
std::time::Instant::now() < deadline,
"push request not received"
);
thread::sleep(Duration::from_millis(1));
}
cancel.cancel(); cancel.cancel();
}); });
assert!(matches!( assert!(matches!(
+22 -13
View File
@@ -162,13 +162,11 @@ impl SshSession {
control.report(GitProgressPhase::Authenticating)?; control.report(GitProgressPhase::Authenticating)?;
let user = connection.user.as_deref().unwrap_or("git"); let user = connection.user.as_deref().unwrap_or("git");
let authenticated = match &connection.identity { let authenticated = match &connection.identity {
SshIdentitySource::KeyFile(path) => runtime.block_on(authenticate_key_file( SshIdentitySource::KeyFile(path) => {
&mut handle, // Synchronous secret stores may drive their own runtime.
user, let key = load_identity(path, passphrases)?;
path, runtime.block_on(authenticate_key_file(&mut handle, user, key, control))?
passphrases, }
control,
))?,
SshIdentitySource::Agent { SshIdentitySource::Agent {
fingerprint, fingerprint,
socket, socket,
@@ -770,13 +768,10 @@ impl client::Handler for HostVerifier {
} }
} }
async fn authenticate_key_file( fn load_identity(
handle: &mut client::Handle<HostVerifier>,
user: &str,
path: &Path, path: &Path,
passphrases: &dyn SshPassphraseProvider, passphrases: &dyn SshPassphraseProvider,
control: &GitOperationControl, ) -> Result<PrivateKey, GitError> {
) -> Result<bool, GitError> {
let metadata = fs::symlink_metadata(path).map_err(|_| GitError::SshIdentityMissing { let metadata = fs::symlink_metadata(path).map_err(|_| GitError::SshIdentityMissing {
path: path.to_owned(), path: path.to_owned(),
})?; })?;
@@ -802,6 +797,15 @@ async fn authenticate_key_file(
fingerprint: fingerprint.clone(), fingerprint: fingerprint.clone(),
})?; })?;
} }
Ok(key)
}
async fn authenticate_key_file(
handle: &mut client::Handle<HostVerifier>,
user: &str,
key: PrivateKey,
control: &GitOperationControl,
) -> Result<bool, GitError> {
let hash = rsa_hash(handle, key.public_key(), control).await?; let hash = rsa_hash(handle, key.public_key(), control).await?;
let result = controlled( let result = controlled(
handle.authenticate_publickey(user, PrivateKeyWithHashAlg::new(Arc::new(key), hash)), handle.authenticate_publickey(user, PrivateKeyWithHashAlg::new(Arc::new(key), hash)),
@@ -1230,7 +1234,12 @@ mod tests {
&self, &self,
fingerprint: &SshFingerprint, fingerprint: &SshFingerprint,
) -> Result<SecretBytes, GitError> { ) -> Result<SecretBytes, GitError> {
self.0 // Model a synchronous secret store that drives its own async backend.
static RUNTIME: std::sync::OnceLock<tokio::runtime::Runtime> =
std::sync::OnceLock::new();
RUNTIME
.get_or_init(|| tokio::runtime::Runtime::new().expect("secret store runtime"))
.block_on(async { self.0 })
.map(|value| SecretBytes::new(value.to_vec())) .map(|value| SecretBytes::new(value.to_vec()))
.ok_or_else(|| GitError::SshKeyPassphraseUnavailable { .ok_or_else(|| GitError::SshKeyPassphraseUnavailable {
fingerprint: fingerprint.clone(), fingerprint: fingerprint.clone(),