URI: 
       tqt PasswordLineEdit: try to clear password from memory - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 5259fcb6fd0cbb2ae77c55dc4efa1ca37350baab
   DIR parent c798e5d9a19f42c86aa09bb48fda465ddc6e774d
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Tue,  7 Apr 2020 18:04:04 +0200
       
       qt PasswordLineEdit: try to clear password from memory
       
       If an attacker has access to the process' memory, it's probably already game over,
       still we can make their life a bit harder.
       
       I really tried but failed to encapsulate this logic inside PasswordLineEdit.
       The destroyed signal arrives too late.
       deleteLater is not called.
       __del__ gets called too late.
       
       Diffstat:
         M electrum/gui/qt/installwizard.py    |     101 +++++++++++++++++--------------
         M electrum/gui/qt/password_dialog.py  |      23 +++++++++++++++++------
         M electrum/gui/qt/util.py             |       6 ++++++
       
       3 files changed, 78 insertions(+), 52 deletions(-)
       ---
   DIR diff --git a/electrum/gui/qt/installwizard.py b/electrum/gui/qt/installwizard.py
       t@@ -281,51 +281,57 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
                name_e.textChanged.connect(on_filename)
                name_e.setText(os.path.basename(path))
        
       -        while True:
       -            if self.loop.exec_() != 2:  # 2 = next
       -                raise UserCancelled
       -            assert temp_storage
       -            if temp_storage.file_exists() and not temp_storage.is_encrypted():
       -                break
       -            if not temp_storage.file_exists():
       -                break
       -            wallet_from_memory = get_wallet_from_daemon(temp_storage.path)
       -            if wallet_from_memory:
       -                raise WalletAlreadyOpenInMemory(wallet_from_memory)
       -            if temp_storage.file_exists() and temp_storage.is_encrypted():
       -                if temp_storage.is_encrypted_with_user_pw():
       -                    password = pw_e.text()
       -                    try:
       -                        temp_storage.decrypt(password)
       -                        break
       -                    except InvalidPassword as e:
       -                        self.show_message(title=_('Error'), msg=str(e))
       -                        continue
       -                    except BaseException as e:
       -                        self.logger.exception('')
       -                        self.show_message(title=_('Error'), msg=repr(e))
       -                        raise UserCancelled()
       -                elif temp_storage.is_encrypted_with_hw_device():
       -                    try:
       -                        self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET, storage=temp_storage)
       -                    except InvalidPassword as e:
       -                        self.show_message(title=_('Error'),
       -                                          msg=_('Failed to decrypt using this hardware device.') + '\n' +
       -                                              _('If you use a passphrase, make sure it is correct.'))
       -                        self.reset_stack()
       -                        return self.select_storage(path, get_wallet_from_daemon)
       -                    except (UserCancelled, GoBack):
       -                        raise
       -                    except BaseException as e:
       -                        self.logger.exception('')
       -                        self.show_message(title=_('Error'), msg=repr(e))
       -                        raise UserCancelled()
       -                    if temp_storage.is_past_initial_decryption():
       -                        break
       +        def run_user_interaction_loop():
       +            while True:
       +                if self.loop.exec_() != 2:  # 2 = next
       +                    raise UserCancelled
       +                assert temp_storage
       +                if temp_storage.file_exists() and not temp_storage.is_encrypted():
       +                    break
       +                if not temp_storage.file_exists():
       +                    break
       +                wallet_from_memory = get_wallet_from_daemon(temp_storage.path)
       +                if wallet_from_memory:
       +                    raise WalletAlreadyOpenInMemory(wallet_from_memory)
       +                if temp_storage.file_exists() and temp_storage.is_encrypted():
       +                    if temp_storage.is_encrypted_with_user_pw():
       +                        password = pw_e.text()
       +                        try:
       +                            temp_storage.decrypt(password)
       +                            break
       +                        except InvalidPassword as e:
       +                            self.show_message(title=_('Error'), msg=str(e))
       +                            continue
       +                        except BaseException as e:
       +                            self.logger.exception('')
       +                            self.show_message(title=_('Error'), msg=repr(e))
       +                            raise UserCancelled()
       +                    elif temp_storage.is_encrypted_with_hw_device():
       +                        try:
       +                            self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET, storage=temp_storage)
       +                        except InvalidPassword as e:
       +                            self.show_message(title=_('Error'),
       +                                              msg=_('Failed to decrypt using this hardware device.') + '\n' +
       +                                                  _('If you use a passphrase, make sure it is correct.'))
       +                            self.reset_stack()
       +                            return self.select_storage(path, get_wallet_from_daemon)
       +                        except (UserCancelled, GoBack):
       +                            raise
       +                        except BaseException as e:
       +                            self.logger.exception('')
       +                            self.show_message(title=_('Error'), msg=repr(e))
       +                            raise UserCancelled()
       +                        if temp_storage.is_past_initial_decryption():
       +                            break
       +                        else:
       +                            raise UserCancelled()
                            else:
       -                        raise UserCancelled()
       -                else:
       -                    raise Exception('Unexpected encryption version')
       +                        raise Exception('Unexpected encryption version')
       +
       +        try:
       +            run_user_interaction_loop()
       +        finally:
       +            pw_e.clear()
        
                return temp_storage.path, (temp_storage if temp_storage.file_exists() else None)
        
       t@@ -482,8 +488,11 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
                playout = PasswordLayout(msg=msg, kind=kind, OK_button=self.next_button,
                                         force_disable_encrypt_cb=force_disable_encrypt_cb)
                playout.encrypt_cb.setChecked(True)
       -        self.exec_layout(playout.layout())
       -        return playout.new_password(), playout.encrypt_cb.isChecked()
       +        try:
       +            self.exec_layout(playout.layout())
       +            return playout.new_password(), playout.encrypt_cb.isChecked()
       +        finally:
       +            playout.clear_password_fields()
        
            @wizard_dialog
            def request_password(self, run_next, force_disable_encrypt_cb=False):
   DIR diff --git a/electrum/gui/qt/password_dialog.py b/electrum/gui/qt/password_dialog.py
       t@@ -25,6 +25,7 @@
        
        import re
        import math
       +from functools import partial
        
        from PyQt5.QtCore import Qt
        from PyQt5.QtGui import QPixmap
       t@@ -165,6 +166,10 @@ class PasswordLayout(object):
                    pw = None
                return pw
        
       +    def clear_password_fields(self):
       +        for field in [self.pw, self.new_pw, self.conf_pw]:
       +            field.clear()
       +
        
        class PasswordLayoutForHW(object):
        
       t@@ -258,9 +263,12 @@ class ChangePasswordDialogForSW(ChangePasswordDialogBase):
                                              force_disable_encrypt_cb=not wallet.can_have_keystore_encryption())
        
            def run(self):
       -        if not self.exec_():
       -            return False, None, None, None
       -        return True, self.playout.old_password(), self.playout.new_password(), self.playout.encrypt_cb.isChecked()
       +        try:
       +            if not self.exec_():
       +                return False, None, None, None
       +            return True, self.playout.old_password(), self.playout.new_password(), self.playout.encrypt_cb.isChecked()
       +        finally:
       +            self.playout.clear_password_fields()
        
        
        class ChangePasswordDialogForHW(ChangePasswordDialogBase):
       t@@ -301,6 +309,9 @@ class PasswordDialog(WindowModalDialog):
                run_hook('password_dialog', pw, grid, 1)
        
            def run(self):
       -        if not self.exec_():
       -            return
       -        return self.pw.text()
       +        try:
       +            if not self.exec_():
       +                return
       +            return self.pw.text()
       +        finally:
       +            self.pw.clear()
   DIR diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py
       t@@ -753,6 +753,12 @@ class PasswordLineEdit(QLineEdit):
                QLineEdit.__init__(self, *args, **kwargs)
                self.setEchoMode(QLineEdit.Password)
        
       +    def clear(self):
       +        # Try to actually overwrite the memory.
       +        # This is really just a best-effort thing...
       +        self.setText(len(self.text()) * " ")
       +        super().clear()
       +
        
        class TaskThread(QThread):
            '''Thread that runs background tasks.  Callbacks are guaranteed