Fix SSH askpass helper text-file-busy race

This commit is contained in:
Keith Smith
2026-03-01 09:42:32 -07:00
parent e2a8b874d7
commit 3c158269bf
2 changed files with 28 additions and 20 deletions

View File

@@ -4,8 +4,8 @@
#include <QFile>
#include <QFileInfo>
#include <QProcessEnvironment>
#include <QTemporaryFile>
#include <QTextStream>
#include <QUuid>
namespace {
QString escapeForShellSingleQuotes(const QString& value)
@@ -33,8 +33,7 @@ SshSessionBackend::SshSessionBackend(const Profile& profile, QObject* parent)
m_connectedProbeTimer(new QTimer(this)),
m_state(SessionState::Disconnected),
m_userInitiatedDisconnect(false),
m_reconnectPending(false),
m_askPassScript(nullptr)
m_reconnectPending(false)
{
m_connectedProbeTimer->setSingleShot(true);
@@ -325,21 +324,26 @@ bool SshSessionBackend::configureAskPass(const SessionConnectOptions& options,
QProcessEnvironment& environment,
QString& error)
{
cleanupAskPassScript();
#ifdef Q_OS_WIN
m_askPassScript = new QTemporaryFile(QDir::tempPath() + QStringLiteral("/orbithub_askpass_XXXXXX.cmd"),
this);
m_askPassScriptPath = QDir::temp().filePath(
QStringLiteral("orbithub_askpass_%1.cmd")
.arg(QUuid::createUuid().toString(QUuid::WithoutBraces)));
#else
m_askPassScript = new QTemporaryFile(QDir::tempPath() + QStringLiteral("/orbithub_askpass_XXXXXX.sh"),
this);
m_askPassScriptPath = QDir::temp().filePath(
QStringLiteral("orbithub_askpass_%1.sh")
.arg(QUuid::createUuid().toString(QUuid::WithoutBraces)));
#endif
if (!m_askPassScript->open()) {
QFile script(m_askPassScriptPath);
if (!script.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
error = QStringLiteral("Failed to create temporary askpass helper script.");
cleanupAskPassScript();
return false;
}
QTextStream out(m_askPassScript);
QTextStream out(&script);
#ifdef Q_OS_WIN
out << "@echo off\r\n";
out << "echo " << escapedForWindowsEcho(options.password) << "\r\n";
@@ -348,15 +352,18 @@ bool SshSessionBackend::configureAskPass(const SessionConnectOptions& options,
out << "printf '%s\\n' '" << escapeForShellSingleQuotes(options.password) << "'\n";
#endif
out.flush();
m_askPassScript->flush();
m_askPassScript->close();
script.close();
#ifndef Q_OS_WIN
QFile::setPermissions(m_askPassScript->fileName(),
QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
if (!QFile::setPermissions(m_askPassScriptPath,
QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)) {
error = QStringLiteral("Failed to set permissions on askpass helper script.");
cleanupAskPassScript();
return false;
}
#endif
environment.insert(QStringLiteral("SSH_ASKPASS"), m_askPassScript->fileName());
environment.insert(QStringLiteral("SSH_ASKPASS"), m_askPassScriptPath);
environment.insert(QStringLiteral("SSH_ASKPASS_REQUIRE"), QStringLiteral("force"));
if (!environment.contains(QStringLiteral("DISPLAY"))) {
environment.insert(QStringLiteral("DISPLAY"), QStringLiteral(":0"));
@@ -367,9 +374,9 @@ bool SshSessionBackend::configureAskPass(const SessionConnectOptions& options,
void SshSessionBackend::cleanupAskPassScript()
{
if (m_askPassScript != nullptr) {
delete m_askPassScript;
m_askPassScript = nullptr;
if (!m_askPassScriptPath.isEmpty()) {
QFile::remove(m_askPassScriptPath);
m_askPassScriptPath.clear();
}
}
@@ -402,6 +409,9 @@ QString SshSessionBackend::mapSshError(const QString& rawError) const
if (raw.contains(QStringLiteral("No such file or directory"), Qt::CaseInsensitive)) {
return QStringLiteral("Required file was not found.");
}
if (raw.contains(QStringLiteral("Text file busy"), Qt::CaseInsensitive)) {
return QStringLiteral("Credential helper could not start (text file busy). Retry the connection.");
}
if (raw.isEmpty()) {
return QStringLiteral("SSH connection failed for an unknown reason.");
}

View File

@@ -7,8 +7,6 @@
#include <QString>
#include <QTimer>
class QTemporaryFile;
class SshSessionBackend : public SessionBackend
{
Q_OBJECT
@@ -37,7 +35,7 @@ private:
bool m_reconnectPending;
SessionConnectOptions m_reconnectOptions;
QString m_lastRawError;
QTemporaryFile* m_askPassScript;
QString m_askPassScriptPath;
void setState(SessionState state, const QString& message);
bool startSshProcess(const SessionConnectOptions& options);