]> git.ipfire.org Git - thirdparty/openvpn.git/commitdiff
Add MSI custom action for reliable Windows 10 detection
authorSimon Rozman <simon@rozman.si>
Tue, 16 Oct 2018 10:26:26 +0000 (12:26 +0200)
committerGert Doering <gert@greenie.muc.de>
Thu, 17 Jan 2019 16:25:23 +0000 (17:25 +0100)
This patch introduces a `FindSystemInfo()` MSI custom action to reliably
detect Windows 10. The MSI built-in properties for Windows version
detection depend on bootstrapper's manifest. We could provide our own
Windows 10 compatible EXE bootstrapper, but that would cover the
Windows 10 detection in the `InstallUISequence` only. The
`InstallExecuteSequence` is launched by msiexec.exe which we cannot
tamper with would still report `VersionNT` as Windows 8 (603).
Acked-by: Jon Kunkee <jkunkee@microsoft.com>
Message-Id: <20181016102627.18676-4-simon@rozman.si>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg17763.html

Signed-off-by: Gert Doering <gert@greenie.muc.de>
src/openvpnmsica/Makefile.am
src/openvpnmsica/openvpnmsica.c
src/openvpnmsica/openvpnmsica.h

index d46170b4540f8169dc3ecc27768a2d5d39c1e84e..ecca74bcfac8f1ba9846d715f7856efe7bf519a6 100644 (file)
@@ -41,7 +41,7 @@ libopenvpnmsica_la_CFLAGS = \
        -municode -D_UNICODE \
        -UNTDDI_VERSION -U_WIN32_WINNT \
        -D_WIN32_WINNT=_WIN32_WINNT_VISTA
-libopenvpnmsica_la_LDFLAGS = -ladvapi32 -lole32 -lmsi -lsetupapi -lshlwapi -no-undefined -avoid-version
+libopenvpnmsica_la_LDFLAGS = -ladvapi32 -lole32 -lmsi -lsetupapi -lshlwapi -lversion -no-undefined -avoid-version
 endif
 
 libopenvpnmsica_la_SOURCES = \
index 3b90ce057e303a10b6dc8c8ef3e9e77dea8a8266..d1642d6a96cb720fc3ac37a37f0622dc42c5bc1c 100644 (file)
 #include <memory.h>
 #include <msiquery.h>
 #include <shlwapi.h>
-#ifdef _MSC_VER
-#pragma comment(lib, "shlwapi.lib")
-#endif
 #include <stdbool.h>
 #include <stdlib.h>
 #include <tchar.h>
 
