Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initialize object caches in a safer manner #14333

Merged
merged 10 commits into from
Nov 7, 2022
101 changes: 37 additions & 64 deletions source/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,85 +419,55 @@ def _handleNVDAModuleCleanupBeforeGUIExit():
brailleViewer.destroyBrailleViewer()


def _pollForForegroundHWND() -> int:
"""
@note: The foreground window should usually be fetched on the first try,
however it may take longer if Windows is taking a long time changing window focus.
Times out after 20 seconds (MAX_WAIT_TIME_SECS).
After timing out, NVDA will give up trying to start and exit.
"""
import ui
from utils.blockUntilConditionMet import blockUntilConditionMet
import winUser

# winUser.getForegroundWindow may return NULL in certain circumstances,
# such as when a window is losing activation.
# This should not remain the case for an extended period of time.
# If NVDA is taking longer than expected to fetch the foreground window, perform a warning.
# We must wait a long time after this warning to
# allow for braille / speech to be understood before exiting.
# Unfortunately we cannot block with a dialog as NVDA cannot read dialogs yet.
WARN_AFTER_SECS = 5
MAX_WAIT_TIME_SECS = 20

success, foregroundHWND = blockUntilConditionMet(
getValue=winUser.getForegroundWindow,
giveUpAfterSeconds=WARN_AFTER_SECS,
)
if success:
return foregroundHWND

exitAfterWarningSecs = MAX_WAIT_TIME_SECS - WARN_AFTER_SECS
ui.message(_(
# Translators: Message when NVDA is having an issue starting up
"NVDA is failing to fetch the foreground window. "
"If this continues, NVDA will quit starting in %d seconds." % exitAfterWarningSecs
))

success, foregroundHWND = blockUntilConditionMet(
getValue=winUser.getForegroundWindow,
giveUpAfterSeconds=exitAfterWarningSecs,
)
if success:
return foregroundHWND
log.critical("NVDA could not fetch the foreground window. Exiting NVDA.")
# Raising exception here causes core.main to exit and NVDA to fail to start
raise NVDANotInitializedError("Could not fetch foreground window")


def _initializeObjectCaches():
"""
Caches the desktop object.
This may make information from the desktop window available on the lock screen,
however no known exploit is known for this.
2023.1 plans to ensure the desktopObject is available only when signed-in.

Also initializes other object caches to the foreground window.
Previously the object that was cached was the desktopObject,
however this may leak secure information to the lock screen.
The foreground window is set as the object cache,
as the foreground window would already be accessible on the lock screen (e.g. Magnifier).
It also is more intuitive that NVDA focuses the foreground window,
as opposed to the desktop object.

@note: The foreground window should usually be fetched on the first try,
however it may take longer if Windows is taking a long time changing window focus.
Times out after 20 seconds (MAX_WAIT_TIME_SECS in _pollForForegroundHWND).
After timing out, NVDA will give up trying to start and exit.
The desktop object must be used, as setting the object caches has side effects,
such as focus events.
Side effects from events generated while setting these objects may require NVDA to be finished initializing.
E.G. An app module for a lockScreen window.
The desktop object is an NVDA object without event handlers associated with it.
"""
import api
import NVDAObjects
import winUser

desktopObject = NVDAObjects.window.Window(windowHandle=winUser.getDesktopWindow())
api.setDesktopObject(desktopObject)
api.setForegroundObject(desktopObject)
api.setFocusObject(desktopObject)
api.setNavigatorObject(desktopObject)
api.setMouseObject(desktopObject)

foregroundHWND = _pollForForegroundHWND()
foregroundObject = NVDAObjects.window.Window(windowHandle=foregroundHWND)
api.setForegroundObject(foregroundObject)
api.setFocusObject(foregroundObject)
api.setNavigatorObject(foregroundObject)
api.setMouseObject(foregroundObject)

class _TrackNVDAInitialization:
"""
During NVDA initialization,
core._initializeObjectCaches needs to cache the desktop object,
regardless of lock state.
Security checks may cause the desktop object to not be set if NVDA starts on the lock screen.
As such, during initialization, NVDA should behave as if Windows is unlocked,
i.e. winAPI.sessionTracking.isWindowsLocked should return False.

TODO: move to NVDAState module
"""

_isNVDAInitialized = False
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
"""When False, isWindowsLocked is forced to return False.
"""

@staticmethod
def markInitializationComplete():
assert not _TrackNVDAInitialization._isNVDAInitialized
_TrackNVDAInitialization._isNVDAInitialized = True

@staticmethod
def isInitializationComplete() -> bool:
return _TrackNVDAInitialization._isNVDAInitialized


def main():
Expand Down Expand Up @@ -861,6 +831,9 @@ def run(self):
else:
log.debug("initializing updateCheck")
updateCheck.initialize()

_TrackNVDAInitialization.markInitializationComplete()

log.info("NVDA initialized")

# Queue the firing of the postNVDAStartup notification.
Expand Down
8 changes: 8 additions & 0 deletions source/winAPI/sessionTracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,14 @@ def isWindowsLocked() -> bool:
Checks if the Window lockscreen is active.
Not to be confused with the Windows sign-in screen, a secure screen.
"""
from core import _TrackNVDAInitialization
if not _TrackNVDAInitialization.isInitializationComplete():
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
# During NVDA initialization,
# core._initializeObjectCaches needs to cache the desktop object,
# regardless of lock state.
# Security checks may cause the desktop object to not be set if NVDA starts on the lock screen.
# As such, during initialization, NVDA should behave as if Windows is unlocked.
return False
if _isSecureDesktop():
# If this is the Secure Desktop,
# we are in secure mode and on a secure screen,
Expand Down