Skip to content
Bitcoin Roots

v30.3-roots.1 · Commit 50: [PATCH 50/68] qt: cleanse and release transient sweep cache

src/qt/sweepdialog.cpp

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

File metadata
index c410a97daa2643469ab2a149605b2ae48d09a3fd..4cdfcb7e5d350a1e1327ec5ee9dd199494361ec6 100644

Download original patch · Release notes

Diff rows 1–97 of 97. 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
@@ -36,7 +36,8 @@ public:
36
36
  SecureString result{encoded.constData(), static_cast<size_t>(encoded.size())};
37
37
  if (!encoded.isEmpty()) memory_cleanse(encoded.data(), encoded.size());
38
38
  // QLineEdit does not expose its internal buffer for cleansing. Replace
39
- // its contents before clearing so the widget no longer retains the WIF.
39
+ // its visible contents before clearing; Qt does not guarantee physical
40
+ // erasure of every internal QString or undo-buffer copy.
40
41
  setText(QString(text().size(), QChar{' '}));
41
42
  clear();
42
43
  return result;
@@ -118,7 +119,7 @@ SweepDialog::SweepDialog(WalletModel* wallet_model, QWidget* parent)
118
119
  m_request_generation = 0;
119
120
  const QSignalBlocker block{m_private_key};
120
121
  m_private_key->clear();
121
- m_private_key_cache.clear();
122
+ clearPrivateKeyCache();
122
123
  m_preview_txid.clear();
123
124
  setBusy(false);
124
125
  m_preview_current = false;
@@ -130,6 +131,15 @@ SweepDialog::SweepDialog(WalletModel* wallet_model, QWidget* parent)
130
131
 
131
132
  SweepDialog::~SweepDialog() { clearSensitive(); }
132
133
 
134
+ void SweepDialog::clearPrivateKeyCache()
135
+ {
136
+ // clear() alone retains storage, including the old secret beyond size().
137
+ // Wipe live bytes first, including short-string storage, then release any
138
+ // allocation through secure_allocator's cleansing deallocator.
139
+ if (!m_private_key_cache.empty()) memory_cleanse(m_private_key_cache.data(), m_private_key_cache.size());
140
+ SecureString{}.swap(m_private_key_cache);
141
+ }
142
+
133
143
  void SweepDialog::invalidate()
134
144
  {
135
145
  if (m_request_generation != 0 && !m_wallet_model->invalidateSweepRequests()) {
@@ -137,7 +147,7 @@ void SweepDialog::invalidate()
137
147
  return;
138
148
  }
139
149
  m_request_generation = 0;
140
- m_private_key_cache.clear();
150
+ clearPrivateKeyCache();
141
151
  m_preview_txid.clear();
142
152
  m_preview_current = false;
143
153
  m_broadcast->setEnabled(false);
@@ -162,6 +172,7 @@ void SweepDialog::setResult(const QString& text, bool can_broadcast)
162
172
 
163
173
  void SweepDialog::preview()
164
174
  {
175
+ clearPrivateKeyCache();
165
176
  const CTxDestination destination{DecodeDestination(m_destination->text().toStdString())};
166
177
  if (!IsValidDestination(destination)) { setResult(tr("Enter a valid destination address."), false); return; }
167
178
  const QSignalBlocker block{m_private_key};
@@ -170,7 +181,11 @@ void SweepDialog::preview()
170
181
  setBusy(true);
171
182
  m_result->setText(tr("Scanning for eligible coins and preparing a preview…"));
172
183
  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);
184
+ if (m_request_generation == 0) {
185
+ clearPrivateKeyCache();
186
+ setBusy(false);
187
+ setResult(tr("Another sweep broadcast is already being submitted."), false);
188
+ }
174
189
  }
175
190
 
176
191
  void SweepDialog::broadcast()
@@ -182,12 +197,20 @@ void SweepDialog::broadcast()
182
197
  if (answer != QMessageBox::Yes) return;
183
198
  const CTxDestination destination{DecodeDestination(m_destination->text().toStdString())};
184
199
  const auto expected_txid{Txid::FromHex(m_preview_txid.toStdString())};
185
- if (!expected_txid) { setResult(tr("The preview is no longer valid. Create a fresh preview."), false); return; }
200
+ if (!expected_txid) {
201
+ clearPrivateKeyCache();
202
+ setResult(tr("The preview is no longer valid. Create a fresh preview."), false);
203
+ return;
204
+ }
186
205
  setBusy(true);
187
206
  m_preview_current = false;
188
207
  m_result->setText(tr("Rechecking the preview and broadcasting the sweep…"));
189
208
  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);
209
+ if (m_request_generation == 0) {
210
+ clearPrivateKeyCache();
211
+ setBusy(false);
212
+ setResult(tr("Sweep broadcast submission has already started. Wait for its result."), false);
213
+ }
191
214
  }
192
215
 
193
216
  void SweepDialog::reject()
@@ -211,7 +234,7 @@ void SweepDialog::handleSweepFinished(uint64_t generation, bool broadcast, bool
211
234
  setResult(broadcast
212
235
  ? tr("Broadcast failed: %1. Create a fresh preview before trying again.").arg(error)
213
236
  : tr("Preview failed: %1").arg(error), false);
214
- m_private_key_cache.clear();
237
+ clearPrivateKeyCache();
215
238
  m_preview_txid.clear();
216
239
  return;
217
240
  }
@@ -237,7 +260,7 @@ void SweepDialog::clearSensitive()
237
260
  m_request_generation = 0;
238
261
  const QSignalBlocker block{m_private_key};
239
262
  m_private_key->clear();
240
- m_private_key_cache.clear();
263
+ clearPrivateKeyCache();
241
264
  m_preview_txid.clear();
242
265
  m_preview_current = false;
243
266
  setBusy(false);