1 /* 2 KeePass Password Safe - The Open-Source Password Manager 3 Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de> 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 2 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program; if not, write to the Free Software 17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 */ 19 20 using System; 21 using System.Collections.Generic; 22 using System.Diagnostics; 23 using System.Drawing; 24 using System.Text; 25 using System.Threading; 26 using System.Windows.Forms; 27 28 using KeePass.App.Configuration; 29 using KeePass.Native; 30 using KeePass.Resources; 31 using KeePass.Util; 32 33 using KeePassLib; 34 using KeePassLib.Cryptography; 35 using KeePassLib.Utility; 36 37 // using KpLibNativeMethods = KeePassLib.Native.NativeMethods; 38 39 namespace KeePass.UI 40 { UIFormConstructor(object objParam)41 public delegate Form UIFormConstructor(object objParam); UIFormResultBuilder(Form f)42 public delegate object UIFormResultBuilder(Form f); 43 44 public sealed class ProtectedDialog 45 { 46 private UIFormConstructor m_fnConstruct; 47 private UIFormResultBuilder m_fnResultBuilder; 48 49 private enum SecureThreadState 50 { 51 None = 0, 52 ShowingDialog, 53 Terminated 54 } 55 56 private sealed class SecureThreadInfo 57 { 58 public List<Bitmap> BackgroundBitmaps = new List<Bitmap>(); 59 public IntPtr ThreadDesktop = IntPtr.Zero; 60 61 public object FormConstructParam = null; 62 63 public DialogResult DialogResult = DialogResult.None; 64 public object ResultObject = null; 65 66 public SecureThreadState State = SecureThreadState.None; 67 } 68 ProtectedDialog(UIFormConstructor fnConstruct, UIFormResultBuilder fnResultBuilder)69 public ProtectedDialog(UIFormConstructor fnConstruct, 70 UIFormResultBuilder fnResultBuilder) 71 { 72 if(fnConstruct == null) { Debug.Assert(false); throw new ArgumentNullException("fnConstruct"); } 73 if(fnResultBuilder == null) { Debug.Assert(false); throw new ArgumentNullException("fnResultBuilder"); } 74 75 m_fnConstruct = fnConstruct; 76 m_fnResultBuilder = fnResultBuilder; 77 } 78 ShowDialog(out object objResult, object objConstructParam)79 public DialogResult ShowDialog(out object objResult, object objConstructParam) 80 { 81 objResult = null; 82 83 ProcessMessagesEx(); 84 85 // Creating a window on the new desktop spawns a CtfMon.exe child 86 // process by default. On Windows Vista, this process is terminated 87 // correctly when the desktop is closed. However, on Windows 7 it 88 // isn't terminated (probably a bug); creating multiple desktops 89 // accumulates CtfMon.exe child processes. 90 ChildProcessesSnapshot cpsCtfMons = new ChildProcessesSnapshot( 91 "CtfMon.exe"); 92 93 ClipboardEventChainBlocker ccb = new ClipboardEventChainBlocker(); 94 byte[] pbClipHash = ClipboardUtil.ComputeHash(); 95 96 SecureThreadInfo stp = new SecureThreadInfo(); 97 foreach(Screen sc in Screen.AllScreens) 98 { 99 Bitmap bmpBack = UIUtil.CreateScreenshot(sc); 100 if(bmpBack != null) UIUtil.DimImage(bmpBack); 101 stp.BackgroundBitmaps.Add(bmpBack); 102 } 103 104 DialogResult dr = DialogResult.None; 105 try 106 { 107 uint uOrgThreadId = NativeMethods.GetCurrentThreadId(); 108 IntPtr pOrgDesktop = NativeMethods.GetThreadDesktop(uOrgThreadId); 109 110 string strName = "D" + Convert.ToBase64String( 111 CryptoRandom.Instance.GetRandomBytes(16)); 112 strName = StrUtil.AlphaNumericOnly(strName); 113 if(strName.Length > 15) strName = strName.Substring(0, 15); 114 115 NativeMethods.DesktopFlags deskFlags = 116 (NativeMethods.DesktopFlags.CreateMenu | 117 NativeMethods.DesktopFlags.CreateWindow | 118 NativeMethods.DesktopFlags.ReadObjects | 119 NativeMethods.DesktopFlags.WriteObjects | 120 NativeMethods.DesktopFlags.SwitchDesktop); 121 122 IntPtr pNewDesktop = NativeMethods.CreateDesktop(strName, 123 null, IntPtr.Zero, 0, deskFlags, IntPtr.Zero); 124 if(pNewDesktop == IntPtr.Zero) 125 throw new InvalidOperationException(); 126 127 bool bNameSupported = NativeMethods.DesktopNameContains(pNewDesktop, 128 strName).GetValueOrDefault(false); 129 Debug.Assert(bNameSupported); 130 131 stp.ThreadDesktop = pNewDesktop; 132 stp.FormConstructParam = objConstructParam; 133 134 Thread th = new Thread(this.SecureDialogThread); 135 th.CurrentCulture = Thread.CurrentThread.CurrentCulture; 136 th.CurrentUICulture = Thread.CurrentThread.CurrentUICulture; 137 th.Start(stp); 138 139 SecureThreadState st = SecureThreadState.None; 140 while(st != SecureThreadState.Terminated) 141 { 142 th.Join(150); 143 144 lock(stp) { st = stp.State; } 145 146 if((st == SecureThreadState.ShowingDialog) && bNameSupported) 147 { 148 IntPtr hCurDesk = NativeMethods.OpenInputDesktop(0, 149 false, NativeMethods.DesktopFlags.ReadObjects); 150 if(hCurDesk == IntPtr.Zero) { Debug.Assert(false); continue; } 151 if(hCurDesk == pNewDesktop) 152 { 153 if(!NativeMethods.CloseDesktop(hCurDesk)) { Debug.Assert(false); } 154 continue; 155 } 156 bool? obOnSec = NativeMethods.DesktopNameContains(hCurDesk, strName); 157 if(!NativeMethods.CloseDesktop(hCurDesk)) { Debug.Assert(false); } 158 159 lock(stp) { st = stp.State; } // Update; might have changed 160 161 if(obOnSec.HasValue && !obOnSec.Value && 162 (st == SecureThreadState.ShowingDialog)) 163 HandleUnexpectedDesktopSwitch(pOrgDesktop, pNewDesktop, stp); 164 } 165 } 166 167 if(!NativeMethods.SwitchDesktop(pOrgDesktop)) { Debug.Assert(false); } 168 NativeMethods.SetThreadDesktop(pOrgDesktop); 169 170 th.Join(); // Ensure thread terminated before closing desktop 171 172 if(!NativeMethods.CloseDesktop(pNewDesktop)) { Debug.Assert(false); } 173 NativeMethods.CloseDesktop(pOrgDesktop); // Optional 174 175 dr = stp.DialogResult; 176 objResult = stp.ResultObject; 177 } 178 catch(Exception) { Debug.Assert(false); } 179 180 byte[] pbNewClipHash = ClipboardUtil.ComputeHash(); 181 if((pbClipHash != null) && (pbNewClipHash != null) && 182 !MemUtil.ArraysEqual(pbClipHash, pbNewClipHash)) 183 ClipboardUtil.Clear(); 184 ccb.Dispose(); 185 186 foreach(Bitmap bmpBack in stp.BackgroundBitmaps) 187 { 188 if(bmpBack != null) bmpBack.Dispose(); 189 } 190 stp.BackgroundBitmaps.Clear(); 191 192 cpsCtfMons.TerminateNewChildsAsync(4100); 193 194 // If something failed, show the dialog on the normal desktop 195 if(dr == DialogResult.None) 196 { 197 Form f = m_fnConstruct(objConstructParam); 198 dr = f.ShowDialog(); 199 objResult = m_fnResultBuilder(f); 200 UIUtil.DestroyForm(f); 201 } 202 203 return dr; 204 } 205 ProcessMessagesEx()206 private static void ProcessMessagesEx() 207 { 208 Application.DoEvents(); 209 Thread.Sleep(5); 210 Application.DoEvents(); 211 } 212 SecureDialogThread(object oParam)213 private void SecureDialogThread(object oParam) 214 { 215 SecureThreadInfo stp = (oParam as SecureThreadInfo); 216 if(stp == null) { Debug.Assert(false); return; } 217 218 List<BackgroundForm> lBackForms = new List<BackgroundForm>(); 219 BackgroundForm formBackPrimary = null; 220 // bool bLangBar = false; 221 222 try 223 { 224 if(!NativeMethods.SetThreadDesktop(stp.ThreadDesktop)) 225 { 226 Debug.Assert(false); 227 return; 228 } 229 230 ProcessMessagesEx(); 231 232 // Test whether we're really on the secure desktop 233 if(NativeMethods.GetThreadDesktop(NativeMethods.GetCurrentThreadId()) != 234 stp.ThreadDesktop) 235 { 236 Debug.Assert(false); 237 return; 238 } 239 240 // Disabling the IME was not required, because we terminate 241 // CtfMon.exe child processes manually. However, since Sept. 2019, 242 // there is an IME bug resulting in a black screen and/or an 243 // IME/CTF process with high CPU load; 244 // https://sourceforge.net/p/keepass/bugs/1881/ 245 // https://keepass.info/help/kb/sec_desk.html#ime 246 try 247 { 248 ulong uif = Program.Config.UI.UIFlags; 249 if((uif & (ulong)AceUIFlags.SecureDesktopIme) == 0) 250 NativeMethods.ImmDisableIME(0); // Always false on 2000/XP 251 } 252 catch(Exception) { Debug.Assert(!WinUtil.IsAtLeastWindows2000); } 253 254 ProcessMessagesEx(); 255 256 Screen[] vScreens = Screen.AllScreens; 257 Screen scPrimary = Screen.PrimaryScreen; 258 Debug.Assert(vScreens.Length == stp.BackgroundBitmaps.Count); 259 int sMin = Math.Min(vScreens.Length, stp.BackgroundBitmaps.Count); 260 for(int i = sMin - 1; i >= 0; --i) 261 { 262 Bitmap bmpBack = stp.BackgroundBitmaps[i]; 263 if(bmpBack == null) continue; 264 Debug.Assert(bmpBack.Size == vScreens[i].Bounds.Size); 265 266 BackgroundForm formBack = new BackgroundForm(bmpBack, 267 vScreens[i]); 268 269 lBackForms.Add(formBack); 270 if(vScreens[i].Equals(scPrimary)) 271 formBackPrimary = formBack; 272 273 formBack.Show(); 274 } 275 if(formBackPrimary == null) 276 { 277 Debug.Assert(false); 278 if(lBackForms.Count > 0) 279 formBackPrimary = lBackForms[lBackForms.Count - 1]; 280 } 281 282 ProcessMessagesEx(); 283 284 if(!NativeMethods.SwitchDesktop(stp.ThreadDesktop)) { Debug.Assert(false); } 285 286 ProcessMessagesEx(); 287 288 Form f = m_fnConstruct(stp.FormConstructParam); 289 if(f == null) { Debug.Assert(false); return; } 290 291 if(Program.Config.UI.SecureDesktopPlaySound) 292 UIUtil.PlayUacSound(); 293 294 // bLangBar = ShowLangBar(true); 295 296 lock(stp) { stp.State = SecureThreadState.ShowingDialog; } 297 stp.DialogResult = f.ShowDialog(formBackPrimary); 298 stp.ResultObject = m_fnResultBuilder(f); 299 300 UIUtil.DestroyForm(f); 301 } 302 catch(Exception) { Debug.Assert(false); } 303 finally 304 { 305 // if(bLangBar) ShowLangBar(false); 306 307 foreach(BackgroundForm formBack in lBackForms) 308 { 309 try 310 { 311 formBack.Close(); 312 UIUtil.DestroyForm(formBack); 313 } 314 catch(Exception) { Debug.Assert(false); } 315 } 316 317 lock(stp) { stp.State = SecureThreadState.Terminated; } 318 } 319 } 320 321 /* private static bool ShowLangBar(bool bShow) 322 { 323 try 324 { 325 return KpLibNativeMethods.TfShowLangBar(bShow ? 326 KpLibNativeMethods.TF_SFT_SHOWNORMAL : 327 KpLibNativeMethods.TF_SFT_HIDDEN); 328 } 329 catch(Exception) { } 330 331 return false; 332 } */ 333 HandleUnexpectedDesktopSwitch(IntPtr pOrgDesktop, IntPtr pNewDesktop, SecureThreadInfo stp)334 private static void HandleUnexpectedDesktopSwitch(IntPtr pOrgDesktop, 335 IntPtr pNewDesktop, SecureThreadInfo stp) 336 { 337 NativeMethods.SwitchDesktop(pOrgDesktop); 338 NativeMethods.SetThreadDesktop(pOrgDesktop); 339 340 ProcessMessagesEx(); 341 342 // Do not use MessageService.ShowWarning, because 343 // it uses the top form's thread 344 MessageService.ExternalIncrementMessageCount(); 345 NativeMethods.MessageBoxFlags mbf = 346 (NativeMethods.MessageBoxFlags.MB_ICONWARNING | 347 NativeMethods.MessageBoxFlags.MB_TASKMODAL | 348 NativeMethods.MessageBoxFlags.MB_SETFOREGROUND | 349 NativeMethods.MessageBoxFlags.MB_TOPMOST); 350 if(StrUtil.RightToLeft) 351 mbf |= (NativeMethods.MessageBoxFlags.MB_RTLREADING | 352 NativeMethods.MessageBoxFlags.MB_RIGHT); 353 NativeMethods.MessageBox(IntPtr.Zero, KPRes.SecDeskOtherSwitched + 354 MessageService.NewParagraph + KPRes.SecDeskSwitchBack, 355 PwDefs.ShortProductName, mbf); 356 MessageService.ExternalDecrementMessageCount(); 357 358 SecureThreadState st; 359 lock(stp) { st = stp.State; } 360 if(st != SecureThreadState.Terminated) 361 { 362 NativeMethods.SwitchDesktop(pNewDesktop); 363 ProcessMessagesEx(); 364 } 365 } 366 367 /* private static void BlockPrintScreen(Form f, bool bBlock) 368 { 369 if(f == null) { Debug.Assert(false); return; } 370 371 try 372 { 373 if(bBlock) 374 { 375 NativeMethods.RegisterHotKey(f.Handle, NativeMethods.IDHOT_SNAPDESKTOP, 376 0, NativeMethods.VK_SNAPSHOT); 377 NativeMethods.RegisterHotKey(f.Handle, NativeMethods.IDHOT_SNAPWINDOW, 378 NativeMethods.MOD_ALT, NativeMethods.VK_SNAPSHOT); 379 } 380 else 381 { 382 NativeMethods.UnregisterHotKey(f.Handle, NativeMethods.IDHOT_SNAPWINDOW); 383 NativeMethods.UnregisterHotKey(f.Handle, NativeMethods.IDHOT_SNAPDESKTOP); 384 } 385 } 386 catch(Exception) { Debug.Assert(false); } 387 } */ 388 } 389 } 390