LibreOffice Module vcl (master) 1
driverblocklist.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 */
9
10#include <driverblocklist.hxx>
11
12#include <algorithm>
13#include <string_view>
14
15#include <sal/log.hxx>
16#include <utility>
17
18#ifdef _WIN32
19#if !defined WIN32_LEAN_AND_MEAN
20#define WIN32_LEAN_AND_MEAN
21#endif
22#include <windows.h>
23#endif
24
26{
27static OperatingSystem getOperatingSystem(std::string_view rString)
28{
29 if (rString == "all")
30 return DRIVER_OS_ALL;
31 else if (rString == "7")
33 else if (rString == "8")
35 else if (rString == "8_1")
37 else if (rString == "10")
39 else if (rString == "windows")
41 else if (rString == "linux")
42 return DRIVER_OS_LINUX;
43 else if (rString == "osx_10_5")
44 return DRIVER_OS_OSX_10_5;
45 else if (rString == "osx_10_6")
46 return DRIVER_OS_OSX_10_6;
47 else if (rString == "osx_10_7")
48 return DRIVER_OS_OSX_10_7;
49 else if (rString == "osx_10_8")
50 return DRIVER_OS_OSX_10_8;
51 else if (rString == "osx")
52 return DRIVER_OS_OSX_ALL;
53 else if (rString == "android")
54 return DRIVER_OS_ANDROID;
55 return DRIVER_OS_UNKNOWN;
56}
57
58static VersionComparisonOp getComparison(std::string_view rString)
59{
60 if (rString == "less")
61 {
62 return DRIVER_LESS_THAN;
63 }
64 else if (rString == "less_equal")
65 {
67 }
68 else if (rString == "greater")
69 {
71 }
72 else if (rString == "greater_equal")
73 {
75 }
76 else if (rString == "equal")
77 {
78 return DRIVER_EQUAL;
79 }
80 else if (rString == "not_equal")
81 {
82 return DRIVER_NOT_EQUAL;
83 }
84 else if (rString == "between_exclusive")
85 {
87 }
88 else if (rString == "between_inclusive")
89 {
91 }
92 else if (rString == "between_inclusive_start")
93 {
95 }
96
98}
99
100static OUString GetVendorId(std::string_view rString)
101{
102 if (rString == "all")
103 {
104 return "";
105 }
106 else if (rString == "intel")
107 {
108 return "0x8086";
109 }
110 else if (rString == "nvidia")
111 {
112 return "0x10de";
113 }
114 else if (rString == "amd")
115 {
116 return "0x1002";
117 }
118 else if (rString == "microsoft")
119 {
120 return "0x1414";
121 }
122 else
123 {
124 // Allow having simply the hex number as such there, too.
125 return OStringToOUString(rString, RTL_TEXTENCODING_UTF8);
126 }
127}
128
130{
131 assert(id >= 0 && id < DeviceVendorMax);
132
133 switch (id)
134 {
135 case VendorAll:
136 return "";
137 case VendorIntel:
138 return "0x8086";
139 case VendorNVIDIA:
140 return "0x10de";
141 case VendorAMD:
142 return "0x1002";
143 case VendorMicrosoft:
144 return "0x1414";
145 }
146 abort();
147}
148
150{
151 switch (id)
152 {
153 case 0x8086:
154 return VendorIntel;
155 case 0x10de:
156 return VendorNVIDIA;
157 case 0x1002:
158 return VendorAMD;
159 case 0x1414:
160 return VendorMicrosoft;
161 default:
162 return VendorAll;
163 }
164}
165
166std::string_view GetVendorNameFromId(uint32_t id)
167{
168 switch (id)
169 {
170 case 0x8086:
171 return "Intel";
172 case 0x10de:
173 return "Nvidia";
174 case 0x1002:
175 return "AMD";
176 case 0x1414:
177 return "Microsoft";
178 default:
179 return "?";
180 }
181}
182
183Parser::Parser(OUString aURL, std::vector<DriverInfo>& rDriverList, VersionType versionType)
184 : meBlockType(BlockType::UNKNOWN)
185 , mrDriverList(rDriverList)
186 , maURL(std::move(aURL))
187 , mVersionType(versionType)
188{
189}
190
192{
193 try
194 {
196 handleContent(aReader);
197 }
198 catch (...)
199 {
200 mrDriverList.clear();
201 return false;
202 }
203 return true;
204}
205
206// This allows us to pad driver version 'substrings' with 0s, this
207// effectively allows us to treat the version numbers as 'decimals'. This is
208// a little strange but this method seems to do the right thing for all
209// different vendor's driver strings. i.e. .98 will become 9800, which is
210// larger than .978 which would become 9780.
211static void PadDriverDecimal(char* aString)
212{
213 for (int i = 0; i < 4; i++)
214 {
215 if (!aString[i])
216 {
217 for (int c = i; c < 4; c++)
218 {
219 aString[c] = '0';
220 }
221 break;
222 }
223 }
224 aString[4] = 0;
225}
226
227// All destination string storage needs to have at least 5 bytes available.
228static bool SplitDriverVersion(const char* aSource, char* aAStr, char* aBStr, char* aCStr,
229 char* aDStr, VersionType versionType)
230{
231 // sscanf doesn't do what we want here to we parse this manually.
232 int len = strlen(aSource);
233 char* dest[4] = { aAStr, aBStr, aCStr, aDStr };
234 unsigned destIdx = 0;
235 unsigned destPos = 0;
236
237 for (int i = 0; i < len; i++)
238 {
239 if (destIdx >= SAL_N_ELEMENTS(dest))
240 {
241 // Invalid format found. Ensure we don't access dest beyond bounds.
242 return false;
243 }
244
245 if (aSource[i] == '.')
246 {
247 dest[destIdx++][destPos] = 0;
248 destPos = 0;
249 continue;
250 }
251
252 if (destPos > 3)
253 {
254 // Ignore more than 4 chars. Ensure we never access dest[destIdx]
255 // beyond its bounds.
256 continue;
257 }
258
259 dest[destIdx][destPos++] = aSource[i];
260 }
261
262 // Add last terminator.
263 dest[destIdx][destPos] = 0;
264
265 // Vulkan version numbers have only 3 fields.
266 if (versionType == VersionType::Vulkan && destIdx == SAL_N_ELEMENTS(dest) - 2)
267 dest[++destIdx][0] = '\0';
268 if (destIdx != SAL_N_ELEMENTS(dest) - 1)
269 {
270 return false;
271 }
272 return true;
273}
274
275static bool ParseDriverVersion(std::u16string_view aVersion, uint64_t& rNumericVersion,
276 VersionType versionType)
277{
278 rNumericVersion = 0;
279
280 int a, b, c, d;
281 char aStr[8], bStr[8], cStr[8], dStr[8];
282 /* honestly, why do I even bother */
283 OString aOVersion = OUStringToOString(aVersion, RTL_TEXTENCODING_UTF8);
284 if (!SplitDriverVersion(aOVersion.getStr(), aStr, bStr, cStr, dStr, versionType))
285 return false;
286
287 if (versionType == VersionType::OpenGL)
288 {
289 PadDriverDecimal(bStr);
290 PadDriverDecimal(cStr);
291 PadDriverDecimal(dStr);
292 }
293
294 a = atoi(aStr);
295 b = atoi(bStr);
296 c = atoi(cStr);
297 d = atoi(dStr);
298
299 if (versionType == VersionType::Vulkan)
300 assert(d == 0);
301
302 if (a < 0 || a > 0xffff)
303 return false;
304 if (b < 0 || b > 0xffff)
305 return false;
306 if (c < 0 || c > 0xffff)
307 return false;
308 if (d < 0 || d > 0xffff)
309 return false;
310
311 rNumericVersion = GFX_DRIVER_VERSION(a, b, c, d);
312 return true;
313}
314
315uint64_t Parser::getVersion(std::string_view rString)
316{
317 OUString aString = OStringToOUString(rString, RTL_TEXTENCODING_UTF8);
318 uint64_t nVersion;
319 bool bResult = ParseDriverVersion(aString, nVersion, mVersionType);
320
321 if (!bResult)
322 {
323 throw InvalidFileException();
324 }
325
326 return nVersion;
327}
328
330{
331 int nLevel = 1;
332 bool bInMsg = false;
333
334 while (true)
335 {
337 int nsId;
338
340 = rReader.nextItem(xmlreader::XmlReader::Text::Normalized, &name, &nsId);
341
342 if (res == xmlreader::XmlReader::Result::Begin)
343 {
344 ++nLevel;
345 if (nLevel > 2)
346 throw InvalidFileException();
347
348 if (name == "msg")
349 {
350 bInMsg = true;
351 }
352 else if (name == "device")
353 {
354 int nsIdDeveice;
355 while (rReader.nextAttribute(&nsIdDeveice, &name))
356 {
357 if (name == "id")
358 {
359 name = rReader.getAttributeValue(false);
360 OString aDeviceId(name.begin, name.length);
361 rDriver.maDevices.push_back(
362 OStringToOUString(aDeviceId, RTL_TEXTENCODING_UTF8));
363 }
364 }
365 }
366 else
367 throw InvalidFileException();
368 }
369 else if (res == xmlreader::XmlReader::Result::End)
370 {
371 --nLevel;
372 bInMsg = false;
373 if (!nLevel)
374 break;
375 }
376 else if (res == xmlreader::XmlReader::Result::Text)
377 {
378 if (bInMsg)
379 {
380 OString sMsg(name.begin, name.length);
381 rDriver.maMsg = OStringToOUString(sMsg, RTL_TEXTENCODING_UTF8);
382 }
383 }
384 }
385}
386
388{
390 {
391 rDriver.mbAllowlisted = true;
392 }
394 {
395 rDriver.mbAllowlisted = false;
396 }
398 {
399 throw InvalidFileException();
400 }
401
403 int nsId;
404
405 while (rReader.nextAttribute(&nsId, &name))
406 {
407 if (name == "os")
408 {
409 name = rReader.getAttributeValue(false);
410 OString sOS(name.begin, name.length);
412 }
413 else if (name == "vendor")
414 {
415 name = rReader.getAttributeValue(false);
416 OString sVendor(name.begin, name.length);
417 rDriver.maAdapterVendor = GetVendorId(sVendor);
418 }
419 else if (name == "compare")
420 {
421 name = rReader.getAttributeValue(false);
422 OString sCompare(name.begin, name.length);
423 rDriver.meComparisonOp = getComparison(sCompare);
424 }
425 else if (name == "version")
426 {
427 name = rReader.getAttributeValue(false);
428 OString sVersion(name.begin, name.length);
429 rDriver.mnDriverVersion = getVersion(sVersion);
430 }
431 else if (name == "minVersion")
432 {
433 name = rReader.getAttributeValue(false);
434 OString sMinVersion(name.begin, name.length);
435 rDriver.mnDriverVersion = getVersion(sMinVersion);
436 }
437 else if (name == "maxVersion")
438 {
439 name = rReader.getAttributeValue(false);
440 OString sMaxVersion(name.begin, name.length);
441 rDriver.mnDriverVersionMax = getVersion(sMaxVersion);
442 }
443 else
444 {
445 OString aAttrName(name.begin, name.length);
446 SAL_WARN("vcl.driver", "unsupported attribute: " << aAttrName);
447 }
448 }
449
450 handleDevices(rDriver, rReader);
451}
452
454{
456 int nsId;
457
458 while (true)
459 {
461 = rReader.nextItem(xmlreader::XmlReader::Text::NONE, &name, &nsId);
462
463 if (res == xmlreader::XmlReader::Result::Begin)
464 {
465 if (name == "entry")
466 {
467 DriverInfo aDriver;
468 handleEntry(aDriver, rReader);
469 mrDriverList.push_back(aDriver);
470 }
471 else if (name == "entryRange")
472 {
473 DriverInfo aDriver;
474 handleEntry(aDriver, rReader);
475 mrDriverList.push_back(aDriver);
476 }
477 else
478 {
479 throw InvalidFileException();
480 }
481 }
482 else if (res == xmlreader::XmlReader::Result::End)
483 {
484 break;
485 }
486 }
487}
488
490{
491 while (true)
492 {
494 int nsId;
495
497 = rReader.nextItem(xmlreader::XmlReader::Text::NONE, &name, &nsId);
498
499 if (res == xmlreader::XmlReader::Result::Begin)
500 {
501 if (name == "allowlist")
502 {
504 handleList(rReader);
505 }
506 else if (name == "denylist")
507 {
509 handleList(rReader);
510 }
511 else if (name == "root")
512 {
513 }
514 else
515 {
516 throw InvalidFileException();
517 }
518 }
519 else if (res == xmlreader::XmlReader::Result::End)
520 {
521 if (name == "allowlist" || name == "denylist")
522 {
524 }
525 }
526 else if (res == xmlreader::XmlReader::Result::Done)
527 {
528 break;
529 }
530 }
531}
532
534{
535#ifdef _WIN32
536 // OS version in 16.16 major/minor form
537 // based on http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx
538 switch (DriverBlocklist::GetWindowsVersion())
539 {
540 case 0x00060001:
541 return DRIVER_OS_WINDOWS_7;
542 case 0x00060002:
543 return DRIVER_OS_WINDOWS_8;
544 case 0x00060003:
546 case 0x000A0000: // Major 10 Minor 0
548 default:
549 return DRIVER_OS_UNKNOWN;
550 }
551#elif defined LINUX
552 return DRIVER_OS_LINUX;
553#else
554 return DRIVER_OS_UNKNOWN;
555#endif
556}
557
558namespace
559{
560struct compareIgnoreAsciiCase
561{
562 explicit compareIgnoreAsciiCase(OUString aString)
563 : maString(std::move(aString))
564 {
565 }
566
567 bool operator()(std::u16string_view rCompare)
568 {
569 return maString.equalsIgnoreAsciiCase(rCompare);
570 }
571
572private:
573 OUString maString;
574};
575}
576
577const uint64_t allDriverVersions = ~(uint64_t(0));
578
580 : meOperatingSystem(DRIVER_OS_UNKNOWN)
581 , maAdapterVendor(GetVendorId(VendorAll))
582 , mbAllowlisted(false)
583 , meComparisonOp(DRIVER_COMPARISON_IGNORED)
584 , mnDriverVersion(0)
585 , mnDriverVersionMax(0)
586{
587}
588
590 uint64_t driverVersion, bool bAllowlisted,
591 const char* suggestedVersion /* = nullptr */)
592 : meOperatingSystem(os)
593 , maAdapterVendor(std::move(vendor))
594 , mbAllowlisted(bAllowlisted)
595 , meComparisonOp(op)
596 , mnDriverVersion(driverVersion)
597 , mnDriverVersionMax(0)
598{
599 if (suggestedVersion)
601 = OStringToOUString(std::string_view(suggestedVersion), RTL_TEXTENCODING_UTF8);
602}
603
604bool FindBlocklistedDeviceInList(std::vector<DriverInfo>& aDeviceInfos, VersionType versionType,
605 std::u16string_view sDriverVersion,
606 std::u16string_view sAdapterVendorID,
607 OUString const& sAdapterDeviceID, OperatingSystem system,
608 const OUString& blocklistURL)
609{
610 uint64_t driverVersion;
611 ParseDriverVersion(sDriverVersion, driverVersion, versionType);
612
613 bool match = false;
614 for (std::vector<DriverInfo>::size_type i = 0; i < aDeviceInfos.size(); i++)
615 {
616 bool osMatch = false;
617 if (aDeviceInfos[i].meOperatingSystem == DRIVER_OS_ALL)
618 osMatch = true;
619 else if (aDeviceInfos[i].meOperatingSystem == system)
620 osMatch = true;
621 else if (aDeviceInfos[i].meOperatingSystem == DRIVER_OS_WINDOWS_ALL
622 && system >= DRIVER_OS_WINDOWS_FIRST && system <= DRIVER_OS_WINDOWS_LAST)
623 osMatch = true;
624 else if (aDeviceInfos[i].meOperatingSystem == DRIVER_OS_OSX_ALL
625 && system >= DRIVER_OS_OSX_FIRST && system <= DRIVER_OS_OSX_LAST)
626 osMatch = true;
627 if (!osMatch)
628 {
629 continue;
630 }
631
632 if (!aDeviceInfos[i].maAdapterVendor.equalsIgnoreAsciiCase(GetVendorId(VendorAll))
633 && !aDeviceInfos[i].maAdapterVendor.equalsIgnoreAsciiCase(sAdapterVendorID))
634 {
635 continue;
636 }
637
638 if (std::none_of(aDeviceInfos[i].maDevices.begin(), aDeviceInfos[i].maDevices.end(),
639 compareIgnoreAsciiCase("all"))
640 && std::none_of(aDeviceInfos[i].maDevices.begin(), aDeviceInfos[i].maDevices.end(),
641 compareIgnoreAsciiCase(sAdapterDeviceID)))
642 {
643 continue;
644 }
645
646 switch (aDeviceInfos[i].meComparisonOp)
647 {
648 case DRIVER_LESS_THAN:
649 match = driverVersion < aDeviceInfos[i].mnDriverVersion;
650 break;
652 match = driverVersion <= aDeviceInfos[i].mnDriverVersion;
653 break;
655 match = driverVersion > aDeviceInfos[i].mnDriverVersion;
656 break;
658 match = driverVersion >= aDeviceInfos[i].mnDriverVersion;
659 break;
660 case DRIVER_EQUAL:
661 match = driverVersion == aDeviceInfos[i].mnDriverVersion;
662 break;
663 case DRIVER_NOT_EQUAL:
664 match = driverVersion != aDeviceInfos[i].mnDriverVersion;
665 break;
667 match = driverVersion > aDeviceInfos[i].mnDriverVersion
668 && driverVersion < aDeviceInfos[i].mnDriverVersionMax;
669 break;
671 match = driverVersion >= aDeviceInfos[i].mnDriverVersion
672 && driverVersion <= aDeviceInfos[i].mnDriverVersionMax;
673 break;
675 match = driverVersion >= aDeviceInfos[i].mnDriverVersion
676 && driverVersion < aDeviceInfos[i].mnDriverVersionMax;
677 break;
679 // We don't have a comparison op, so we match everything.
680 match = true;
681 break;
682 default:
683 SAL_WARN("vcl.driver", "Bogus op in " << blocklistURL);
684 break;
685 }
686
687 if (match || aDeviceInfos[i].mnDriverVersion == allDriverVersions)
688 {
689 // white listed drivers
690 if (aDeviceInfos[i].mbAllowlisted)
691 {
692 SAL_INFO("vcl.driver", "allowlisted driver");
693 return false;
694 }
695
696 match = true;
697 if (!aDeviceInfos[i].maSuggestedVersion.isEmpty())
698 {
699 SAL_WARN("vcl.driver", "use : " << aDeviceInfos[i].maSuggestedVersion);
700 }
701 break;
702 }
703 }
704
705 SAL_INFO("vcl.driver", (match ? "denylisted" : "not denylisted") << " in " << blocklistURL);
706 return match;
707}
708
709bool IsDeviceBlocked(const OUString& blocklistURL, VersionType versionType,
710 std::u16string_view driverVersion, std::u16string_view vendorId,
711 const OUString& deviceId)
712{
713 std::vector<DriverInfo> driverList;
714 Parser parser(blocklistURL, driverList, versionType);
715 if (!parser.parse())
716 {
717 SAL_WARN("vcl.driver", "error parsing denylist " << blocklistURL);
718 return false;
719 }
720 return FindBlocklistedDeviceInList(driverList, versionType, driverVersion, vendorId, deviceId,
721 getOperatingSystem(), blocklistURL);
722}
723
724#ifdef _WIN32
725int32_t GetWindowsVersion()
726{
727 static int32_t winVersion = []() {
728 // GetVersion(Ex) and VersionHelpers (based on VerifyVersionInfo) API are
729 // subject to manifest-based behavior since Windows 8.1, so give wrong results.
730 // Another approach would be to use NetWkstaGetInfo, but that has some small
731 // reported delays (some milliseconds), and might get slower in domains with
732 // poor network connections.
733 // So go with a solution described at https://msdn.microsoft.com/en-us/library/ms724429
734 HINSTANCE hLibrary = LoadLibraryW(L"kernel32.dll");
735 if (hLibrary != nullptr)
736 {
737 wchar_t szPath[MAX_PATH];
738 DWORD dwCount = GetModuleFileNameW(hLibrary, szPath, SAL_N_ELEMENTS(szPath));
739 FreeLibrary(hLibrary);
740 if (dwCount != 0 && dwCount < SAL_N_ELEMENTS(szPath))
741 {
742 dwCount = GetFileVersionInfoSizeW(szPath, nullptr);
743 if (dwCount != 0)
744 {
745 std::unique_ptr<char[]> ver(new char[dwCount]);
746 if (GetFileVersionInfoW(szPath, 0, dwCount, ver.get()) != FALSE)
747 {
748 void* pBlock = nullptr;
749 UINT dwBlockSz = 0;
750 if (VerQueryValueW(ver.get(), L"\\", &pBlock, &dwBlockSz) != FALSE
751 && dwBlockSz >= sizeof(VS_FIXEDFILEINFO))
752 {
753 VS_FIXEDFILEINFO* vinfo = static_cast<VS_FIXEDFILEINFO*>(pBlock);
754 return int32_t(vinfo->dwProductVersionMS);
755 }
756 }
757 }
758 }
759 }
760 return 0;
761 }();
762
763 return winVersion;
764}
765#endif
766
767} // namespace
768
769/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
double d
static void handleDevices(DriverInfo &rDriver, xmlreader::XmlReader &rReader)
void handleEntry(DriverInfo &rDriver, xmlreader::XmlReader &rReader)
const VersionType mVersionType
Parser(OUString aURL, std::vector< DriverInfo > &rDriverList, VersionType versionType)
std::vector< DriverInfo > & mrDriverList
void handleContent(xmlreader::XmlReader &rReader)
uint64_t getVersion(std::string_view rString)
void handleList(xmlreader::XmlReader &rReader)
Result nextItem(Text reportText, Span *data, int *nsId)
bool nextAttribute(int *nsId, Span *localName)
Span getAttributeValue(bool fullyNormalize)
URL aURL
OUString maString
#define GFX_DRIVER_VERSION(a, b, c, d)
sal_Int16 nVersion
UNKNOWN
#define MAX_PATH
const char * name
uno_Any a
#define SAL_WARN(area, stream)
#define SAL_INFO(area, stream)
#define SAL_N_ELEMENTS(arr)
aStr
static OperatingSystem getOperatingSystem(std::string_view rString)
DeviceVendor GetVendorFromId(uint32_t id)
Returns vendor for the given vendor ID, or VendorAll if not known.
std::string_view GetVendorNameFromId(uint32_t id)
static void PadDriverDecimal(char *aString)
static VersionComparisonOp getComparison(std::string_view rString)
bool FindBlocklistedDeviceInList(std::vector< DriverInfo > &aDeviceInfos, VersionType versionType, std::u16string_view sDriverVersion, std::u16string_view sAdapterVendorID, OUString const &sAdapterDeviceID, OperatingSystem system, const OUString &blocklistURL)
static bool ParseDriverVersion(std::u16string_view aVersion, uint64_t &rNumericVersion, VersionType versionType)
const int DeviceVendorMax
const uint64_t allDriverVersions
static OUString GetVendorId(std::string_view rString)
bool IsDeviceBlocked(const OUString &blocklistURL, VersionType versionType, std::u16string_view driverVersion, std::u16string_view vendorId, const OUString &deviceId)
static bool SplitDriverVersion(const char *aSource, char *aAStr, char *aBStr, char *aCStr, char *aDStr, VersionType versionType)
VCL_DLLPUBLIC uint32_t vendorId
bool match(const sal_Unicode *pWild, const sal_Unicode *pStr, const sal_Unicode cEscape)
int i
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
parser
const URL maURL
std::vector< OUString > maDevices
VersionComparisonOp meComparisonOp