Skip to content
Bitcoin Roots

v29.4-roots.3 · Commit 28: [PATCH 28/30] fix(wallet): secure asynchronous sweep cancellation

src/qt/sweepdialog.cpp

Before: src/qt/sweepdialog.cpp · After: src/qt/sweepdialog.cpp

File metadata
index b3b34e5dc8f50739b02c7d5490481fa6c73ad4da..630c562dd7a50d3686f512a576d154662d68bafb 100644

Download original patch · Release notes

Diff rows 1–85 of 85. Line numbers refer to the original before/after files.

+ Added− RemovedUnmarked lines are context.

Scroll the code area horizontally to read long lines. The two line-number columns refer to before and after.

src/qt/sweepdialog.cpp CHANGED
@@ -7,11 +7,13 @@
7
7
  #include <qt/bitcoinunits.h>
8
8
  #include <qt/optionsmodel.h>
9
9
  #include <qt/walletmodel.h>
10
+ #include <support/cleanse.h>
10
11
  #include <util/translation.h>
11
12
  #include <uint256.h>
12
13
 
13
- #include <QDialogButtonBox>
14
+ #include <QByteArray>
14
15
  #include <QCheckBox>
16
+ #include <QDialogButtonBox>
15
17
  #include <QFormLayout>
16
18
  #include <QKeyEvent>
17
19
  #include <QKeySequence>
@@ -28,6 +30,18 @@ class SecureLineEdit final : public QLineEdit
28
30
  public:
29
31
  using QLineEdit::QLineEdit;
30
32
 
33
+ SecureString TakeText()
34
+ {
35
+ QByteArray encoded{text().toLatin1()};
36
+ SecureString result{encoded.constData(), static_cast<size_t>(encoded.size())};
37
+ if (!encoded.isEmpty()) memory_cleanse(encoded.data(), encoded.size());
38
+ // QLineEdit does not expose its internal buffer for cleansing. Replace
39
+ // its contents before clearing so the widget no longer retains the WIF.
40
+ setText(QString(text().size(), QChar{' '}));
41
+ clear();
42
+ return result;
43
+ }
44
+
31
45
  protected:
32
46
  void keyPressEvent(QKeyEvent* event) override
33
47
  {
@@ -118,7 +132,10 @@ SweepDialog::~SweepDialog() { clearSensitive(); }
118
132
 
119
133
  void SweepDialog::invalidate()
120
134
  {
121
- if (m_request_generation != 0) m_wallet_model->invalidateSweepRequests();
135
+ if (m_request_generation != 0 && !m_wallet_model->invalidateSweepRequests()) {
136
+ m_result->setText(tr("Sweep broadcast submission has started and can no longer be cancelled. Wait for its result."));
137
+ return;
138
+ }
122
139
  m_request_generation = 0;
123
140
  m_private_key_cache.clear();
124
141
  m_preview_txid.clear();
@@ -147,13 +164,13 @@ void SweepDialog::preview()
147
164
  {
148
165
  const CTxDestination destination{DecodeDestination(m_destination->text().toStdString())};
149
166
  if (!IsValidDestination(destination)) { setResult(tr("Enter a valid destination address."), false); return; }
150
- m_private_key_cache = SecureString{m_private_key->text().toStdString()};
151
- if (m_private_key_cache.empty()) { setResult(tr("Enter a private key."), false); return; }
152
167
  const QSignalBlocker block{m_private_key};
153
- m_private_key->clear();
168
+ m_private_key_cache = static_cast<SecureLineEdit*>(m_private_key)->TakeText();
169
+ if (m_private_key_cache.empty()) { setResult(tr("Enter a private key."), false); return; }
154
170
  setBusy(true);
155
171
  m_result->setText(tr("Scanning for eligible coins and preparing a preview…"));
156
172
  m_request_generation = m_wallet_model->requestSweep(m_private_key_cache, destination, false);
173
+ if (m_request_generation == 0) setResult(tr("Another sweep broadcast is already being submitted."), false);
157
174
  }
158
175
 
159
176
  void SweepDialog::broadcast()
@@ -170,6 +187,18 @@ void SweepDialog::broadcast()
170
187
  m_preview_current = false;
171
188
  m_result->setText(tr("Rechecking the preview and broadcasting the sweep…"));
172
189
  m_request_generation = m_wallet_model->requestSweep(m_private_key_cache, destination, true, expected_txid);
190
+ if (m_request_generation == 0) setResult(tr("Sweep broadcast submission has already started. Wait for its result."), false);
191
+ }
192
+
193
+ void SweepDialog::reject()
194
+ {
195
+ if (m_request_generation != 0 && !m_wallet_model->invalidateSweepRequests()) {
196
+ m_result->setText(tr("Sweep broadcast submission has started and can no longer be cancelled. Wait for its result."));
197
+ return;
198
+ }
199
+ m_request_generation = 0;
200
+ clearSensitive();
201
+ QDialog::reject();
173
202
  }
174
203
 
175
204
  void SweepDialog::handleSweepFinished(uint64_t generation, bool broadcast, bool success, const QString& error,
@@ -204,7 +233,7 @@ void SweepDialog::handleSweepFinished(uint64_t generation, bool broadcast, bool
204
233
 
205
234
  void SweepDialog::clearSensitive()
206
235
  {
207
- if (m_request_generation != 0) m_wallet_model->invalidateSweepRequests();
236
+ if (m_request_generation != 0 && !m_wallet_model->invalidateSweepRequests()) return;
208
237
  m_request_generation = 0;
209
238
  const QSignalBlocker block{m_private_key};
210
239
  m_private_key->clear();