Alex Gough | 93ec01c | 2025-04-11 04:52:42 | [diff] [blame] | 1 | # Security guidelines for HANDLEs on Windows |
| 2 | |
| 3 | TL;DR; |
| 4 | |
| 5 | * Use [ScopedHandle](https://source.chromium.org/chromium/chromium/src/+/main:base/win/scoped_handle.h) |
| 6 | whenever possible. |
| 7 | * If you have to use a raw HANDLE: |
| 8 | * Reject invalid values using [base::win::IsPseudoHandle()](https://source.chromium.org/chromium/chromium/src/+/main:base/win/windows_handle_util.h) |
| 9 | do not just check against INVALID_HANDLE_VALUE, |
| 10 | * Initialize and return nullptr on failure. |
| 11 | * Avoid storing pseudo handle values. |
| 12 | * Provide GetCurrentProcess() or GetCurrentThread() directly to Windows APIs. |
| 13 | |
| 14 | ## Discussion |
| 15 | |
| 16 | Windows HANDLEs are used to represent objects such as Events, Files, Processes |
| 17 | and Threads. HANDLEs are typedef’d as a pointer-sized type but Windows only uses |
| 18 | 32 bits (with sign extension) so that 32 and 64 bit processes can interoperate. |
| 19 | |
| 20 | Windows HANDLEs are generally returned by Windows APIs that create or open |
| 21 | objects, and must be closed once the object is no longer in use to conserve |
| 22 | system resources. HANDLEs to the same object might have different access rights |
| 23 | (for instance, a process handle might only allow waiting on exit, or might allow |
| 24 | full memory access). HANDLEs can also be duplicated, both within the process |
| 25 | that created them (to allow code with different lifetimes to refer to the same |
| 26 | underlying object), and to other processes. |
| 27 | |
| 28 | HANDLE has several quirks. Many Windows APIs take or return pseudo handle |
| 29 | values, such as the return from ::GetCurrentProcess() or ::GetCurrentThread(). |
| 30 | These pseudo handle values may overlap with return values from some handle |
| 31 | manipulation functions, and malicious code could send these placeholder values |
| 32 | from less privileged processes. |
| 33 | |
| 34 | In particular, GetCurrentProcess() == INVALID_HANDLE_VALUE. Code must be very |
| 35 | careful not to use these pseudo handle values accidentally. For instance, code |
| 36 | might intend to open and send a file handle to a renderer, but if the file |
| 37 | opening failed it might accidentally provide INVALID_HANDLE_VALUE as the source |
| 38 | handle to DuplicateHandle(), resulting in a handle to the current process being |
| 39 | sent to the renderer, which would be a serious security bug. |
| 40 | |
| 41 | In general it should not be necessary for Chromium code to directly manipulate |
| 42 | Windows HANDLEs, and instead code should use abstractions such as [base::File](https://source.chromium.org/chromium/chromium/src/+/main:base/files/file.h). |
| 43 | Chromium, via mojo, makes it easy to send appropriately wrapped objects to |
| 44 | children using [mojo_base](https://source.chromium.org/chromium/chromium/src/+/main:mojo/public/mojom/base/) wrappers. |
| 45 | |
| 46 | # Guidelines |
| 47 | |
| 48 | ## Use ScopedHandle whenever possible |
| 49 | |
| 50 | In Chromium, HANDLEs should always be owned by a [base::win::ScopedHandle](https://source.chromium.org/chromium/chromium/src/+/main:base/win/scoped_handle.h) |
| 51 | and their underlying HANDLE should only be accessed when a related Windows API |
| 52 | is called. This ensures that HANDLEs are not leaked and that ownership of the |
| 53 | underlying object in Chromium code is clear. ScopedHandle refuses to adopt or |
| 54 | represent pseudo handle values, and will not return them from its .get() |
| 55 | accessor. |
| 56 | |
| 57 | When duplicating a ScopedHandle’s underlying HANDLE always call |
| 58 | ScopedHandle::is_valid() before calling DuplicateHandle, and return an empty |
| 59 | ScopedHandle or a raw nullptr HANDLE if duplication fails. |
| 60 | |
| 61 | When adopting a HANDLE value into a base::win::ScopedHandle that another process |
| 62 | duplicated into the current process (e.g. a log file handle on a command line) |
| 63 | use [base::win::TakeHandleOfType()](https://source.chromium.org/chromium/chromium/src/+/main:base/win/scoped_handle.h) |
| 64 | to validate that the HANDLE is not a pseudo handle and points to a valid object |
| 65 | before adopting it. |
| 66 | |
| 67 | ## Using HANDLE in Chromium |
| 68 | |
| 69 | If you must manipulate HANDLEs directly, the following guidelines apply: |
| 70 | |
| 71 | Do not store pseudo handles, and instead simply call ::GetCurrentProcess() when |
| 72 | you need the pseudo handle value. If you need a real handle to a process (and |
| 73 | [base::Process](https://source.chromium.org/chromium/chromium/src/+/main:base/process/process.h) |
| 74 | does not provide what you need), you must duplicate to a real HANDLE before |
| 75 | adopting it. |
| 76 | |
| 77 | Use nullptr to represent uninitialized or invalid values. In code that manages |
| 78 | its own HANDLEs use nullptr to initialize all HANDLE variables and members and |
| 79 | return nullptr from any accessor that returns a raw HANDLE while the wrapper |
| 80 | object is invalid. Use [base::win::IsPseudoHandle()](https://source.chromium.org/chromium/chromium/src/+/main:base/win/windows_handle_util.h) to validate HANDLE values |
| 81 | provided to constructors (do not just check handle != INVALID_HANDLE_VALUE). |