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

View File

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