1
0
Fork 0
libreoffice/vcl/source/helper/driverblocklist.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

769 lines
22 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <driverblocklist.hxx>
#include <algorithm>
#include <string_view>
#include <sal/log.hxx>
#include <utility>
#ifdef _WIN32
#if !defined WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#endif
namespace DriverBlocklist
{
static OperatingSystem getOperatingSystem(std::string_view rString)
{
if (rString == "all")
return DRIVER_OS_ALL;
else if (rString == "7")
return DRIVER_OS_WINDOWS_7;
else if (rString == "8")
return DRIVER_OS_WINDOWS_8;
else if (rString == "8_1")
return DRIVER_OS_WINDOWS_8_1;
else if (rString == "10")
return DRIVER_OS_WINDOWS_10;
else if (rString == "windows")
return DRIVER_OS_WINDOWS_ALL;
else if (rString == "linux")
return DRIVER_OS_LINUX;
else if (rString == "osx_10_5")
return DRIVER_OS_OSX_10_5;
else if (rString == "osx_10_6")
return DRIVER_OS_OSX_10_6;
else if (rString == "osx_10_7")
return DRIVER_OS_OSX_10_7;
else if (rString == "osx_10_8")
return DRIVER_OS_OSX_10_8;
else if (rString == "osx")
return DRIVER_OS_OSX_ALL;
else if (rString == "android")
return DRIVER_OS_ANDROID;
return DRIVER_OS_UNKNOWN;
}
static VersionComparisonOp getComparison(std::string_view rString)
{
if (rString == "less")
{
return DRIVER_LESS_THAN;
}
else if (rString == "less_equal")
{
return DRIVER_LESS_THAN_OR_EQUAL;
}
else if (rString == "greater")
{
return DRIVER_GREATER_THAN;
}
else if (rString == "greater_equal")
{
return DRIVER_GREATER_THAN_OR_EQUAL;
}
else if (rString == "equal")
{
return DRIVER_EQUAL;
}
else if (rString == "not_equal")
{
return DRIVER_NOT_EQUAL;
}
else if (rString == "between_exclusive")
{
return DRIVER_BETWEEN_EXCLUSIVE;
}
else if (rString == "between_inclusive")
{
return DRIVER_BETWEEN_INCLUSIVE;
}
else if (rString == "between_inclusive_start")
{
return DRIVER_BETWEEN_INCLUSIVE_START;
}
throw InvalidFileException();
}
static OUString GetVendorId(std::string_view rString)
{
if (rString == "all")
{
return u""_ustr;
}
else if (rString == "intel")
{
return u"0x8086"_ustr;
}
else if (rString == "nvidia")
{
return u"0x10de"_ustr;
}
else if (rString == "amd")
{
return u"0x1002"_ustr;
}
else if (rString == "microsoft")
{
return u"0x1414"_ustr;
}
else
{
// Allow having simply the hex number as such there, too.
return OStringToOUString(rString, RTL_TEXTENCODING_UTF8);
}
}
OUString GetVendorId(DeviceVendor id)
{
assert(id >= 0 && id < DeviceVendorMax);
switch (id)
{
case VendorAll:
return u""_ustr;
case VendorIntel:
return u"0x8086"_ustr;
case VendorNVIDIA:
return u"0x10de"_ustr;
case VendorAMD:
return u"0x1002"_ustr;
case VendorMicrosoft:
return u"0x1414"_ustr;
}
abort();
}
DeviceVendor GetVendorFromId(uint32_t id)
{
switch (id)
{
case 0x8086:
return VendorIntel;
case 0x10de:
return VendorNVIDIA;
case 0x1002:
return VendorAMD;
case 0x1414:
return VendorMicrosoft;
default:
return VendorAll;
}
}
std::string_view GetVendorNameFromId(uint32_t id)
{
switch (id)
{
case 0x8086:
return "Intel";
case 0x10de:
return "Nvidia";
case 0x1002:
return "AMD";
case 0x1414:
return "Microsoft";
default:
return "?";
}
}
Parser::Parser(OUString aURL, std::vector<DriverInfo>& rDriverList, VersionType versionType)
: meBlockType(BlockType::UNKNOWN)
, mrDriverList(rDriverList)
, maURL(std::move(aURL))
, mVersionType(versionType)
{
}
bool Parser::parse()
{
try
{
xmlreader::XmlReader aReader(maURL);
handleContent(aReader);
}
catch (...)
{
mrDriverList.clear();
return false;
}
return true;
}
// This allows us to pad driver version 'substrings' with 0s, this
// effectively allows us to treat the version numbers as 'decimals'. This is
// a little strange but this method seems to do the right thing for all
// different vendor's driver strings. i.e. .98 will become 9800, which is
// larger than .978 which would become 9780.
static void PadDriverDecimal(char* aString)
{
for (int i = 0; i < 4; i++)
{
if (!aString[i])
{
for (int c = i; c < 4; c++)
{
aString[c] = '0';
}
break;
}
}
aString[4] = 0;
}
// All destination string storage needs to have at least 5 bytes available.
static bool SplitDriverVersion(const char* aSource, char* aAStr, char* aBStr, char* aCStr,
char* aDStr, VersionType versionType)
{
// sscanf doesn't do what we want here to we parse this manually.
int len = strlen(aSource);
char* dest[4] = { aAStr, aBStr, aCStr, aDStr };
unsigned destIdx = 0;
unsigned destPos = 0;
for (int i = 0; i < len; i++)
{
if (destIdx >= SAL_N_ELEMENTS(dest))
{
// Invalid format found. Ensure we don't access dest beyond bounds.
return false;
}
if (aSource[i] == '.')
{
dest[destIdx++][destPos] = 0;
destPos = 0;
continue;
}
if (destPos > 3)
{
// Ignore more than 4 chars. Ensure we never access dest[destIdx]
// beyond its bounds.
continue;
}
dest[destIdx][destPos++] = aSource[i];
}
// Add last terminator.
dest[destIdx][destPos] = 0;
// Vulkan version numbers have only 3 fields.
if (versionType == VersionType::Vulkan && destIdx == SAL_N_ELEMENTS(dest) - 2)
dest[++destIdx][0] = '\0';
if (destIdx != SAL_N_ELEMENTS(dest) - 1)
{
return false;
}
return true;
}
static bool ParseDriverVersion(std::u16string_view aVersion, uint64_t& rNumericVersion,
VersionType versionType)
{
rNumericVersion = 0;
int a, b, c, d;
char aStr[8], bStr[8], cStr[8], dStr[8];
/* honestly, why do I even bother */
OString aOVersion = OUStringToOString(aVersion, RTL_TEXTENCODING_UTF8);
if (!SplitDriverVersion(aOVersion.getStr(), aStr, bStr, cStr, dStr, versionType))
return false;
if (versionType == VersionType::OpenGL)
{
PadDriverDecimal(bStr);
PadDriverDecimal(cStr);
PadDriverDecimal(dStr);
}
a = atoi(aStr);
b = atoi(bStr);
c = atoi(cStr);
d = atoi(dStr);
if (versionType == VersionType::Vulkan)
assert(d == 0);
if (a < 0 || a > 0xffff)
return false;
if (b < 0 || b > 0xffff)
return false;
if (c < 0 || c > 0xffff)
return false;
if (d < 0 || d > 0xffff)
return false;
rNumericVersion = GFX_DRIVER_VERSION(a, b, c, d);
return true;
}
uint64_t Parser::getVersion(std::string_view rString)
{
OUString aString = OStringToOUString(rString, RTL_TEXTENCODING_UTF8);
uint64_t nVersion;
bool bResult = ParseDriverVersion(aString, nVersion, mVersionType);
if (!bResult)
{
throw InvalidFileException();
}
return nVersion;
}
void Parser::handleDevices(DriverInfo& rDriver, xmlreader::XmlReader& rReader)
{
int nLevel = 1;
bool bInMsg = false;
while (true)
{
xmlreader::Span name;
int nsId;
xmlreader::XmlReader::Result res
= rReader.nextItem(xmlreader::XmlReader::Text::Normalized, &name, &nsId);
if (res == xmlreader::XmlReader::Result::Begin)
{
++nLevel;
if (nLevel > 2)
throw InvalidFileException();
if (name == "msg")
{
bInMsg = true;
}
else if (name == "device")
{
int nsIdDeveice;
while (rReader.nextAttribute(&nsIdDeveice, &name))
{
if (name == "id")
{
name = rReader.getAttributeValue(false);
OString aDeviceId(name.begin, name.length);
rDriver.maDevices.push_back(
OStringToOUString(aDeviceId, RTL_TEXTENCODING_UTF8));
}
}
}
else
throw InvalidFileException();
}
else if (res == xmlreader::XmlReader::Result::End)
{
--nLevel;
bInMsg = false;
if (!nLevel)
break;
}
else if (res == xmlreader::XmlReader::Result::Text)
{
if (bInMsg)
{
OString sMsg(name.begin, name.length);
rDriver.maMsg = OStringToOUString(sMsg, RTL_TEXTENCODING_UTF8);
}
}
}
}
void Parser::handleEntry(DriverInfo& rDriver, xmlreader::XmlReader& rReader)
{
if (meBlockType == BlockType::ALLOWLIST)
{
rDriver.mbAllowlisted = true;
}
else if (meBlockType == BlockType::DENYLIST)
{
rDriver.mbAllowlisted = false;
}
else if (meBlockType == BlockType::UNKNOWN)
{
throw InvalidFileException();
}
xmlreader::Span name;
int nsId;
while (rReader.nextAttribute(&nsId, &name))
{
if (name == "os")
{
name = rReader.getAttributeValue(false);
OString sOS(name.begin, name.length);
rDriver.meOperatingSystem = getOperatingSystem(sOS);
}
else if (name == "vendor")
{
name = rReader.getAttributeValue(false);
OString sVendor(name.begin, name.length);
rDriver.maAdapterVendor = GetVendorId(sVendor);
}
else if (name == "compare")
{
name = rReader.getAttributeValue(false);
OString sCompare(name.begin, name.length);
rDriver.meComparisonOp = getComparison(sCompare);
}
else if (name == "version")
{
name = rReader.getAttributeValue(false);
OString sVersion(name.begin, name.length);
rDriver.mnDriverVersion = getVersion(sVersion);
}
else if (name == "minVersion")
{
name = rReader.getAttributeValue(false);
OString sMinVersion(name.begin, name.length);
rDriver.mnDriverVersion = getVersion(sMinVersion);
}
else if (name == "maxVersion")
{
name = rReader.getAttributeValue(false);
OString sMaxVersion(name.begin, name.length);
rDriver.mnDriverVersionMax = getVersion(sMaxVersion);
}
else
{
OString aAttrName(name.begin, name.length);
SAL_WARN("vcl.driver", "unsupported attribute: " << aAttrName);
}
}
handleDevices(rDriver, rReader);
}
void Parser::handleList(xmlreader::XmlReader& rReader)
{
xmlreader::Span name;
int nsId;
while (true)
{
xmlreader::XmlReader::Result res
= rReader.nextItem(xmlreader::XmlReader::Text::NONE, &name, &nsId);
if (res == xmlreader::XmlReader::Result::Begin)
{
if (name == "entry")
{
DriverInfo aDriver;
handleEntry(aDriver, rReader);
mrDriverList.push_back(aDriver);
}
else if (name == "entryRange")
{
DriverInfo aDriver;
handleEntry(aDriver, rReader);
mrDriverList.push_back(aDriver);
}
else
{
throw InvalidFileException();
}
}
else if (res == xmlreader::XmlReader::Result::End)
{
break;
}
}
}
void Parser::handleContent(xmlreader::XmlReader& rReader)
{
while (true)
{
xmlreader::Span name;
int nsId;
xmlreader::XmlReader::Result res
= rReader.nextItem(xmlreader::XmlReader::Text::NONE, &name, &nsId);
if (res == xmlreader::XmlReader::Result::Begin)
{
if (name == "allowlist")
{
meBlockType = BlockType::ALLOWLIST;
handleList(rReader);
}
else if (name == "denylist")
{
meBlockType = BlockType::DENYLIST;
handleList(rReader);
}
else if (name == "root")
{
}
else
{
throw InvalidFileException();
}
}
else if (res == xmlreader::XmlReader::Result::End)
{
if (name == "allowlist" || name == "denylist")
{
meBlockType = BlockType::UNKNOWN;
}
}
else if (res == xmlreader::XmlReader::Result::Done)
{
break;
}
}
}
static OperatingSystem getOperatingSystem()
{
#ifdef _WIN32
// OS version in 16.16 major/minor form
// based on http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx
switch (DriverBlocklist::GetWindowsVersion())
{
case 0x00060001:
return DRIVER_OS_WINDOWS_7;
case 0x00060002:
return DRIVER_OS_WINDOWS_8;
case 0x00060003:
return DRIVER_OS_WINDOWS_8_1;
case 0x000A0000: // Major 10 Minor 0
return DRIVER_OS_WINDOWS_10;
default:
return DRIVER_OS_UNKNOWN;
}
#elif defined LINUX
return DRIVER_OS_LINUX;
#else
return DRIVER_OS_UNKNOWN;
#endif
}
namespace
{
struct compareIgnoreAsciiCase
{
explicit compareIgnoreAsciiCase(OUString aString)
: maString(std::move(aString))
{
}
bool operator()(std::u16string_view rCompare)
{
return maString.equalsIgnoreAsciiCase(rCompare);
}
private:
OUString maString;
};
}
const uint64_t allDriverVersions = ~(uint64_t(0));
DriverInfo::DriverInfo()
: meOperatingSystem(DRIVER_OS_UNKNOWN)
, maAdapterVendor(GetVendorId(VendorAll))
, mbAllowlisted(false)
, meComparisonOp(DRIVER_COMPARISON_IGNORED)
, mnDriverVersion(0)
, mnDriverVersionMax(0)
{
}
DriverInfo::DriverInfo(OperatingSystem os, OUString vendor, VersionComparisonOp op,
uint64_t driverVersion, bool bAllowlisted,
const char* suggestedVersion /* = nullptr */)
: meOperatingSystem(os)
, maAdapterVendor(std::move(vendor))
, mbAllowlisted(bAllowlisted)
, meComparisonOp(op)
, mnDriverVersion(driverVersion)
, mnDriverVersionMax(0)
{
if (suggestedVersion)
maSuggestedVersion
= OStringToOUString(std::string_view(suggestedVersion), RTL_TEXTENCODING_UTF8);
}
bool FindBlocklistedDeviceInList(std::vector<DriverInfo>& aDeviceInfos, VersionType versionType,
std::u16string_view sDriverVersion,
std::u16string_view sAdapterVendorID,
OUString const& sAdapterDeviceID, OperatingSystem system,
const OUString& blocklistURL)
{
uint64_t driverVersion;
ParseDriverVersion(sDriverVersion, driverVersion, versionType);
bool match = false;
for (const auto& rDeviceInfo : aDeviceInfos)
{
bool osMatch = false;
if (rDeviceInfo.meOperatingSystem == DRIVER_OS_ALL)
osMatch = true;
else if (rDeviceInfo.meOperatingSystem == system)
osMatch = true;
else if (rDeviceInfo.meOperatingSystem == DRIVER_OS_WINDOWS_ALL
&& system >= DRIVER_OS_WINDOWS_FIRST && system <= DRIVER_OS_WINDOWS_LAST)
osMatch = true;
else if (rDeviceInfo.meOperatingSystem == DRIVER_OS_OSX_ALL && system >= DRIVER_OS_OSX_FIRST
&& system <= DRIVER_OS_OSX_LAST)
osMatch = true;
if (!osMatch)
{
continue;
}
if (!rDeviceInfo.maAdapterVendor.equalsIgnoreAsciiCase(GetVendorId(VendorAll))
&& !rDeviceInfo.maAdapterVendor.equalsIgnoreAsciiCase(sAdapterVendorID))
{
continue;
}
if (std::none_of(rDeviceInfo.maDevices.begin(), rDeviceInfo.maDevices.end(),
compareIgnoreAsciiCase(u"all"_ustr))
&& std::none_of(rDeviceInfo.maDevices.begin(), rDeviceInfo.maDevices.end(),
compareIgnoreAsciiCase(sAdapterDeviceID)))
{
continue;
}
switch (rDeviceInfo.meComparisonOp)
{
case DRIVER_LESS_THAN:
match = driverVersion < rDeviceInfo.mnDriverVersion;
break;
case DRIVER_LESS_THAN_OR_EQUAL:
match = driverVersion <= rDeviceInfo.mnDriverVersion;
break;
case DRIVER_GREATER_THAN:
match = driverVersion > rDeviceInfo.mnDriverVersion;
break;
case DRIVER_GREATER_THAN_OR_EQUAL:
match = driverVersion >= rDeviceInfo.mnDriverVersion;
break;
case DRIVER_EQUAL:
match = driverVersion == rDeviceInfo.mnDriverVersion;
break;
case DRIVER_NOT_EQUAL:
match = driverVersion != rDeviceInfo.mnDriverVersion;
break;
case DRIVER_BETWEEN_EXCLUSIVE:
match = driverVersion > rDeviceInfo.mnDriverVersion
&& driverVersion < rDeviceInfo.mnDriverVersionMax;
break;
case DRIVER_BETWEEN_INCLUSIVE:
match = driverVersion >= rDeviceInfo.mnDriverVersion
&& driverVersion <= rDeviceInfo.mnDriverVersionMax;
break;
case DRIVER_BETWEEN_INCLUSIVE_START:
match = driverVersion >= rDeviceInfo.mnDriverVersion
&& driverVersion < rDeviceInfo.mnDriverVersionMax;
break;
case DRIVER_COMPARISON_IGNORED:
// We don't have a comparison op, so we match everything.
match = true;
break;
default:
SAL_WARN("vcl.driver", "Bogus op in " << blocklistURL);
break;
}
if (match || rDeviceInfo.mnDriverVersion == allDriverVersions)
{
// white listed drivers
if (rDeviceInfo.mbAllowlisted)
{
SAL_INFO("vcl.driver", "allowlisted driver");
return false;
}
match = true;
if (!rDeviceInfo.maSuggestedVersion.isEmpty())
{
SAL_WARN("vcl.driver", "use : " << rDeviceInfo.maSuggestedVersion);
}
break;
}
}
SAL_INFO("vcl.driver", (match ? "denylisted" : "not denylisted") << " in " << blocklistURL);
return match;
}
bool IsDeviceBlocked(const OUString& blocklistURL, VersionType versionType,
std::u16string_view driverVersion, std::u16string_view vendorId,
const OUString& deviceId)
{
std::vector<DriverInfo> driverList;
Parser parser(blocklistURL, driverList, versionType);
if (!parser.parse())
{
SAL_WARN("vcl.driver", "error parsing denylist " << blocklistURL);
return false;
}
return FindBlocklistedDeviceInList(driverList, versionType, driverVersion, vendorId, deviceId,
getOperatingSystem(), blocklistURL);
}
#ifdef _WIN32
int32_t GetWindowsVersion()
{
static int32_t winVersion = []() {
// GetVersion(Ex) and VersionHelpers (based on VerifyVersionInfo) API are
// subject to manifest-based behavior since Windows 8.1, so give wrong results.
// Another approach would be to use NetWkstaGetInfo, but that has some small
// reported delays (some milliseconds), and might get slower in domains with
// poor network connections.
// So go with a solution described at https://msdn.microsoft.com/en-us/library/ms724429
HINSTANCE hLibrary = LoadLibraryW(L"kernel32.dll");
if (hLibrary != nullptr)
{
wchar_t szPath[MAX_PATH];
DWORD dwCount = GetModuleFileNameW(hLibrary, szPath, SAL_N_ELEMENTS(szPath));
FreeLibrary(hLibrary);
if (dwCount != 0 && dwCount < SAL_N_ELEMENTS(szPath))
{
dwCount = GetFileVersionInfoSizeW(szPath, nullptr);
if (dwCount != 0)
{
std::unique_ptr<char[]> ver(new char[dwCount]);
if (GetFileVersionInfoW(szPath, 0, dwCount, ver.get()) != FALSE)
{
void* pBlock = nullptr;
UINT dwBlockSz = 0;
if (VerQueryValueW(ver.get(), L"\\", &pBlock, &dwBlockSz) != FALSE
&& dwBlockSz >= sizeof(VS_FIXEDFILEINFO))
{
VS_FIXEDFILEINFO* vinfo = static_cast<VS_FIXEDFILEINFO*>(pBlock);
return int32_t(vinfo->dwProductVersionMS);
}
}
}
}
}
return 0;
}();
return winVersion;
}
#endif
} // namespace
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */