[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,39 @@
#ifndef PAYLOAD_PATHS_H
#define PAYLOAD_PATHS_H
#include <string>
/**
* @brief The last name in @p path: what follows its last separator, or @p path itself when
* it has none.
*
* Empty for an empty path and for one that ends in a separator, which has no name of its
* own - a caller that hands in a directory there gets no name rather than the directory's
* own last component.
*/
std::wstring fileNameOf(const std::wstring &path);
/**
* @brief The directory @p path lies in, without a trailing separator.
*
* Empty when @p path has no directory part. Used to tell where a process was started from:
* an image running directly in the install directory has that directory as its parent,
* while one started from a folder below it has that folder, and the two are not the same
* answer to "does this process belong to the installation being replaced?".
*/
std::wstring parentDirectoryOf(const std::wstring &path);
/**
* @brief Whether @p imageName is one of the programs the payload installs.
*
* The single place that answers which images are Cockatrice's own, so the guard acts on the
* same set it names, and so a program a user happens to keep in the install directory is
* never treated as an instance of the application. Matched on the image name alone, without
* case: the names are the payload's own, and the comparison is deliberately free of Windows
* API so it can be exercised off Windows.
*
* @param imageName the file name of a process image, empty when it could not be read
*/
bool isPayloadProgram(const std::wstring &imageName);
#endif