Skip to content

Commit 3ce266b

Browse files
lukaszgo1seanbudd
andauthored
Ensure that NVDA can get file version information for binaries in system32 on 64-bit versions of Windows (#12943)
discussion in #12852 Summary of the issue: NVDA can retrieve information for a binary file such as application name and version from which given appModule has been created. While they're not shown to the user directly they can be inspected as part of the developer info - also NVDA relies on them for various stuff internally. Description of how this pull request fixes the issue: For a duration of retrieving file info for a binary placed in system32 and when running on a version of Windows which have both system32 and SysWOW64 WOW64 redirection is disabled. While at it I've removed deprecated shlobj.SHGetFolderPath and replaced it with SHGetKnownFolderPath and removed unused function from the config module ((while not strictly related it relied on SHGetFolderPath). Testing strategy: On 64-bit version of Windows installed self signed build from this branch, enabled service debug, started an application with the admin privileges, inspected developer info for the UAC screen and made sure that for consent.exe file version and file name can be retrieved. Made sure that this code is not executed on 32-bit version of Windows and that file version info can still be retrieved there. Co-authored-by: buddsean <sean@nvaccess.org>
1 parent 8b17f66 commit 3ce266b

6 files changed

Lines changed: 126 additions & 44 deletions

File tree

source/config/__init__.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ def getInstalledUserConfigPath():
102102
configInLocalAppData = bool(winreg.QueryValueEx(k, CONFIG_IN_LOCAL_APPDATA_SUBKEY)[0])
103103
except WindowsError:
104104
configInLocalAppData=False
105-
configParent=shlobj.SHGetFolderPath(0, shlobj.CSIDL_LOCAL_APPDATA if configInLocalAppData else shlobj.CSIDL_APPDATA)
105+
configParent = shlobj.SHGetKnownFolderPath(
106+
shlobj.FOLDERID.LocalAppData.value if configInLocalAppData else shlobj.FOLDERID.RoamingAppData.value
107+
)
106108
try:
107109
return os.path.join(configParent, "nvda")
108110
except WindowsError:
@@ -126,14 +128,6 @@ def getUserDefaultConfigPath(useInstalledPathIfExists=False):
126128
return installedUserConfigPath
127129
return os.path.join(globalVars.appDir, 'userConfig')
128130

129-
def getSystemConfigPath():
130-
if isInstalledCopy():
131-
try:
132-
return os.path.join(shlobj.SHGetFolderPath(0, shlobj.CSIDL_COMMON_APPDATA), "nvda")
133-
except WindowsError:
134-
pass
135-
return None
136-
137131

138132
SCRATCH_PAD_ONLY_DIRS = (
139133
'appModules',

source/fileUtils.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
#fileUtils.py
2-
#A part of NonVisual Desktop Access (NVDA)
3-
#Copyright (C) 2017-2019 NV Access Limited, Bram Duvigneau
4-
#This file is covered by the GNU General Public License.
5-
#See the file COPYING for more details.
1+
# A part of NonVisual Desktop Access (NVDA)
2+
# Copyright (C) 2017-2021 NV Access Limited, Bram Duvigneau, Łukasz Golonka
3+
# This file is covered by the GNU General Public License.
4+
# See the file COPYING for more details.
65

76
import os
87
import ctypes
@@ -13,6 +12,10 @@
1312
from logHandler import log
1413
from six import text_type
1514
import winKernel
15+
import shlobj
16+
from functools import wraps
17+
import systemUtils
18+
1619

1720
@contextmanager
1821
def FaultTolerantFile(name):
@@ -40,6 +43,32 @@ def FaultTolerantFile(name):
4043
f.close()
4144
winKernel.moveFileEx(f.name, name, winKernel.MOVEFILE_REPLACE_EXISTING)
4245

46+
47+
def _suspendWow64RedirectionForFileInfoRetrieval(func):
48+
"""
49+
This decorator checks if the file provided as a `filePath`
50+
is placed in a system32 directory, and if for the current system system32
51+
redirects 32-bit processes such as NVDA to a different syswow64 directory
52+
disables redirection for the duration of the function call.
53+
This is necessary when fetching file version info since NVDA is a 32-bit application
54+
and without redirection disabled we would either access a wrong file or not be able to access it at all.
55+
"""
56+
@wraps(func)
57+
def funcWrapper(filePath, *attributes):
58+
nativeSys32 = shlobj.SHGetKnownFolderPath(shlobj.FOLDERID.System.value)
59+
if (
60+
systemUtils.hasSyswow64Dir()
61+
# `os.path.commonpath` is necessary to perform case-insensitive comparisons
62+
and os.path.commonpath([nativeSys32]) == os.path.commonpath([nativeSys32, filePath])
63+
):
64+
with winKernel.suspendWow64Redirection():
65+
return func(filePath, *attributes)
66+
else:
67+
return func(filePath, *attributes)
68+
return funcWrapper
69+
70+
71+
@_suspendWow64RedirectionForFileInfoRetrieval
4372
def getFileVersionInfo(name, *attributes):
4473
"""Gets the specified file version info attributes from the provided file."""
4574
if not isinstance(name, text_type):

source/shlobj.py

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,56 @@
1-
# -*- coding: UTF-8 -*-
2-
#shlobj.py
3-
#A part of NonVisual Desktop Access (NVDA)
4-
#Copyright (C) 2006-2017 NV Access Limited, Babbage B.V.
5-
#This file is covered by the GNU General Public License.
6-
#See the file COPYING for more details.
1+
# A part of NonVisual Desktop Access (NVDA)
2+
# Copyright (C) 2009-2021 NV Access Limited, Babbage B.V., Łukasz Golonka
3+
# This file is covered by the GNU General Public License.
4+
# See the file COPYING for more details.
75

8-
"""
9-
This module wraps the SHGetFolderPath function in shell32.dll and defines the necessary contstants.
10-
CSIDL (constant special item ID list) values provide a unique system-independent way to
6+
r"""
7+
This module wraps the `SHGetKnownFolderPath` function in shell32.dll and defines the necessary contstants.
8+
Known folder ids provide a unique system-independent way to
119
identify special folders used frequently by applications, but which may not have the same name
1210
or location on any given system. For example, the system folder may be "C:\Windows" on one system
13-
and "C:\Winnt" on another. The CSIDL system is used to be compatible with Windows XP.
11+
and "C:\Winnt" on another.
1412
"""
1513

16-
from ctypes import *
17-
from ctypes.wintypes import *
14+
import comtypes
15+
import ctypes
16+
import enum
17+
import functools
18+
import typing
1819

19-
shell32 = windll.shell32
2020

21-
MAX_PATH = 260
21+
class FOLDERID(str, enum.Enum):
22+
"""Contains guids of known folders from Knownfolders.h. Full list is availabe at:
23+
https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid"""
24+
#: The file system directory that serves as a common repository for application-specific data.
25+
#: A typical path is C:\Documents and Settings\username\Application Data.
26+
RoamingAppData = "{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}"
27+
#: The file system directory that serves as a data repository for local (nonroaming) applications.
28+
#: A typical path is C:\Documents and Settings\username\Local Settings\Application Data.
29+
LocalAppData = "{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}"
30+
#: The file system directory that contains application data for all users.
31+
#: A typical path is C:\Documents and Settings\All Users\Application Data.
32+
#: This folder is used for application data that is not user specific.
33+
ProgramData = "{62AB5D82-FDC1-4DC3-A9DD-070D1D495D97}"
34+
# The Windows System folder.
35+
# A typical path is C:\Windows\System32.
36+
System = "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}"
37+
SystemX86 = "{D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27}"
2238

23-
#: The file system directory that serves as a common repository for application-specific data.
24-
#: A typical path is C:\Documents and Settings\username\Application Data.
25-
CSIDL_APPDATA = 0x001a
26-
#: The file system directory that serves as a data repository for local (nonroaming) applications.
27-
#: A typical path is C:\Documents and Settings\username\Local Settings\Application Data.
28-
CSIDL_LOCAL_APPDATA = 0x001c
29-
#: The file system directory that contains application data for all users.
30-
#: A typical path is C:\Documents and Settings\All Users\Application Data.
31-
#: This folder is used for application data that is not user specific.
32-
CSIDL_COMMON_APPDATA = 0x0023
3339

34-
def SHGetFolderPath(owner, folder, token=0, flags=0):
35-
path = create_unicode_buffer(MAX_PATH)
36-
# Note  As of Windows Vista, this function is merely a wrapper for SHGetKnownFolderPath
37-
if shell32.SHGetFolderPathW(owner, folder, token, flags, byref(path)) != 0:
38-
raise WinError()
39-
return path.value
40+
@functools.lru_cache(maxsize=128)
41+
def SHGetKnownFolderPath(folderGuid: str, dwFlags: int = 0, hToken: typing.Optional[int] = None) -> str:
42+
"""Wrapper for `SHGetKnownFolderPath` which caches the results
43+
to avoid calling the win32 function unnecessarily."""
44+
guid = comtypes.GUID(folderGuid)
45+
pathPointer = ctypes.c_wchar_p()
46+
res = ctypes.windll.shell32.SHGetKnownFolderPath(
47+
comtypes.byref(guid),
48+
dwFlags,
49+
hToken,
50+
ctypes.byref(pathPointer)
51+
)
52+
if res != 0:
53+
raise RuntimeError(f"SHGetKnownFolderPath failed with erro code {res}")
54+
path = pathPointer.value
55+
ctypes.windll.ole32.CoTaskMemFree(pathPointer)
56+
return path

source/systemUtils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@
1010
import shellapi
1111
import winUser
1212
import os
13+
import functools
14+
import shlobj
15+
16+
17+
@functools.lru_cache(maxsize=1)
18+
def hasSyswow64Dir() -> bool:
19+
"""Returns `True` if the current system has separate system32 directories for 32-bit processes."""
20+
nativeSys32 = shlobj.SHGetKnownFolderPath(shlobj.FOLDERID.System.value)
21+
Syswow64Sys32 = shlobj.SHGetKnownFolderPath(shlobj.FOLDERID.SystemX86.value)
22+
return nativeSys32 != Syswow64Sys32
23+
1324

1425
def openUserConfigurationDirectory():
1526
"""Opens directory containing config files for the current user"""

source/winKernel.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,36 @@ def GetSystemPowerStatus(sps):
150150
def getThreadLocale():
151151
return kernel32.GetThreadLocale()
152152

153+
154+
ERROR_INVALID_FUNCTION = 0x1
155+
156+
157+
@contextlib.contextmanager
158+
def suspendWow64Redirection():
159+
"""Context manager which disables Wow64 redirection for a section of code and re-enables it afterwards"""
160+
oldValue = LPVOID()
161+
res = kernel32.Wow64DisableWow64FsRedirection(byref(oldValue))
162+
if res == 0:
163+
# Disabling redirection failed.
164+
# This can occur if we're running on 32-bit Windows (no Wow64 redirection)
165+
# or as a 64-bit process on 64-bit Windows (Wow64 redirection not applicable)
166+
# In this case failure is expected and there is no reason to raise an exception.
167+
# Inspect last error code to determine reason for the failure.
168+
errorCode = kernel32.GetLastError()
169+
if errorCode == ERROR_INVALID_FUNCTION: # Redirection not supported or not applicable.
170+
redirectionDisabled = False
171+
else:
172+
raise WinError(errorCode)
173+
else:
174+
redirectionDisabled = True
175+
try:
176+
yield
177+
finally:
178+
if redirectionDisabled:
179+
if kernel32.Wow64RevertWow64FsRedirection(oldValue) == 0:
180+
raise WinError()
181+
182+
153183
class SYSTEMTIME(ctypes.Structure):
154184
_fields_ = (
155185
("wYear", WORD),

user_docs/en/changes.t2t

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ What's New in NVDA
1919
- Clipboard manager pane should no longer incorrectly steal focus when opening some Office programs. (#12736)
2020
- On a system where the user has chosen to swap the primary mouse button from the left to the right, NVDA will no longer accidentally bring up a context menu instead of activating an item, in applications such as web browsers. (#12642)
2121
- When moving the review cursor past the end of text controls, such as in Microsoft Word with UI Automation, "bottom" is correctly reported in more situations. (#12808)
22+
- NVDA can report the application name and version for binaries placed in system32 when running under 64-bit version of Windows. (#12943)
2223
-
2324

2425
== Changes for Developers ==

0 commit comments

Comments
 (0)