diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index c1f2accc599e7..b4c9a15baa074 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -24,6 +24,7 @@ #endif // ENABLE_WALLET #include #include +#include #include #include #include @@ -691,6 +692,85 @@ void RPCConsole::WriteCommandHistory() settings.endArray(); } +void RPCConsole::ClearCommandHistory() +{ + // Best-effort attempt to securely overwrite command history in memory. + // Note: Complete memory sanitization cannot be guaranteed with QString due to + // Qt's implicit sharing (copy-on-write) and internal memory management. + for (QString& cmd : history) { + if (cmd.isEmpty()) continue; + + // Attempt to cleanse QString's internal buffer using Bitcoin Core's + // secure memory_cleanse function (uses platform-specific secure zeroing) + QChar* data = cmd.data(); // Get pointer to mutable internal buffer + size_t byte_size = cmd.size() * sizeof(QChar); + + // First pass: overwrite with 'x' and cleanse + cmd.fill('x'); + if (data == cmd.data()) { // Verify we still have the same buffer + memory_cleanse(cmd.data(), byte_size); + } + + // Second pass: overwrite with null and cleanse + cmd.fill('\0'); + if (data == cmd.data()) { // Verify we still have the same buffer + memory_cleanse(cmd.data(), byte_size); + } + } + + // Overwrite saved history in settings with dummy data + QSettings settings; + + // First pass: read existing commands and overwrite with dummy data of same length + QStringList dummy_commands; + int size = settings.beginReadArray("nRPCConsoleWindowHistory"); + for (int i = 0; i < size; ++i) { + settings.setArrayIndex(i); + QString cmd = settings.value("cmd").toString(); + // Store dummy command with same length as original (don't leak length info) + dummy_commands.append(QString(cmd.size(), 'x')); + } + settings.endArray(); + + // Write dummy data to overwrite the original commands + if (!dummy_commands.empty()) { + settings.beginWriteArray("nRPCConsoleWindowHistory"); + for (int i = 0; i < dummy_commands.size(); ++i) { + settings.setArrayIndex(i); + settings.setValue("cmd", dummy_commands[i]); + } + settings.endArray(); + settings.sync(); // Force to disk + } + + // Clear the history list + history.clear(); + historyPtr = 0; + + // Also cleanse cmdBeforeBrowsing which may contain command data + if (!cmdBeforeBrowsing.isEmpty()) { + QChar* data = cmdBeforeBrowsing.data(); + size_t byte_size = cmdBeforeBrowsing.size() * sizeof(QChar); + cmdBeforeBrowsing.fill('x'); + if (data == cmdBeforeBrowsing.data()) { + memory_cleanse(cmdBeforeBrowsing.data(), byte_size); + } + cmdBeforeBrowsing.fill('\0'); + if (data == cmdBeforeBrowsing.data()) { + memory_cleanse(cmdBeforeBrowsing.data(), byte_size); + } + } + cmdBeforeBrowsing.clear(); + + // Second pass: write empty history to settings + settings.beginWriteArray("nRPCConsoleWindowHistory"); + // Empty array - no entries written + settings.endArray(); + + // Force settings to sync to disk + settings.sync(); +} + RPCConsole::~RPCConsole() { QSettings settings; @@ -1182,6 +1262,26 @@ void RPCConsole::on_lineEdit_returnPressed() return; } + // Special command to clear command history + if (cmd == QLatin1String("/clearhistory")) { + QMessageBox::StandardButton reply = QMessageBox::question(this, + tr("Clear Command History"), + tr("This will permanently clear your command history and console output.

" + "While this action is irreversible, complete removal from memory and disk " + "cannot be guaranteed due to system and Qt framework limitations.

" + "Are you sure you want to proceed?"), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No); + + if (reply == QMessageBox::Yes) { + ClearCommandHistory(); + clear(false); // Clear console output too + message(CMD_REPLY, tr("Command history and console output cleared.")); + } + ui->lineEdit->clear(); + return; + } + if (m_is_executing) { return; } diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 0be4923bcf9fa..c7b4802155ae5 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -166,6 +166,7 @@ public Q_SLOTS: void startExecutor(); void setTrafficGraphRange(int mins); void WriteCommandHistory(); + void ClearCommandHistory(); enum ColumnWidths {