New Features
-
Extend
ALTERNATIVE_LOCATIONS
for per-user installations
Whengit
is not found in aPATH
search on Windows, common
locations where Git for Windows is often installed are checked.
But only systemwide installations had been checked before.This extends the locations examined to include the current user's
own program files directory. This directory, if present, is
expected to be thePrograms
subdirectory of the user's local
application data directory, typically:C:\Users\<user>\AppData\Local\Programs
When Git for Windows is installed for a user rather than
systemwide, it is typically installed aGit
subdirectory of that
Programs
directory (much as it is typically installed in aGit
subdirectory of a directory such asC:\Program Files
when
installed systemwide). This looks for a suitable Git for Windows
directory tree at such a location.It is possible for Git for Windows installations to be present both
in the current user's own program files directory and systemwide.
If that happens, such that both kinds of installations are able to
be found, then we choose the per-user installation.This is based on the idea that an individual user may choose to
install a newer or otherwise different version of Git for Windows,
or that a developer may even test out a custom build by manually
placing it in that directory. Because, in either case, the
architecture of the Git for Windows executable may differ from what
is currently installed systemwide or even from what is typically
preferred, we will attempt to use a per-user build of any of the
architectures Git for Windows has published, before then using the
systemwide installations as done before.Although the user program files directory is under the local rather
than roaming application data directory, and is thus not shared
across machines on a domain, it is possible that some techniques of
backing up and restoring data may restore a per-user installation
of Git for Windows that is for a different machine architecture
that cannot run, or that the user would not want to be used. In
that case, this enhancement may actually break something that was
working before.That seems fairly unlikely to occur, and it can be worked around by
making agit
command available in aPATH
search. Nonetheless,
if this is found to happen in practice, then further refinement of
theALTERNATIVE_LOCATIONS
enumeration order may be warranted. -
Extend
EXEPATH
optimization to support ARM64 Windows
On Windows, thegix_path::env::system_prefix()
function has two
strategies. The first is an optimization that doesn't always work,
where theEXEPATH
environment variable is consulted. In Git Bash,
this variable is usually available, pointing somewhere into the
Git for Windows installation. Most commonly, but in practice not
universally, it points to the top-level directory of the Git for
Windows installation.system_prefix()
on Windows looks for a
subdirectory of this directory that is named for one of the MSYS2
environment https://www.msys2.org/docs/environments prefixes, of
those Git for Windows has builds for. (Otherwise, it falls back to
traversing to such a location upward from whatgit --exec-path
reveals, which is often slower asgit
must sometimes be run.)However, this
EXEPATH
optimization had only checked for the
mingw64
andmingw32
environment prefixes. This was ideal
before ARM64 builds of Git for Windows were released. But since
then, it is useful to allow theclangarm64
environment prefix
as well, to allow theEXEPATH
optimization to work on ARM64
builds of Git for Windows. This makes that change.
Bug Fixes
-
Don't use
EXEPATH
unless it is absolute
The first of two strategies used by thesystem_prefix()
function
on Windows is an optimization that looks forclangarm64
,
mingw64
, ormingw32
subdirectories of a directory whose path is
given by the value of theEXEPATH
environment variable. If
EXEPATH
is absent, can't be interpreted as a path, or can be
interpreted as a path but does not point to a directory that can be
verified to contain such a subdirectory, then this optimization was
already being skipped and the more reliable but sometimes slower
git --exec-path
-based method used.However, when
EXEPATH
contains a relative path, including in the
case where it is an empty string (which could be set by accident or
if it is being used with some meaning other than what we and Git
for Windows recognize), then it was still used, even though that
would not usually be correct and would sometimes not be safe.Although an attacker is not expected to be able to control the
value ofEXEPATH
(nor most environment variables), a value such
as an empty string would causeclangarm64
,mingw64
, and
mingw32
subdirectories of the current directory to be used.A user might intentionally set
EXEPATH
to a relative path to
leverage theEXEPATH
optimization ofsystem_prefix()
with that
path. ButEXEPATH
is a short variable name with many plausible
meanings that is not named with a distinctive prefix suchGIT_
or
GIX_
. So it would not be a good tradeoff to continue recognizing
it forsystem_prefix()
anytime it is non-absoliute.Thus, as a stability fix and possible security enhancement, this
adds a check that the value ofEXEPATH
is an absolute path before
proceeding with theEXEPATH
optimization. -
Skip
EXEPATH
optimization if it finds no best path
TheEXEPATH
optimization forgix_path::env::system_prefix()
on
Windows previously tried https://www.msys2.org/docs/environments
prefixes used by Git for Windows, returning a successful result on
the first subdirectory found. However, ifEXEPATH
points to a
directory that contains more than one such subdirectory, then the
subdirectory returned would not necessarily be the correct one.Getting more than one subdirectory is unlikely. It is expected that
at most one ofclangarm64
,mingw64
, andmingw32
will be
present. However, there are at least three ways it could happen:-
EXEPATH
has the intended meaning as the root of a Git for
Windows installation, but the directory it refers to contains
multiple directories left over from a previous installation. A
corresponding scenario applies toALTERNATIVE_LOCATIONS
, but
it is resolved by checking all the directories in a reasonable
order. Here, we are not checking the contents of the directories,
so no matter what order we look for them in, picking one when
there are others risks picking the wrong one. -
EXEPATH
has the intended meaning as the root of a Git for
Windows installation, but it is a custom installation produced
from local build or custom distribution rather than an official
build, and it contains multiple such directories because it
carries binaries built against more than one of the targets (even
if only one of them hasgit
itself). A corresponding scenario
is fairly unlikely forALTERNATIVE_LOCATIONS
, because a custom
MSYS2 tree, even if created using the Git for Windows SDK, would
probably not be installed in one of the common Git for Windows
installation locations, without considering the effects of doing
so. In contrast,EXEPATH
will often be set in a Git Bash
environment even in a highly customized Git for Windows tree. -
EXEPATH
has a different meaning from what is intended. For
example, the user might set it to the root of an ordinary MSYS2
installation. (Note that it is also likely to have various other
meanings even more different from these, but those won't likely
cause theEXEPATH
optimization to be used when it shouldn't,
because most possible meanings ofEXEPATH
won't involve a
subdirectory of any of the names we look for.
Instead of using the first existing subdirectory we fine, this
checks for all of them and requires that exactly one exist. If more
than one exist, then that is now treated the same as if none exist,
falling back to thegit --exec-path
-based strategy, which is
sometimes slower but more robust. -
-
Extend
ALTERNATIVE_LOCATIONS
for ARM64 Windows
gix-path
looks for and runs an installedgit
executable, when
present, to discover information about how Git is set up. This
is used in several functions ingix_path::env
, especially on
Windows, where ifgit.exe
is not found in aPATH
search, then
common installation locations for Git for Windows are checked.These locations on Windows are resolved based on information from
the current environment, since different systems have different
program files directories. This was implemented in #1456 (which
built on #1419). Although this was sufficient to find the most
common Git for Windows installation locations, it did not find
ARM64 builds of Git for Windows. Such builds place the non-shim
git.exe
program in(git root)\clangarm64\bin
, rather than in
(git root)\mingw64\bin
or(git root)\mingw32\bin
. At the time
of #1419 and #1456, no stable ARM64 builds of Git for Windows were
available. Since then, Git for Windows began releasing such builds.This modifies the alternative locations examined if
git.exe
is
not found in aPATH
search on Windows so that, where(git root)
is in a 64-bit program files directory, we check for a
(git root)\clangarm64\bin
directory, in addition to checking for
a(git root)\mingw64\bin
directory as was already done.Although 64-bit and 32-bit program files directories are separate,
on ARM64 systems the 64-bit program files directory is used both
for ARM64 programs, which the system can run directly, and for
x86_64 programs, which the system must run through emulation.This checks both
clangarm64
andmingw64
, wheremingw64
was
checked before. It does so in that order, because if both are
available, then we are probably on an ARM64 system, and the ARM64
build of Git for Windows should be preferred, both because it will
tend to perform better and because the user is likely to expect
that to be used. (An x86_64 build, especially if present directly
alongside an ARM64 build, may be left over from a previous version
of Git for Windows that didn't have ARM64 builds and that was only
incompletely uninstalled.)This checks both, in that order, on all systems where we had
checkedmingw64
before, even on x86_64 systems. This is because:-
To limit production dependencies and code complexity, we have
been examining only environment variables (and information
available at build time) to ascertain which program files
directories exist and whether they are 64-bit or 32-bit program
files directories. At least for now, this preserves that general
approach, continuing not to explicitly call Windows API functions
or access the Windows registry, other than in tests. -
But determining from environment variables whether the system is
ARM64 or x86_64 is less straightforward than determining the
program files directory locations, in one major case as well as
various edge cases.
The reason it's less straightforward is that, if our parent process
(or other ancestor) passes down a sanitized environment while still
attempting to let the program files directories be found, then:-
That process should make available all of the
ProgramFiles
,
ProgramW6432
,ProgramFiles(x86)
, andProgramFiles(ARM)
variables that exist in its own environment.(This is because, on 64-bit Windows, the child
ProgramFiles
is
populated from the parentProgramW6432
,ProgramFiles(x86)
, or
ProgramFiles(ARM)
, depending on the architectures of the parent
and child, if the parent passes down the relevant variable.) -
Even if the parent/ancestor is not designed with the WoW64 rules
in mind, it will likely pass down at leastProgramFiles
.This will then be used as the child
ProgramFiles
, if whichever
ofProgramFilesW6432
,ProgramFiles(x86)
, or
ProgramFiles(ARM)
is relevant is not passed down by the parent. -
In contrast, the parent/ancestor may omit the variables
PROCESSOR_ARCHITECTURE
andPROCESSOR_ARCHITEW6432
, which are
not obviously needed for locating programs.In and of itself, this is not necessarily a problem, because on
ARM64 Windows, at least one of the two will still be set
automatically. (Even if the parent process runs us with an
explicitly empty environment, we will get one of these, on ARM64
Windows.)However, in this case, as in others, it will generally be set
according to our process architecture, rather than the system
architecture. -
Thus, even if
PROCESSOR_ARCHITE*
variables are preserved or set
correctly, there are common cases where they are not sufficient.The main such case is when we are an x86_64 build, but the system
is ARM64, and Git for Windows is ARM64.gix-path
is a library
crate, and it will sometimes to be used in an x86_64 program on
such a system.Also, if
gix-path
is used in an Arm64EC program, then even
though its code may be ARM64 with thearm64ec-pc-windows-msvc
Rust target, the program would still be treated as an x86_64
program in its interactions with the system and other programs,
including in how its environment variables' values are populated
and how their values are affected by WoW64.(
gix-path
may also be x86_64 code where the program is Arm64EC.
One way this happens is ifgix-path
is used in an x86_64 DLL,
and an Arm64EC program loads the DLL. Using x86_64 DLLs from
ARM64 code is one of the reasons a program may target Arm64EC.
In this case, the Rust target will be for x86_64, not ARM64 or
Arm64EC. So checking our own Rust build target can't fully check
if the program is Arm64EC.) -
Although the
PROCESSOR_IDENTIFIER
variable is more reliable if
present--see actions/partner-runner-images#117 for an example of
where this is more reliable thanPROCESSOR_ARCHITECTURE
--it is
slightly more complex to parse. Much more importantly, unlike
PROCESSOR_ARCHITE*
, thePROCESSOR_IDENTIFIER
variable is not
set automatically in a child process whose parent/ancestor has
removed it. Whether or not a parent/ancestor passes down
PROCESSOR_ARCHITE*
variables, it may still not choose to let
PROCESSOR_IDENTIFIER
through, if it sanitizes the environment. -
It would sometimes work to look for
ProgramFiles(ARM)
. This
environment variable, if present, gives the 32-bit ARM program
files directory location on an ARM64 Windows system. If set, then
the Windows system could be treated as ARM64. However, a
parent/ancestor process may omit this even when passing down
program files related environment variables, including
ProgramFiles(x86)
. It may do so because theProgramFiles(ARM)
environment variable is less well known. Or it may omit it
intentionally, if the parent process is only passing down
variables needed to find Git for Windows, for which one shouldn't
need to know the 32-bit ARM program files directory location,
since Git for Windows has never had 32-bit ARM builds.Relatedly, augmenting the search by checking the filesystem for a
sibling (or hard-coded) directory namedProgram Files (ARM)
should not be done, because this directory has no special meaning
outside of ARM64 Windows. It may therefore be left over from a
previous installation or migration, or even created by a local
user as in the CVE-2024-40644 scenario patched in #1456.
(These complexities all relate to deciding whether and in what
order to searchbin
subdirectories ofclangarm64
,mingw64
,
andmingw32
. They would go away if we looked for the shim rather
than the non-shim executable. This is because the path from
(git root)
to the shim does not contain a directory component
named after a build target. That simplification would carry its own
tradeoffs, and it is unclear if it ought to be done; it is not done
here.) -
-
Use
nul
instead ofNUL
on Windows
NULL_DEVICE
is/dev/null
except on Windows, where there are
several possible choices. Previously we were usingNUL
because
the more modern full path\\.\NUL
is not supported bygit
.However,
git
also rejectsNUL
, when capitalized, on some
Windows systems. This can be observed on Windows 11 ARM64 builds.
In contrast, the lower-casenul
, which Windows itself treats the
same asNUL
, is always accepted by Git for Windows.Although it's not readily apparent why Git for Windows accepts
NUL
on some Windows operating systems and/or platforms and not
others, the preferential treatment ofnul
(not extending to
NUL
) can be seen in a few places in the Git for Windows source
code, including inmingw_access
(strcmp
is case-sensitive):
Commit Statistics
- 39 commits contributed to the release over the course of 79 calendar days.
- 79 days passed between releases.
- 6 commits were understood as conventional.
- 0 issues like '(#ID)' were seen in commit messages
Commit Details
view details
- Uncategorized
- Merge pull request #2217 from GitoxideLabs/copilot/update-msrv-to-rust-1-82 (4da2927)
- Update MSRV to 1.82 and replace once_cell with std equivalents (6cc8464)
- Merge pull request #2202 from GitoxideLabs/dependabot/cargo/cargo-4a7155215a (9365cc3)
- Bump the cargo group across 1 directory with 64 updates (838ff95)
- Merge pull request #2134 from EliahKagan/user-program-files (2fffc50)
- Extend
ALTERNATIVE_LOCATIONS
for per-user installations (7e77d40) - Migrate remaining
known-folders
usage towindows
(082e22e) - Clarify safety at and around
SHGetKnownFolderPath
call (998fb48) - Get
UserProgramFiles
withKF_FLAG_DONT_VERIFY
for test (1c7a34e) - Update alternative location unit tests for user program files (c9ff0ac)
- Fix user program files alternative locations expectations (cab4c85)
- Update alternative locations integration tests for user program files (9e71d55)
- Merge pull request #2133 from EliahKagan/program-files-next (e365244)
- Add a "SAFETY:" comment in
PlatformBitness::current()
(1f3edb5) - Move the vacuous case outside the "ordinary" group (eec407f)
- Rename cases to clarify "ordinary" and "strange" (edc0f3c)
- Divide up
locations_under_program_files_*
assertions (35beea1) - Clarify which global program files paths are used and why (fd000f5)
- Merge pull request #2115 from EliahKagan/run-ci/arm-windows (c2c8c2f)
- Don't use
EXEPATH
unless it is absolute (9000a84) - Test that relative
EXEPATH
doesn't trigger the optimization (f4bb773) - Factor out shared code to a helper (84f9672)
- Test that empty
EXEPATH
doesn't trigger the optimization (24c11ac) - Skip
EXEPATH
optimization if it finds no best path (5ac8cff) - Extend
EXEPATH
optimization to support ARM64 Windows (2d2c11b) - Refactor slices in test cases for readability (d5f4c9f)
- Test the
EXEPATH
optimization ofsystem_prefix()
(2b0639b) - Extract
system_prefix()
Windows strategies to helpers (3b304fa) - Update outdated
expect()
message in test helper (571ca6e) - Rename
PlatformArchitecture
helperPlatformBitness
(5bf265b) - Further refactor
rules
loop inlocations_under_program_files
(2c6dcb2) - Clarify
rules
loop inlocations_under_program_files
(d06b89d) - Extend
ALTERNATIVE_LOCATIONS
for ARM64 Windows (1fa24cd) - Tweak formatting to fix lint (4662233)
- Update alternative locations unit tests for ARM64 (ff3df53)
- Update alternative locations integration tests for ARM64 (e9770a7)
- Copyedit
gix_path::env::git
comments (8d2c262) - Use
nul
instead ofNUL
on Windows (b24783a) - Merge pull request #2100 from GitoxideLabs/release (202bc6d)