1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include <driverblocklist.hxx>
13 #include <string_view>
15 #include <sal/log.hxx>
19 #if !defined WIN32_LEAN_AND_MEAN
20 #define WIN32_LEAN_AND_MEAN
25 namespace DriverBlocklist
27 static OperatingSystem
getOperatingSystem(std::string_view rString
)
31 else if (rString
== "10")
32 return DRIVER_OS_WINDOWS_10
;
33 else if (rString
== "windows")
34 return DRIVER_OS_WINDOWS_ALL
;
35 else if (rString
== "linux")
36 return DRIVER_OS_LINUX
;
37 else if (rString
== "osx_10_5")
38 return DRIVER_OS_OSX_10_5
;
39 else if (rString
== "osx_10_6")
40 return DRIVER_OS_OSX_10_6
;
41 else if (rString
== "osx_10_7")
42 return DRIVER_OS_OSX_10_7
;
43 else if (rString
== "osx_10_8")
44 return DRIVER_OS_OSX_10_8
;
45 else if (rString
== "osx")
46 return DRIVER_OS_OSX_ALL
;
47 else if (rString
== "android")
48 return DRIVER_OS_ANDROID
;
49 return DRIVER_OS_UNKNOWN
;
52 static VersionComparisonOp
getComparison(std::string_view rString
)
54 if (rString
== "less")
56 return DRIVER_LESS_THAN
;
58 else if (rString
== "less_equal")
60 return DRIVER_LESS_THAN_OR_EQUAL
;
62 else if (rString
== "greater")
64 return DRIVER_GREATER_THAN
;
66 else if (rString
== "greater_equal")
68 return DRIVER_GREATER_THAN_OR_EQUAL
;
70 else if (rString
== "equal")
74 else if (rString
== "not_equal")
76 return DRIVER_NOT_EQUAL
;
78 else if (rString
== "between_exclusive")
80 return DRIVER_BETWEEN_EXCLUSIVE
;
82 else if (rString
== "between_inclusive")
84 return DRIVER_BETWEEN_INCLUSIVE
;
86 else if (rString
== "between_inclusive_start")
88 return DRIVER_BETWEEN_INCLUSIVE_START
;
91 throw InvalidFileException();
94 static OUString
GetVendorId(std::string_view rString
)
100 else if (rString
== "intel")
102 return u
"0x8086"_ustr
;
104 else if (rString
== "nvidia")
106 return u
"0x10de"_ustr
;
108 else if (rString
== "amd")
110 return u
"0x1002"_ustr
;
112 else if (rString
== "microsoft")
114 return u
"0x1414"_ustr
;
118 // Allow having simply the hex number as such there, too.
119 return OStringToOUString(rString
, RTL_TEXTENCODING_UTF8
);
123 OUString
GetVendorId(DeviceVendor id
)
125 assert(id
>= 0 && id
< DeviceVendorMax
);
132 return u
"0x8086"_ustr
;
134 return u
"0x10de"_ustr
;
136 return u
"0x1002"_ustr
;
137 case VendorMicrosoft
:
138 return u
"0x1414"_ustr
;
143 DeviceVendor
GetVendorFromId(uint32_t id
)
154 return VendorMicrosoft
;
160 std::string_view
GetVendorNameFromId(uint32_t id
)
177 Parser::Parser(OUString aURL
, std::vector
<DriverInfo
>& rDriverList
, VersionType versionType
)
178 : meBlockType(BlockType::UNKNOWN
)
179 , mrDriverList(rDriverList
)
180 , maURL(std::move(aURL
))
181 , mVersionType(versionType
)
189 xmlreader::XmlReader
aReader(maURL
);
190 handleContent(aReader
);
194 mrDriverList
.clear();
200 // This allows us to pad driver version 'substrings' with 0s, this
201 // effectively allows us to treat the version numbers as 'decimals'. This is
202 // a little strange but this method seems to do the right thing for all
203 // different vendor's driver strings. i.e. .98 will become 9800, which is
204 // larger than .978 which would become 9780.
205 static void PadDriverDecimal(char* aString
)
207 for (int i
= 0; i
< 4; i
++)
211 for (int c
= i
; c
< 4; c
++)
221 // All destination string storage needs to have at least 5 bytes available.
222 static bool SplitDriverVersion(const char* aSource
, char* aAStr
, char* aBStr
, char* aCStr
,
223 char* aDStr
, VersionType versionType
)
225 // sscanf doesn't do what we want here to we parse this manually.
226 int len
= strlen(aSource
);
227 char* dest
[4] = { aAStr
, aBStr
, aCStr
, aDStr
};
228 unsigned destIdx
= 0;
229 unsigned destPos
= 0;
231 for (int i
= 0; i
< len
; i
++)
233 if (destIdx
>= SAL_N_ELEMENTS(dest
))
235 // Invalid format found. Ensure we don't access dest beyond bounds.
239 if (aSource
[i
] == '.')
241 dest
[destIdx
++][destPos
] = 0;
248 // Ignore more than 4 chars. Ensure we never access dest[destIdx]
249 // beyond its bounds.
253 dest
[destIdx
][destPos
++] = aSource
[i
];
256 // Add last terminator.
257 dest
[destIdx
][destPos
] = 0;
259 // Vulkan version numbers have only 3 fields.
260 if (versionType
== VersionType::Vulkan
&& destIdx
== SAL_N_ELEMENTS(dest
) - 2)
261 dest
[++destIdx
][0] = '\0';
262 if (destIdx
!= SAL_N_ELEMENTS(dest
) - 1)
269 static bool ParseDriverVersion(std::u16string_view aVersion
, uint64_t& rNumericVersion
,
270 VersionType versionType
)
275 char aStr
[8], bStr
[8], cStr
[8], dStr
[8];
276 /* honestly, why do I even bother */
277 OString aOVersion
= OUStringToOString(aVersion
, RTL_TEXTENCODING_UTF8
);
278 if (!SplitDriverVersion(aOVersion
.getStr(), aStr
, bStr
, cStr
, dStr
, versionType
))
281 if (versionType
== VersionType::OpenGL
)
283 PadDriverDecimal(bStr
);
284 PadDriverDecimal(cStr
);
285 PadDriverDecimal(dStr
);
293 if (versionType
== VersionType::Vulkan
)
296 if (a
< 0 || a
> 0xffff)
298 if (b
< 0 || b
> 0xffff)
300 if (c
< 0 || c
> 0xffff)
302 if (d
< 0 || d
> 0xffff)
305 rNumericVersion
= GFX_DRIVER_VERSION(a
, b
, c
, d
);
309 uint64_t Parser::getVersion(std::string_view rString
)
311 OUString aString
= OStringToOUString(rString
, RTL_TEXTENCODING_UTF8
);
313 bool bResult
= ParseDriverVersion(aString
, nVersion
, mVersionType
);
317 throw InvalidFileException();
323 void Parser::handleDevices(DriverInfo
& rDriver
, xmlreader::XmlReader
& rReader
)
330 xmlreader::Span name
;
333 xmlreader::XmlReader::Result res
334 = rReader
.nextItem(xmlreader::XmlReader::Text::Normalized
, &name
, &nsId
);
336 if (res
== xmlreader::XmlReader::Result::Begin
)
340 throw InvalidFileException();
346 else if (name
== "device")
349 while (rReader
.nextAttribute(&nsIdDeveice
, &name
))
353 name
= rReader
.getAttributeValue(false);
354 OString
aDeviceId(name
.begin
, name
.length
);
355 rDriver
.maDevices
.push_back(
356 OStringToOUString(aDeviceId
, RTL_TEXTENCODING_UTF8
));
361 throw InvalidFileException();
363 else if (res
== xmlreader::XmlReader::Result::End
)
370 else if (res
== xmlreader::XmlReader::Result::Text
)
374 OString
sMsg(name
.begin
, name
.length
);
375 rDriver
.maMsg
= OStringToOUString(sMsg
, RTL_TEXTENCODING_UTF8
);
381 void Parser::handleEntry(DriverInfo
& rDriver
, xmlreader::XmlReader
& rReader
)
383 if (meBlockType
== BlockType::ALLOWLIST
)
385 rDriver
.mbAllowlisted
= true;
387 else if (meBlockType
== BlockType::DENYLIST
)
389 rDriver
.mbAllowlisted
= false;
391 else if (meBlockType
== BlockType::UNKNOWN
)
393 throw InvalidFileException();
396 xmlreader::Span name
;
399 while (rReader
.nextAttribute(&nsId
, &name
))
403 name
= rReader
.getAttributeValue(false);
404 OString
sOS(name
.begin
, name
.length
);
405 rDriver
.meOperatingSystem
= getOperatingSystem(sOS
);
407 else if (name
== "vendor")
409 name
= rReader
.getAttributeValue(false);
410 OString
sVendor(name
.begin
, name
.length
);
411 rDriver
.maAdapterVendor
= GetVendorId(sVendor
);
413 else if (name
== "compare")
415 name
= rReader
.getAttributeValue(false);
416 OString
sCompare(name
.begin
, name
.length
);
417 rDriver
.meComparisonOp
= getComparison(sCompare
);
419 else if (name
== "version")
421 name
= rReader
.getAttributeValue(false);
422 OString
sVersion(name
.begin
, name
.length
);
423 rDriver
.mnDriverVersion
= getVersion(sVersion
);
425 else if (name
== "minVersion")
427 name
= rReader
.getAttributeValue(false);
428 OString
sMinVersion(name
.begin
, name
.length
);
429 rDriver
.mnDriverVersion
= getVersion(sMinVersion
);
431 else if (name
== "maxVersion")
433 name
= rReader
.getAttributeValue(false);
434 OString
sMaxVersion(name
.begin
, name
.length
);
435 rDriver
.mnDriverVersionMax
= getVersion(sMaxVersion
);
439 OString
aAttrName(name
.begin
, name
.length
);
440 SAL_WARN("vcl.driver", "unsupported attribute: " << aAttrName
);
444 handleDevices(rDriver
, rReader
);
447 void Parser::handleList(xmlreader::XmlReader
& rReader
)
449 xmlreader::Span name
;
454 xmlreader::XmlReader::Result res
455 = rReader
.nextItem(xmlreader::XmlReader::Text::NONE
, &name
, &nsId
);
457 if (res
== xmlreader::XmlReader::Result::Begin
)
462 handleEntry(aDriver
, rReader
);
463 mrDriverList
.push_back(aDriver
);
465 else if (name
== "entryRange")
468 handleEntry(aDriver
, rReader
);
469 mrDriverList
.push_back(aDriver
);
473 throw InvalidFileException();
476 else if (res
== xmlreader::XmlReader::Result::End
)
483 void Parser::handleContent(xmlreader::XmlReader
& rReader
)
487 xmlreader::Span name
;
490 xmlreader::XmlReader::Result res
491 = rReader
.nextItem(xmlreader::XmlReader::Text::NONE
, &name
, &nsId
);
493 if (res
== xmlreader::XmlReader::Result::Begin
)
495 if (name
== "allowlist")
497 meBlockType
= BlockType::ALLOWLIST
;
500 else if (name
== "denylist")
502 meBlockType
= BlockType::DENYLIST
;
505 else if (name
== "root")
510 throw InvalidFileException();
513 else if (res
== xmlreader::XmlReader::Result::End
)
515 if (name
== "allowlist" || name
== "denylist")
517 meBlockType
= BlockType::UNKNOWN
;
520 else if (res
== xmlreader::XmlReader::Result::Done
)
527 static OperatingSystem
getOperatingSystem()
530 // OS version in 16.16 major/minor form
531 // based on http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx
532 switch (DriverBlocklist::GetWindowsVersion())
534 case 0x000A0000: // Major 10 Minor 0
535 return DRIVER_OS_WINDOWS_10
;
537 return DRIVER_OS_UNKNOWN
;
540 return DRIVER_OS_LINUX
;
542 return DRIVER_OS_UNKNOWN
;
548 struct compareIgnoreAsciiCase
550 explicit compareIgnoreAsciiCase(OUString aString
)
551 : maString(std::move(aString
))
555 bool operator()(std::u16string_view rCompare
)
557 return maString
.equalsIgnoreAsciiCase(rCompare
);
565 const uint64_t allDriverVersions
= ~(uint64_t(0));
567 DriverInfo::DriverInfo()
568 : meOperatingSystem(DRIVER_OS_UNKNOWN
)
569 , maAdapterVendor(GetVendorId(VendorAll
))
570 , mbAllowlisted(false)
571 , meComparisonOp(DRIVER_COMPARISON_IGNORED
)
573 , mnDriverVersionMax(0)
577 DriverInfo::DriverInfo(OperatingSystem os
, OUString vendor
, VersionComparisonOp op
,
578 uint64_t driverVersion
, bool bAllowlisted
,
579 const char* suggestedVersion
/* = nullptr */)
580 : meOperatingSystem(os
)
581 , maAdapterVendor(std::move(vendor
))
582 , mbAllowlisted(bAllowlisted
)
584 , mnDriverVersion(driverVersion
)
585 , mnDriverVersionMax(0)
587 if (suggestedVersion
)
589 = OStringToOUString(std::string_view(suggestedVersion
), RTL_TEXTENCODING_UTF8
);
592 bool FindBlocklistedDeviceInList(std::vector
<DriverInfo
>& aDeviceInfos
, VersionType versionType
,
593 std::u16string_view sDriverVersion
,
594 std::u16string_view sAdapterVendorID
,
595 OUString
const& sAdapterDeviceID
, OperatingSystem system
,
596 const OUString
& blocklistURL
)
598 uint64_t driverVersion
;
599 ParseDriverVersion(sDriverVersion
, driverVersion
, versionType
);
602 for (const auto& rDeviceInfo
: aDeviceInfos
)
604 bool osMatch
= false;
605 if (rDeviceInfo
.meOperatingSystem
== DRIVER_OS_ALL
)
607 else if (rDeviceInfo
.meOperatingSystem
== system
)
609 else if (rDeviceInfo
.meOperatingSystem
== DRIVER_OS_WINDOWS_ALL
610 && system
>= DRIVER_OS_WINDOWS_FIRST
&& system
<= DRIVER_OS_WINDOWS_LAST
)
612 else if (rDeviceInfo
.meOperatingSystem
== DRIVER_OS_OSX_ALL
&& system
>= DRIVER_OS_OSX_FIRST
613 && system
<= DRIVER_OS_OSX_LAST
)
620 if (!rDeviceInfo
.maAdapterVendor
.equalsIgnoreAsciiCase(GetVendorId(VendorAll
))
621 && !rDeviceInfo
.maAdapterVendor
.equalsIgnoreAsciiCase(sAdapterVendorID
))
626 if (std::none_of(rDeviceInfo
.maDevices
.begin(), rDeviceInfo
.maDevices
.end(),
627 compareIgnoreAsciiCase(u
"all"_ustr
))
628 && std::none_of(rDeviceInfo
.maDevices
.begin(), rDeviceInfo
.maDevices
.end(),
629 compareIgnoreAsciiCase(sAdapterDeviceID
)))
634 switch (rDeviceInfo
.meComparisonOp
)
636 case DRIVER_LESS_THAN
:
637 match
= driverVersion
< rDeviceInfo
.mnDriverVersion
;
639 case DRIVER_LESS_THAN_OR_EQUAL
:
640 match
= driverVersion
<= rDeviceInfo
.mnDriverVersion
;
642 case DRIVER_GREATER_THAN
:
643 match
= driverVersion
> rDeviceInfo
.mnDriverVersion
;
645 case DRIVER_GREATER_THAN_OR_EQUAL
:
646 match
= driverVersion
>= rDeviceInfo
.mnDriverVersion
;
649 match
= driverVersion
== rDeviceInfo
.mnDriverVersion
;
651 case DRIVER_NOT_EQUAL
:
652 match
= driverVersion
!= rDeviceInfo
.mnDriverVersion
;
654 case DRIVER_BETWEEN_EXCLUSIVE
:
655 match
= driverVersion
> rDeviceInfo
.mnDriverVersion
656 && driverVersion
< rDeviceInfo
.mnDriverVersionMax
;
658 case DRIVER_BETWEEN_INCLUSIVE
:
659 match
= driverVersion
>= rDeviceInfo
.mnDriverVersion
660 && driverVersion
<= rDeviceInfo
.mnDriverVersionMax
;
662 case DRIVER_BETWEEN_INCLUSIVE_START
:
663 match
= driverVersion
>= rDeviceInfo
.mnDriverVersion
664 && driverVersion
< rDeviceInfo
.mnDriverVersionMax
;
666 case DRIVER_COMPARISON_IGNORED
:
667 // We don't have a comparison op, so we match everything.
671 SAL_WARN("vcl.driver", "Bogus op in " << blocklistURL
);
675 if (match
|| rDeviceInfo
.mnDriverVersion
== allDriverVersions
)
677 // white listed drivers
678 if (rDeviceInfo
.mbAllowlisted
)
680 SAL_INFO("vcl.driver", "allowlisted driver");
685 if (!rDeviceInfo
.maSuggestedVersion
.isEmpty())
687 SAL_WARN("vcl.driver", "use : " << rDeviceInfo
.maSuggestedVersion
);
693 SAL_INFO("vcl.driver", (match
? "denylisted" : "not denylisted") << " in " << blocklistURL
);
697 bool IsDeviceBlocked(const OUString
& blocklistURL
, VersionType versionType
,
698 std::u16string_view driverVersion
, std::u16string_view vendorId
,
699 const OUString
& deviceId
)
701 std::vector
<DriverInfo
> driverList
;
702 Parser
parser(blocklistURL
, driverList
, versionType
);
705 SAL_WARN("vcl.driver", "error parsing denylist " << blocklistURL
);
708 return FindBlocklistedDeviceInList(driverList
, versionType
, driverVersion
, vendorId
, deviceId
,
709 getOperatingSystem(), blocklistURL
);
713 int32_t GetWindowsVersion()
715 static int32_t winVersion
= []() {
716 // GetVersion(Ex) and VersionHelpers (based on VerifyVersionInfo) API are
717 // subject to manifest-based behavior since Windows 8.1, so give wrong results.
718 // Another approach would be to use NetWkstaGetInfo, but that has some small
719 // reported delays (some milliseconds), and might get slower in domains with
720 // poor network connections.
721 // So go with a solution described at https://msdn.microsoft.com/en-us/library/ms724429
722 HINSTANCE hLibrary
= LoadLibraryW(L
"kernel32.dll");
723 if (hLibrary
!= nullptr)
725 wchar_t szPath
[MAX_PATH
];
726 DWORD dwCount
= GetModuleFileNameW(hLibrary
, szPath
, SAL_N_ELEMENTS(szPath
));
727 FreeLibrary(hLibrary
);
728 if (dwCount
!= 0 && dwCount
< SAL_N_ELEMENTS(szPath
))
730 dwCount
= GetFileVersionInfoSizeW(szPath
, nullptr);
733 std::unique_ptr
<char[]> ver(new char[dwCount
]);
734 if (GetFileVersionInfoW(szPath
, 0, dwCount
, ver
.get()) != FALSE
)
736 void* pBlock
= nullptr;
738 if (VerQueryValueW(ver
.get(), L
"\\", &pBlock
, &dwBlockSz
) != FALSE
739 && dwBlockSz
>= sizeof(VS_FIXEDFILEINFO
))
741 VS_FIXEDFILEINFO
* vinfo
= static_cast<VS_FIXEDFILEINFO
*>(pBlock
);
742 return int32_t(vinfo
->dwProductVersionMS
);
757 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */