Bug 144405 - cli_cppuhelper policy installed in GAC for wrong architecture
Summary: cli_cppuhelper policy installed in GAC for wrong architecture
Status: UNCONFIRMED
Alias: None
Product: LibreOffice
Classification: Unclassified
Component: Installation (show other bugs)
Version:
(earliest affected)
7.2.0.4 release
Hardware: x86-64 (AMD64) Windows (All)
: medium normal
Assignee: Not Assigned
URL:
Whiteboard:
Keywords:
Depends on:
Blocks: Installer-Windows
  Show dependency treegraph
 
Reported: 2021-09-09 10:42 UTC by Kalle Niemitalo
Modified: 2023-08-22 10:50 UTC (History)
6 users (show)

See Also:
Crash report or crash signature:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Kalle Niemitalo 2021-09-09 10:42:13 UTC
Description:
LibreOffice_7.2.0_Win_x64.msi (LibreOffice 7.2.0.4) on Windows 10 x64 installs CLI Language Binding assemblies to the Global Assembly Cache.  The problem is that policy.1.0.cli_cppuhelper is installed to GAC_32 but cli_cppuhelper itself is installed to GAC_64.  A .NET Framework x64 application that references cl_cppuhelper will then ignore the policy and fail to load cli_cppuhelper from the GAC unless the application references exactly the installed version.

Steps to Reproduce:
1. Install LibreOffice_7.2.0_Win_x64.msi on Windows 10 x64.  (Do not install the 32-bit version.)
2. Start Windows PowerShell 5.1 (64-bit).  (Do not use PowerShell Core 6 or PowerShell 7, because those do not run on .NET Framework.)
3. Execute in PowerShell: [System.Reflection.Assembly]::Load("cli_cppuhelper, Version=1.0.22.0, Culture=neutral, PublicKeyToken=ce2cb7e279207b9e, processorArchitecture=AMD64")

Actual Results:
.NET Framework ignores the binding redirection in policy.1.0.cli_cppuhelper and tries to load version 1.0.22.0, which has not been installed:

Exception calling "Load" with "1" argument(s): "Could not load file or assembly 'cli_cppuhelper, Version=1.0.22.0, Culture=neutral, PublicKeyToken=ce2cb7e279207b9e' or one of its dependencies. The system cannot
 find the file specified."
At line:1 char:1
+ [System.Reflection.Assembly]::Load("cli_cppuhelper, Version=1.0.22.0, ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : FileNotFoundException

Expected Results:
.NET Framework should use the binding redirection in policy.1.0.cli_cppuhelper and load version 1.0.23.0 instead:

GAC    Version        Location
---    -------        --------
True   v4.0.30319     C:\WINDOWS\Microsoft.Net\assembly\GAC_64\cli_cppuhelper\v4.0_1.0.23.0__ce2cb7e279207b9e\cli_cppuhelper.dll


Reproducible: Always


User Profile Reset: No



Additional Info:
Version: 7.2.0.4 (x64) / LibreOffice Community
Build ID: 9a9c6381e3f7a62afc1329bd359cc48accb6435b
CPU threads: 8; OS: Windows 10.0 Build 19043; UI render: Skia/Vulkan; VCL: win
Locale: fi-FI (fi_FI); UI: en-US
Calc: threaded

policy.1.0.cli_cppuhelper is in GAC_32 but cli_cppuhelper itself is in GAC_64:

C:\>dir /s /b C:\Windows\Microsoft.NET\assembly\*cli_*.dll
C:\Windows\Microsoft.NET\assembly\GAC_32\policy.1.0.cli_cppuhelper\v4.0_23.0.0.0__ce2cb7e279207b9e\policy.1.0.cli_cppuhelper.dll
C:\Windows\Microsoft.NET\assembly\GAC_64\cli_cppuhelper\v4.0_1.0.23.0__ce2cb7e279207b9e\cli_cppuhelper.dll
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\cli_basetypes\v4.0_1.0.20.0__ce2cb7e279207b9e\cli_basetypes.dll
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\cli_oootypes\v4.0_1.0.9.0__ce2cb7e279207b9e\cli_oootypes.dll
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\cli_ure\v4.0_1.0.23.0__ce2cb7e279207b9e\cli_ure.dll
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\cli_uretypes\v4.0_1.0.9.0__ce2cb7e279207b9e\cli_uretypes.dll
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\policy.1.0.cli_basetypes\v4.0_20.0.0.0__ce2cb7e279207b9e\policy.1.0.cli_basetypes.dll
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\policy.1.0.cli_oootypes\v4.0_9.0.0.0__ce2cb7e279207b9e\policy.1.0.cli_oootypes.dll
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\policy.1.0.cli_ure\v4.0_23.0.0.0__ce2cb7e279207b9e\policy.1.0.cli_ure.dll
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\policy.1.0.cli_uretypes\v4.0_9.0.0.0__ce2cb7e279207b9e\policy.1.0.cli_uretypes.dll

policy.1.0.cli_cppuhelper contains this binding redirection:

      <dependentAssembly>
        <assemblyIdentity name="cli_cppuhelper" publicKeyToken="ce2cb7e279207b9e"/>
        <bindingRedirect oldVersion="1.0.0.0-1.0.22.0" newVersion="1.0.23.0" />
      </dependentAssembly>

When .NET Framework tries to load e.g. version 1.0.22.0 of cli_cppuhelper, this policy should make it load version 1.0.23.0 instead. However, it does not take effect in x64 processes because the policy is in the wrong directory. Therefore, a .NET Framework x64 application that references cl_cppuhelper will fail to load it from the GAC unless the application references exactly the installed version.

I have found two mistakes in the setup:

1. The policy.1.0.cli_cppuhelper.dll file has been built for x86; its CLR header has the 32BITREQUIRED flag, and evaluating [System.Reflection.AssemblyName]::GetAssemblyName("C:\Windows\Microsoft.NET\assembly\GAC_32\policy.1.0.cli_cppuhelper\v4.0_23.0.0.0__ce2cb7e279207b9e\policy.1.0.cli_cppuhelper.dll").ProcessorArchitecture in Windows PowerShell 5.1 returns X86. (In contrast, the same for "C:\Windows\Microsoft.NET\assembly\GAC_64\cli_cppuhelper\v4.0_1.0.23.0__ce2cb7e279207b9e\cli_cppuhelper.dll" returns Amd64.) I don't know which file in the source tree specifies the incorrect architecture for this.

2. ORCA shows that the MsiAssemblyName table of the MSI package has processorArchitecture=x86 for both cli_cppuhelper and policy.1.0.cli_cppuhelper. These should be amd64 instead. This mismatch does not seem to affect where Fusion installs the files, but it may prevent gacutil /lr cli_cppuhelper from showing that the assembly is referenced by a Windows Installer package. I suspect this mismatch originates from scp2/source/ooo/ure.scp, which unconditionally specifies ProcessorArchitecture = "x86" for both gid_File_Lib_Cli_Cppuhelper_Assembly and gid_File_Lib_Policy_Cli_Cppuhelper_Assembly.
Comment 1 Kalle Niemitalo 2021-09-09 10:48:13 UTC
This bug might not affect applications running on .NET Core or .NET 5 or 6, because assembly resolution is so different there. But I don't know whether the CLI Language Binding assemblies of LibreOffice support those frameworks in other respects.
Comment 2 Kalle Niemitalo 2021-09-09 10:57:27 UTC
According to Bug #94265 comment #22, nightly builds do not register their files the same way as releases, unless given a specific parameter. I don't know whether that is still true. Anyway, if you test this bug with a nightly build and the assembly won't load even when you specify the correct version, then that may be the reason.
Comment 3 Kalle Niemitalo 2021-09-09 11:07:58 UTC
Added the difficultyMedium keyword because fixing this will require understanding Windows and studying the rather complex build system, but it will not need any C++ language skills and the changes might be just a dozen lines.
Comment 4 Kalle Niemitalo 2021-09-09 14:28:21 UTC
The assembly version of cli_cppuhelper is defined in cli_ure/version/version.txt. It was incremented to 1.0.23.0 for bug #108709 in August 2017, four years ago. That was then released in LibreOffice 6.0.0.1, and now LibreOffice 7.2.0.4 and 7.2.1.2 are still using the same assembly version. I think that means it is unlikely to be incremented again any time soon. Therefore, an application that references cli_cppuhelper version 1.0.23.0 will probably keep working even if this bug is not fixed and LibreOffice is upgraded.
Comment 5 Julien Nabet 2021-09-10 19:01:24 UTC
Michael: Since it's related to Windows internals, thought you might be interested in this one.

Stephan: you might also be interested in this one since it's related with UNO
Comment 6 Mike Kaganski 2021-09-11 19:30:37 UTC
Thanks for the detailed report!

(In reply to Kalle Niemitalo from comment #3)
> Added the difficultyMedium keyword

This is not needed: "difficultyFoo" are only for so-called easy hacks.

@Stephan: I suppose that cli_cppuhelper can't be "cpu-neutral"; if so, should we bundle two versions of cli_cppuhelper (and related policy), one for 32-bit processes, and one for 64-bit, each in respective GAC? Or does CLI allow to have only one such library, and use it in all processes (32/64/neutral)?
Comment 7 Stephan Bergmann 2021-09-13 07:22:20 UTC
(In reply to Mike Kaganski from comment #6)
> @Stephan: I suppose that cli_cppuhelper can't be "cpu-neutral"; if so,
> should we bundle two versions of cli_cppuhelper (and related policy), one
> for 32-bit processes, and one for 64-bit, each in respective GAC? Or does
> CLI allow to have only one such library, and use it in all processes
> (32/64/neutral)?

I know very little about CLI, the CLI UNO bridge, GAC, etc.  That said, the cli_cppuhelper.dll is built by cli_ure/CliNativeLibrary_cli_cppuhelper.mk, so I would naively assume that it contains native (x86 vs. x86-64) code.

My guess is that places that indicate 32-bit x86 rather than 64-bit x86-64/amd64 even for a 64-bit LO build on Windows are accidental leftovers that were not properly adapted when that 64-bit configuration was added back in the day.

I don't see a reason to bundle two versions of cli_cppuhelper.  I would assume that users who want to interact with LO from a 32-bit CLI environment install the 32-bit LO version, and users who want to interact from a 64-bit CLI environment install the 64-bit version.
Comment 8 Michael Stahl (allotropia) 2021-09-13 09:34:53 UTC
> 1. The policy.1.0.cli_cppuhelper.dll file has been built for x86; its CLR header has the 32BITREQUIRED flag, and evaluating 

hmm... there is a function gb_CliAssemblyTarget_set_platform that would cause a "-platform" argument to be passed but it has never been used in the build system.

probably only the native code library would require it? guess it should be set from the constructor gb_CliNativeLibrary_CliNativeLibrary then...

(... and java's model of putting .dll files into jars is so much easier to understand than this overengineered CLR nightmare of policy dlls, keyfiles, GAC, DLLs that may be native or may be portable, etc.)

> the assembly is referenced by a Windows Installer package. I suspect this
> mismatch originates from scp2/source/ooo/ure.scp, which unconditionally
> specifies ProcessorArchitecture = "x86" for both
> gid_File_Lib_Cli_Cppuhelper_Assembly and
> gid_File_Lib_Policy_Cli_Cppuhelper_Assembly.

sounds likely.

(In reply to Kalle Niemitalo from comment #4)
> The assembly version of cli_cppuhelper is defined in
> cli_ure/version/version.txt. It was incremented to 1.0.23.0 for bug #108709
> in August 2017, four years ago. That was then released in LibreOffice
> 6.0.0.1, and now LibreOffice 7.2.0.4 and 7.2.1.2 are still using the same
> assembly version. I think that means it is unlikely to be incremented again
> any time soon. Therefore, an application that references cli_cppuhelper
> version 1.0.23.0 will probably keep working even if this bug is not fixed
> and LibreOffice is upgraded.

well the idea was that release engineering would update the version numbers, but it looks like that didn't happen.
Comment 9 Kalle Niemitalo 2021-09-17 12:57:48 UTC
(In reply to Stephan Bergmann from comment #7)

> cli_cppuhelper.dll is built by cli_ure/CliNativeLibrary_cli_cppuhelper.mk,
> so I would naively assume that it contains native (x86 vs. x86-64) code.

It contains both native code and managed code, and the C++/CLI compiler may have hardcoded values that depend on sizeof(void*) in each.

> My guess is that places that indicate 32-bit x86 rather than 64-bit
> x86-64/amd64 even for a 64-bit LO build on Windows are accidental leftovers
> that were not properly adapted when that 64-bit configuration was added back
> in the day.

That is my guess as well.

> I don't see a reason to bundle two versions of cli_cppuhelper.  I would
> assume that users who want to interact with LO from a 32-bit CLI environment
> install the 32-bit LO version, and users who want to interact from a 64-bit
> CLI environment install the 64-bit version.

If a user needs to access LibreOffice from both x86 and amd64 CLI processes, then that is currently cumbersome, because the x86 and amd64 builds of LibreOffice apparently cannot be installed side by side.  It could thus be useful to have both x86 and amd64 binaries of cli_cppuhelper and policy.1.0.cli_cppuhelper in the amd64 LibreOffice package.  I think that can be handled as a separate feature request, though.

(Installing the 7.2.0.4 x86 build uninstalled the amd64 build, but the uninstallation left the amd64 binary of cli_cppuhelper in the GAC.  I don't yet know whether it will be removed after the next reboot or whether it was permanently leaked because of the incorrect data in the MsiAssemblyName table.)

(In reply to Michael Stahl (allotropia) from comment #8)

> hmm... there is a function gb_CliAssemblyTarget_set_platform that would
> cause a "-platform" argument to be passed but it has never been used in the
> build system.
> 
> probably only the native code library would require it? guess it should be
> set from the constructor gb_CliNativeLibrary_CliNativeLibrary then...

As of libreoffice-7.2.0.4, gb_CliNativeLibrary_CliNativeLibrary calls $(call gb_CliAssembly_set_platform,$(1),$(gb_CliNativeLibrary_PLATFORM_DEFAULT)), and gb_CliAssembly_set_platform then forwards to gb_CliAssemblyTarget_set_platform.  gb_CliNativeLibrary_PLATFORM_DEFAULT however is defined as x86 and I haven't found anything that would change it.  Is that what assigns the incorrect platform to the policy assembly?

The Jenkins log https://ci.libreoffice.org/job/lo_daily_tb_win_7-2/80/consoleText unfortunately does not show the commands with which policy.1.0.cli_cppuhelper was built.
Comment 10 Kalle Niemitalo 2021-09-17 13:21:17 UTC
Thinking about dual-architecture operation some more -- cli_cppuhelper delay-loads cppu3.dll and cppuhelper3MSC.dll, which are surely architecture-specific.  It wouldn't make sense to install an x86 binary of cli_cppuhelper from an amd64 build of LibreOffice, without also installing x86 binaries of those DLLs and whatever they depend on.  In the end, it might be simplest to just allow side-by-side installations of x86 and amd64 builds of LibreOffice, if users need to run CLI processes with both architectures in the same computer.  That, or start publishing NuGet packages that carry the necessary CLI and native DLLs of LibreOffice so that they can be installed as part of each CLI application (incurring some license obligations).  It doesn't seem to be in scope for this bug, though.
Comment 11 Julien Nabet 2021-09-17 17:27:03 UTC
Kalle: reading your comments and seeing you've got some knowledge about Windows internals, perhaps you may be interested in contributing?

If yes, here are 2 links:
1) general info about code contributing:
https://wiki.documentfoundation.org/Development/GetInvolved


2) building on Windows:
https://wiki.documentfoundation.org/Development/BuildingOnWindows

if no or don't have time, you may also just take search keyword in the code here:
https://opengrok.libreoffice.org/
to pinpoint pbs in LO code/propose some patches?
Comment 12 Kalle Niemitalo 2021-09-18 02:14:24 UTC
(In reply to Kalle Niemitalo from comment #0)

> 1. The policy.1.0.cli_cppuhelper.dll file has been built for x86

I think this can be fixed by changing gb_CliNativeLibrary_PLATFORM_DEFAULT here:

https://opengrok.libreoffice.org/xref/core/solenv/gbuild/CliNativeLibrary.mk?r=0adc9b61#12

The value is currently always x86.  If the amd64 build of LibreOffice instead used amd64 here too, I think this setting would get passed to the "al" command and policy.1.0.cli_cppuhelper.dll would get the correct architecture in its headers, causing Fusion to install it to GAC_64 rather than GAC_32.  It would still be possible to override this default by calling gb_CliNativeLibrary_set_platform for individual libraries, should that ever become necessary.


> 2. ORCA shows that the MsiAssemblyName table of the MSI package has
> processorArchitecture=x86 for both cli_cppuhelper and
> policy.1.0.cli_cppuhelper.

processorArchitecture of policy.1.0.cli_cppuhelper is defined for the MsiAssemblyName table here:

https://opengrok.libreoffice.org/xref/core/scp2/source/ooo/ure.scp?r=0adc9b61#210

If gb_CliNativeLibrary_PLATFORM_DEFAULT is conditionally changed to "amd64", then the same change should be made here as well.


processorArchitecture of cli_cppuhelper is defined for the MsiAssemblyName table here:

https://opengrok.libreoffice.org/xref/core/scp2/source/ooo/ure.scp?r=0adc9b61#197

Correcting that might have no effect on cli_cppuhelper assembly loading, but it could instead fix Bug#101200 (cli_cppuhelper not uninstalled from GAC).
Comment 13 murphyde835 2023-08-22 10:50:32 UTC Comment hidden (spam)