[Installer] Name only the payload programs installed in the directory (#7684)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Desktop / Prune caches (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / Ubuntu 26.04 cover (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled

The running-instance lookup named every process whose image path began with the
install directory, subfolders included. Installing into a folder that holds
another application in a subfolder therefore reported that application as
holding the previous version, and nothing the guard offers could clear it: the
close asks cockatrice.exe only, and the end answer refuses anything that is not
a payload program. Both answers were no-ops, so the update could only abort and
tell the user to close a program that was never touching the files.

Name only the programs the payload installs, and only when the image lies in the
install directory itself. A program below the directory that really does hold a
payload file is still named by the Restart Manager lookup, which is the lookup
for that question.

The Windows probe check now covers a payload-named program in a subfolder and a
program the payload does not install; both have to come back as nothing.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
sentry[bot] 2026-10-30 13:16:50 +01:00 • committed by GitHub
parent ded83aaab5
commit 0369aaffa9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 279 additions and 89 deletions

View file

@ -0,0 +1,52 @@
#include "payload_paths.h"
#include <algorithm>
#include <cstddef>
#include <iterator>
namespace
{
/// The payload programs, by image name. A process running one of these is what the guard
/// exists to name, so askingProcessIds() stops its walk up the parent chain at one.
constexpr const wchar_t *PAYLOAD_PROGRAMS[] = {L"cockatrice.exe", L"oracle.exe", L"servatrice.exe"};
/// Compares two file names the way the file system does, without a locale.
///
/// Only the names above are ever compared against, and all of them are ASCII, so folding
/// the ASCII letters is the whole answer. The Windows ordinal comparison is not used here
/// on purpose: this file is compiled into a test that runs everywhere, and the question
/// "is this image a program the payload installs?" must be answerable without Windows.
bool equalsIgnoringAsciiCase(const std::wstring &left, const std::wstring &right)
{
if (left.size() != right.size()) {
return false;
}
return std::equal(left.begin(), left.end(), right.begin(), [](wchar_t a, wchar_t b) {
const wchar_t foldedA = a >= L'A' && a <= L'Z' ? static_cast<wchar_t>(a - L'A' + L'a') : a;
const wchar_t foldedB = b >= L'A' && b <= L'Z' ? static_cast<wchar_t>(b - L'A' + L'a') : b;
return foldedA == foldedB;
});
}
} // namespace
std::wstring fileNameOf(const std::wstring &path)
{
const std::size_t separator = path.find_last_of(L"\\/");
return separator == std::wstring::npos ? path : path.substr(separator + 1);
}
std::wstring parentDirectoryOf(const std::wstring &path)
{
const std::size_t separator = path.find_last_of(L"\\/");
return separator == std::wstring::npos ? std::wstring() : path.substr(0, separator);
}
bool isPayloadProgram(const std::wstring &imageName)
{
for (const wchar_t *program : PAYLOAD_PROGRAMS) {
if (equalsIgnoringAsciiCase(imageName, program)) {
return true;
}
}
return false;
}