+#ifdef _MSC_VER
+#pragma comment(lib, "shlwapi.lib")
+#pragma comment(lib, "version.lib")
+#endif
+
 
 /**
  * Local constants
@@ -119,7 +121,7 @@ openvpnmsica_setup_sequence_filename(
     {
         size_t len_action_name_z = _tcslen(openvpnmsica_cleanup_action_seqs[i].szName) + 1;
         TCHAR *szPropertyEx = (TCHAR*)malloc((len_property_name + len_action_name_z) * sizeof(TCHAR));
-        memcpy(szPropertyEx                    , szProperty                         , len_property_name * sizeof(TCHAR));
+        memcpy(szPropertyEx                    , szProperty                                , len_property_name * sizeof(TCHAR));
         memcpy(szPropertyEx + len_property_name, openvpnmsica_cleanup_action_seqs[i].szName, len_action_name_z * sizeof(TCHAR));
         _stprintf_s(
             szFilenameEx, _countof(szFilenameEx),
@@ -142,6 +144,120 @@ openvpnmsica_setup_sequence_filename(
 }
 
 
+UINT __stdcall
+FindSystemInfo(_In_ MSIHANDLE hInstall)
+{
+#ifdef _MSC_VER
+#pragma comment(linker, DLLEXP_EXPORT)
+#endif
+
+#ifdef _DEBUG
+    MessageBox(NULL, TEXT("Attach debugger!"), TEXT(__FUNCTION__) TEXT(" v")  TEXT(PACKAGE_VERSION), MB_OK);
+#endif
+
+    UINT uiResult;
+    BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));
+
+    /* Set MSI session handle in TLS. */
+    struct openvpnmsica_tls_data *s = (struct openvpnmsica_tls_data *)TlsGetValue(openvpnmsica_tlsidx_session);
+    s->hInstall = hInstall;
+
+    // Get Windows version.
+    OSVERSIONINFOEX ver_info = { .dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX) };
+    if (!GetVersionEx((LPOSVERSIONINFO)&ver_info)) {
+        uiResult = GetLastError();
+        msg(M_NONFATAL | M_ERRNO, "%s: GetVersionEx() failed", __FUNCTION__);
+        goto cleanup_CoInitialize;
+    }
+
+    // The Windows version is usually spoofed, check using RtlGetVersion().
+    TCHAR szDllPath[0x1000];
+    ExpandEnvironmentStrings(TEXT("%SystemRoot%\\System32\\ntdll.dll"), szDllPath,
+#ifdef UNICODE
+        _countof(szDllPath)
+#else
+        _countof(szDllPath) - 1
+#endif
+    );
+    HMODULE hNtDllModule = LoadLibrary(szDllPath);
+    if (hNtDllModule)
+    {
+        typedef NTSTATUS (WINAPI* fnRtlGetVersion)(PRTL_OSVERSIONINFOW);
+        fnRtlGetVersion RtlGetVersion = (fnRtlGetVersion)GetProcAddress(hNtDllModule, "RtlGetVersion");
+        if (RtlGetVersion)
+        {
+            RTL_OSVERSIONINFOW rtl_ver_info = { .dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOW) };
+            if (RtlGetVersion(&rtl_ver_info) == 0)
+                if (
+                    rtl_ver_info.dwMajorVersion >  ver_info.dwMajorVersion ||
+                    rtl_ver_info.dwMajorVersion == ver_info.dwMajorVersion && rtl_ver_info.dwMinorVersion >  ver_info.dwMinorVersion ||
+                    rtl_ver_info.dwMajorVersion == ver_info.dwMajorVersion && rtl_ver_info.dwMinorVersion == ver_info.dwMinorVersion && rtl_ver_info.dwBuildNumber > ver_info.dwBuildNumber)
+                {
+                    // We got RtlGetVersion() and it reported newer version than GetVersionEx().
+                    ver_info.dwMajorVersion = rtl_ver_info.dwMajorVersion;
+                    ver_info.dwMinorVersion = rtl_ver_info.dwMinorVersion;
+                    ver_info.dwBuildNumber  = rtl_ver_info.dwBuildNumber;
+                    ver_info.dwPlatformId   = rtl_ver_info.dwPlatformId;
+                }
+        }
+
+        FreeLibrary(hNtDllModule);
+    }
+
+    // We don't trust RtlGetVersion() either. Check the version resource of kernel32.dll.
+    ExpandEnvironmentStrings(TEXT("%SystemRoot%\\System32\\kernel32.dll"), szDllPath,
+#ifdef UNICODE
+        _countof(szDllPath)
+#else
+        _countof(szDllPath)-1
+#endif
+    );
+
+    DWORD dwHandle;
+    DWORD dwVerInfoSize = GetFileVersionInfoSize(szDllPath, &dwHandle);
+    if (dwVerInfoSize)
+    {
+        LPVOID pVersionInfo = malloc(dwVerInfoSize);
+        if (pVersionInfo)
+        {
+            // Read version info.
+            if (GetFileVersionInfo(szDllPath, dwHandle, dwVerInfoSize, pVersionInfo))
+            {
+                // Get the value for the root block.
+                UINT uiSize = 0;
+                VS_FIXEDFILEINFO *pVSFixedFileInfo = NULL;
+                if (VerQueryValue(pVersionInfo, TEXT("\\"), &pVSFixedFileInfo, &uiSize) && uiSize && pVSFixedFileInfo)
+                    if (HIWORD(pVSFixedFileInfo->dwProductVersionMS) >  ver_info.dwMajorVersion ||
+                        HIWORD(pVSFixedFileInfo->dwProductVersionMS) == ver_info.dwMajorVersion && LOWORD(pVSFixedFileInfo->dwProductVersionMS) >  ver_info.dwMinorVersion ||
+                        HIWORD(pVSFixedFileInfo->dwProductVersionMS) == ver_info.dwMajorVersion && LOWORD(pVSFixedFileInfo->dwProductVersionMS) == ver_info.dwMinorVersion && HIWORD(pVSFixedFileInfo->dwProductVersionLS) > ver_info.dwBuildNumber)
+                    {
+                        // We got kernel32.dll version and it is newer.
+                        ver_info.dwMajorVersion = HIWORD(pVSFixedFileInfo->dwProductVersionMS);
+                        ver_info.dwMinorVersion = LOWORD(pVSFixedFileInfo->dwProductVersionMS);
+                        ver_info.dwBuildNumber  = HIWORD(pVSFixedFileInfo->dwProductVersionLS);
+                    }
+            }
+
+            free(pVersionInfo);
+        }
+    }
+
+    uiResult = MsiSetProperty(hInstall, TEXT("DriverCertification"), ver_info.dwMajorVersion >= 10 ? ver_info.wProductType > VER_NT_WORKSTATION ? TEXT("whql") : TEXT("attsgn") : TEXT(""));
+    if (uiResult != ERROR_SUCCESS)
+    {
+        SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"TAPINTERFACES\") failed", __FUNCTION__);
+        goto cleanup_CoInitialize;
+    }
+
+    uiResult = ERROR_SUCCESS;
+
+cleanup_CoInitialize:
+    if (bIsCoInitialized) CoUninitialize();
+    return uiResult;
+}
+
+
 UINT __stdcall
 FindTAPInterfaces(_In_ MSIHANDLE hInstall)
 {
index bb8e28ec456d440891b34f9dcdb173a9a5a151f3..da145062e77cd96aa3901b157f1f8b2d3bc2b47c 100644 (file)
@@ -63,6 +63,21 @@ extern "C" {
 #endif
 
 
+/**
+ * Determines Windows information:
+ * - Sets `DriverCertification` MSI property to "", "attsgn" or "whql"
+ *   according to the driver certification required by the running version of
+ *   Windows.
+ *
+ * @param hInstall      Handle to the installation provided to the DLL custom action
+ *
+ * @return ERROR_SUCCESS on success; An error code otherwise
+ *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx
+ */
+DLLEXP_DECL UINT __stdcall
+FindSystemInfo(_In_ MSIHANDLE hInstall);
+
+
 /**
  * Find existing TAP interfaces and set TAPINTERFACES property with semicolon delimited list
  * of installed TAP interface GUIDs.