Difference between revisions of "COM management"

From OPC Labs Knowledge Base
Jump to navigation Jump to search
(31 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
[[Category:COM/DCOM]]  
 
[[Category:COM/DCOM]]  
  
This article applies to QuickOPC 2022.1 and later.
+
This article applies to QuickOPC 2022.1 and later.  
  
 +
= Concepts =
  
(TBD)
+
This article describes the features present in QuickOPC applications that relate to management of Microsoft COM/DCOM, for the purpose of connecting to OPC Classic servers. The effective COM/DCOM behavior is given by a combination of settings made in the system (Windows - mainly by the DCOMCNFG tool), and the code or settings inside the QuickOPC-based application.
 +
 
 +
QuickOPC applications can perform tasks related to COM management using the {{Style=Identifier|ComManagement}} class. Although you can create your own instances of this class, you would normally be using an instance provided to you by a static read-only field, {{Style=Identifier|ComManagement.Instance}}. The {{Style=Identifier|ComManagement}} class is a semi-singleton, therefore even if you had multiple instances of it, they would all work on and share the same underlying data.
  
 
= Configuring COM =
 
= Configuring COM =
  
 +
In QuickOPC applications, COM is configured using the parameters in {{Style=Identifier|ComManagement.Instance.Configuration}} object. There are two main areas that can be configured:
 +
* [[#COM security initialization|COM security initialization]]
 +
* [[#COM object instantiation|COM object instantiation]]
 +
 +
The configuration parameters default to a reasonable set of values for many application. Upon program start, these defaults are optionally overridden by [[Intrinsic Component Configuration]], which means that you can change them without needing to change the program code itself, which comes handy e.g. in scenarios where the software is already deployed in production environment, and its code cannot be easily changed. After the initial values are determined in this way, your code can further modify them, by setting the corresponding property values.
  
 
= COM security initialization =
 
= COM security initialization =
Line 14: Line 22:
  
 
Furthermore, COM security initialization can either be invoked either
 
Furthermore, COM security initialization can either be invoked either
* '''Explicitly''': Some code in the process (your own code, QuickOPC, or execution infrastructure code) calls the CoInitializeSecurity Win32 API. In the explicit initialization case, the code specifies the COM security parameters to be used.
+
* '''Explicitly''': Some code in the process (your own code, QuickOPC, or execution infrastructure code) calls the {{Style=Identifier|CoInitializeSecurity}} Win32 API. In the explicit initialization case, the code specifies the COM security parameters to be used.
* '''Implicitly''': Some code in the process (your own code, QuickOPC, or execution infrastructure code) marshalls or unmarshalls a COM interface. This can happen e.g. when the code uses some COM object and does not initialize its thread apartment state to MTA. In the implicit initialization case, parameters from computer-wide COM default properties (in DCOMCNFG) are used.
+
* '''Implicitly''': Some code in the process (your own code, QuickOPC, or execution infrastructure code) marshalls or unmarshalls a COM interface. This can happen e.g. when the code uses some COM object and does not initialize its thread apartment state to MTA<ref>[https://docs.microsoft.com/en-us/troubleshoot/windows/win32/descriptions-workings-ole-threading-models Descriptions and workings of OLE threading models]</ref>. In the implicit initialization case, parameters from computer-wide COM default properties (in DCOMCNFG) are used.
  
 
As you can see, whoever initializes the COM security first in the process, either intentionally or intentionally, is the winner for the remainder of the existence of the process. This is unfortunate and unavoidable, although understandable aspect of how COM on Windows work. The danger here is that the initialization made "for you", earlier than you intended, would not have the parameters you want. Large parts of this article deal with approaches that aim at assuring that the COM security parameters can be set "your way".
 
As you can see, whoever initializes the COM security first in the process, either intentionally or intentionally, is the winner for the remainder of the existence of the process. This is unfortunate and unavoidable, although understandable aspect of how COM on Windows work. The danger here is that the initialization made "for you", earlier than you intended, would not have the parameters you want. Large parts of this article deal with approaches that aim at assuring that the COM security parameters can be set "your way".
  
== QuickOPC concepts ==
+
== Security initialization by QuickOPC ==
  
!!!
+
In QuickOPC applications, the COM security is controlled by the properties in the {{Style=Identifier|ComManagement.Instance.Configuration.SecurityParameters}} object. Your code can set them to desired values, and QuickOPC will use them at the moment it initializes the COM security. Further changes to those parameters have no effect.
  
 +
By default, the {{Style=Identifier|UseCustomSecurity}} property is set to true, which means that QuickOPC attempts to set the COM security according to the parameters specified. If you do not want QuickOPC to attempt to initialize the COM security, set the {{Style=Identifier|UseCustomSecurity}} property to false.
 +
 +
If the {{Style=Identifier|UseCustomSecurity}} property is set to true (the default), QuickOPC applications will attempt to initialize the COM security
 +
* when {{Style=Identifier|ComManagement.Instance.AssureSecurityInitialization}} (or {{Style=Identifier|AssureSecurityInitializationAndRunOnStaThread}}) method is called, or
 +
* when a first OPC Classic (i.e. OPC-DA or OPC A&E) operation is performed.
 +
 +
Your code can thus use any of following approaches, depending on its needs:
 +
* Have no code related to COM security. In this case, QuickOPC will attempt to initialize the COM security with its default parameters when a first OPC Classic operation is performed.
 +
* Put in code that modifies the parameters in {{Style=Identifier|ComManagement.Instance.Configuration.SecurityParameters}} before a first OPC Classic operation is performed. In this case, QuickOPC will attempt to initialize the COM security with the parameters your code has specified, when a first OPC Classic operation is performed. With this approach, you have good control over the COM security parameters, but the moment of COM security initialization is postponed and not quite apparent.
 +
* Put in code that modifies the parameters in {{Style=Identifier|ComManagement.Instance.Configuration.SecurityParameters}} somewhere near the beginning of your program, and call the {{Style=Identifier|ComManagement.Instance.AssureSecurityInitialization}} method. In this case, QuickOPC will attempt to initialize the COM security with the parameters your code has specified, and will do so in the {{Style=Identifier|AssureSecurityInitialization}} method. With this approach, you have best control over when the COM security initialization happens, and its parameters.
 +
 +
== The {{Style=Identifier|AssureSecurityInitialization}} method ==
 +
 +
The {{Style=Identifier|ComManagement.AssureSecurityInitialization}} method (or its variant, {{Style=Identifier|AssureSecurityInitializationAndRunOnStaThread}}) assures that COM security is initialized in the QuickOPC application. If your program does not call this method, the COM security will still be initialized. However, using this method can give you better control over when the COM security initialization happens and with which security parameters. Calling it soon enough may prevent unwanted COM security initialization made implicitly or explicitly by other parts of the program.
 +
 +
Executing this method multiple times, with the same values in {{Style=Identifier|ComManagement.Instance.AppID}} and {{Style=Identifier|ComManagement.Instance.Configuration.SecurityParameters}}, is not an error. Such subsequent calls have no effect, though.
 +
 +
The method returns a "value result" ({{Style=Identifier|ValueResult}}) object, indicating success or failure of the operation. In case of success, the value is true if the operation has initialized the COM security; the value is false if COM security has been initialized earlier, with the same parameters.
  
 
== Execution flow ==
 
== Execution flow ==
Line 29: Line 55:
  
 
# Your program's process starts. In some cases (e.g. standalone console apps, Windows Forms apps, WPF apps), this is a process dedicated to you app. In other cases (e.g. Web apps or Web services, or Windows services), this is some kind of hosting process over which you have less or no control.
 
# Your program's process starts. In some cases (e.g. standalone console apps, Windows Forms apps, WPF apps), this is a process dedicated to you app. In other cases (e.g. Web apps or Web services, or Windows services), this is some kind of hosting process over which you have less or no control.
# Before you own code gets a chance to execute, the process may (or may not) initialize COM security, either explicitly (by calling the CoInitializeSecurity function in Win32 API), or implicitly (usually by using some COM object from an STA thread). In case of explicit initialization, the hosting process determines the parameters used. In case of implicit initialization, the computer-wide COM default properties (from DCOMCNFG) are used.
+
# Before you own code gets a chance to execute, the process may (or may not) initialize COM security, either explicitly (by calling the {{Style=Identifier|CoInitializeSecurity}} function in Win32 API), or implicitly (usually by using some COM object from an STA thread). In case of explicit initialization, the hosting process determines the parameters used. In case of implicit initialization, the computer-wide COM default properties (from DCOMCNFG) are used.
 
# A code in you project runs. In some project types, there is a "main" method that gets executed. In other project types, there can be just objects that get instantiated and their constructors and other methods get executed.
 
# A code in you project runs. In some project types, there is a "main" method that gets executed. In other project types, there can be just objects that get instantiated and their constructors and other methods get executed.
 
# A code in your project may (usually unintentionally, by using some COM object from an STA thread) cause an implicit COM security initialization, if COM security has not been initialized yet.
 
# A code in your project may (usually unintentionally, by using some COM object from an STA thread) cause an implicit COM security initialization, if COM security has not been initialized yet.
# When your code makes a ComManagement.Instance.AssureSecurityInitialization() call, or it attempts a first OPC "Classic" operation, QuickOPC attempts to initialize the COM security, using the parameters provided in ComManagement.Instance.Configuration.SecurityParameters. This attempt will succeed if the COM security has not been initialized earlier; otherwise, it will fail with RPC_E_TOO_LATE.
+
# When your code makes a {{Style=Identifier|ComManagement.Instance.AssureSecurityInitialization}}() call, or it makes a first OPC "Classic" operation, QuickOPC attempts to initialize the COM security, using the parameters provided in {{Style=Identifier|ComManagement.Instance.Configuration.SecurityParameters}}. This attempt will succeed if the COM security has not been initialized earlier; otherwise, it will fail with error RPC_E_TOO_LATE.
  
 
In short, QuickOPC allows you to initialize the COM security and specify the parameters that should be used for it, but it only works if some other code in the process has not already initialized the COM security earlier.
 
In short, QuickOPC allows you to initialize the COM security and specify the parameters that should be used for it, but it only works if some other code in the process has not already initialized the COM security earlier.
Line 54: Line 80:
 
There may be different reasons for COM security initialization to fail (and the message text will differ); however, the failure with code 0x80010119 (RPC_E_TOO_LATE) is by far the most common, and it is the one we will focus on here. It means that the COM security has already been initialized, before QuickOPC code got a chance to do it.
 
There may be different reasons for COM security initialization to fail (and the message text will differ); however, the failure with code 0x80010119 (RPC_E_TOO_LATE) is by far the most common, and it is the one we will focus on here. It means that the COM security has already been initialized, before QuickOPC code got a chance to do it.
  
== Guidance for specific project types ==
+
A failure in COM security initialization does not mean that COM will not work at all. It simply means that it will not have the parameters specified, which can cause problems in connecting to or communication with OPC servers, depending on many other factors.
 +
 
 +
= COM object instantiation =
 +
 
 +
Default parameters that influence how COM objects are instantiated by QuickOPC are in {{Style=Identifier|ComManagement.Instance.Configuration.InstantiationParameters}}. These parameters can then be overridden for specific purposes, such as for OPCEnum instantiation, or instantiation of specific OPC servers, using additional properties elsewhere.
  
Use the steps below to for proper COM security initialization in various project types.
+
= Dealing with CVE-2021-26414 =
  
; Console apps in C# : The main method runs on an MTA thread, unless specified otherwise. This is generally good, because it prevents premature COM security initialization. If COM security initialization fails with RPC_E_TOO_LATE, and there is an [STAThread] attribute on the program's main method, remove it (and optionally replace with an [MTAThread] attribute). If it still fails with RPC_E_TOO_LATE, add a ComManagement.Instance.AssureSecurityInitialization() call to the very beginning of the program's main method.
+
As a mitigation for Windows DCOM Server Security Feature Bypass vulnerability (CVE-2021-26414), newer Microsoft Windows systems enforce a minimum activation authentication level of RPC_C_AUTHN_LEVEL_PKT_INTEGRITY. This can cause OPC Classic interoperability problems.
; Console apps in VB.NET : The main method runs on an STA thread, unless specified otherwise. This causes a premature COM security initialization. If there is an <STAThread> attribute on the program's main method, remove it. Then, add an <MTAThread> attribute to the program's main method. If the COM security initialization still fails with RPC_E_TOO_LATE, add a ComManagement.Instance.AssureSecurityInitialization() call to the very beginning of the program's main method.
+
 
 +
If you are affected by this issue, you should configure the COM security in your QuickOPC-based client application accordingly. The corresponding setting is in the {{Style=Identifier|ComManagement.Instance.Configuration.SecurityParameters.EnsureDataIntegrity}} property (defaults to false). Set this property to true to deal with CVE-2021-26414 mitigation. This should be done before QuickOPC attempts to initialize the COM security, at the beginning of your program. It may then look similar to this (C#):
 +
 
 +
[MTAThread]
 +
static void Main()
 +
{
 +
    ComManagement.Instance.Configuration.SecurityParameters.EnsureDataIntegrity = true;
 +
    ComManagement.Instance.AssureSecurityInitialization();
 +
 +
    ...
 +
}
 +
 
 +
In cases when the execution framework initializes the COM security for you (as indicated by the RPC_E_TOO_LATE error in the Windows Application event log) and you cannot prevent that using the approaches described in this article (from the code), try setting the computer-wide COM default properties (in DCOMCNFG) - this will work unless the executing environment has explicitly initialized the COM security to some parameters of its own making. To do so:
 +
# Type DCOMCNFG into Windows Search box, and start the DCOMCNFG application.
 +
# In the "Component Services" window, right-click on Console Root -> Component Services -> Computers -> My Computer, and select Properties.
 +
# In "My Computer Properties" dialog, switch to "Default Properties" tab, and under Default Distributed COM Communication Properties -> Default Authentication Level, select "Packet Integrity".
 +
# Press OK to close the "My Computer Properties" dialog.
 +
# Close the "Component Services" window.
 +
# You may have to restart your computer for the setting to take effect (probably not necessary, but we are really not sure about this).
 +
 
 +
= Guidance for projects types and tools =
 +
 
 +
Use the steps below to for proper COM security initialization in various project types and tools.
 +
 
 +
== .NET development ==
 +
 
 +
; Console apps in C# : The main method runs on an MTA thread, unless specified otherwise. This is generally good, because it prevents premature COM security initialization. If COM security initialization fails with RPC_E_TOO_LATE, and there is an [STAThread] attribute on the program's main method, remove it (and optionally replace with an [MTAThread] attribute). If it still fails with RPC_E_TOO_LATE, add a {{Style=Identifier|ComManagement.Instance.AssureSecurityInitialization}}() call to the very beginning of the program's main method.
 +
; Console apps in VB.NET : The main method runs on an STA thread, unless specified otherwise. This causes a premature COM security initialization. If there is an <STAThread> attribute on the program's main method, remove it. Then, add an <MTAThread> attribute to the program's main method. If the COM security initialization still fails with RPC_E_TOO_LATE, add a {{Style=Identifier|ComManagement.Instance.AssureSecurityInitialization}}() call to the very beginning of the program's main method.
 
; Web apps and Web services : The COM security will most likely be initialized before your code gets a chance to do so, and further attempts will fail with RPC_E_TOO_LATE. In this case, the parameters used for the COM security initialization depend on the host (Web server), and you need to figure out whether the host allows them to be configured. If not, there is a chance that a DCOM configuration for the app (in DCOMCNFG) that represents the Web server, or the computer-wide COM default properties, will have effect.
 
; Web apps and Web services : The COM security will most likely be initialized before your code gets a chance to do so, and further attempts will fail with RPC_E_TOO_LATE. In this case, the parameters used for the COM security initialization depend on the host (Web server), and you need to figure out whether the host allows them to be configured. If not, there is a chance that a DCOM configuration for the app (in DCOMCNFG) that represents the Web server, or the computer-wide COM default properties, will have effect.
; Windows Forms apps in C# : The main method runs on an MTA thread, unless specified otherwise. This is generally good, because it prevents premature COM security initialization. If COM security initialization fails with RPC_E_TOO_LATE, and there is an [STAThread] attribute on the program's main method, remove it (and optionally replace with an [MTAThread] attribute). If it still fails with RPC_E_TOO_LATE, add a ComManagement.Instance.AssureSecurityInitialization() call to the very beginning of the program's main method.
+
; Windows Forms apps in C# : The main method runs on an MTA thread, unless specified otherwise. This is generally good, because it prevents premature COM security initialization. If COM security initialization fails with RPC_E_TOO_LATE, and there is an [STAThread] attribute on the program's main method, remove it (and optionally replace with an [MTAThread] attribute). If it still fails with RPC_E_TOO_LATE, add a {{Style=Identifier|ComManagement.Instance.AssureSecurityInitialization}}() call to the very beginning of the program's main method.
; Windows Forms apps in VB.NET : The main method runs on an STA thread, unless specified otherwise. This causes a premature COM security initialization. If there is an <STAThread> attribute on the program's main method, remove it. Then, add an <MTAThread> attribute to the program's main method. If the COM security initialization still fails with RPC_E_TOO_LATE, add a ComManagement.Instance.AssureSecurityInitialization() call to the very beginning of the program's main method. Note: Depending on how your project has been created, there may be no Program.vb file with the main method. In this case, create or locate Program.vb in some other Windows Forms project, copy it over to your project and edit accordingly (the namespace etc.). Then, go to project Properties -> Application, uncheck "Enable application framework", and set "Startup object" to "Sub Main".
+
; Windows Forms apps in VB.NET : The main method runs on an STA thread, unless specified otherwise. This causes a premature COM security initialization. If there is an <STAThread> attribute on the program's main method, remove it. Then, add an <MTAThread> attribute to the program's main method. If the COM security initialization still fails with RPC_E_TOO_LATE, add a {{Style=Identifier|ComManagement.Instance.AssureSecurityInitialization}}() call to the very beginning of the program's main method. Note: Depending on how your project has been created, there may be no Program.vb file with the main method. In this case, create or locate Program.vb in some other Windows Forms project, copy it over to your project and edit accordingly (the namespace etc.). Then, go to project Properties -> Application, uncheck "Enable application framework", and set "Startup object" to "Sub Main".
; Windows services : COM security is not initialized implicitly, therefore the initialization provided by QuickOPC works well. You can add ComManagement.Instance.AssureSecurityInitialization() call to the Main() method in Program.cs or Program.vb.
+
; Windows services : COM security is not initialized implicitly, therefore the initialization provided by QuickOPC works well. You can add {{Style=Identifier|ComManagement.Instance.AssureSecurityInitialization}}() call to the Main() method in Program.cs or Program.vb.
; WPF apps: COM security initialization fails with RPC_E_TOO_LATE in the generated project. It should be possible to customize the program's main method (search the Web for "WPF main method" for hints on how to do that). Make the program's main method use the MTA thread and add a ComManagement.Instance.AssureSecurityInitialization() call to it. Alternatively, there is a chance computer-wide COM default properties (in DCOMCNFG) will have effect.
+
; WPF apps: COM security initialization fails with RPC_E_TOO_LATE in the generated project. It should be possible to customize the program's main method (search the Web for "WPF main method" for hints on how to do that). Make the program's main method use the MTA thread and add a {{Style=Identifier|ComManagement.Instance.AssureSecurityInitialization}}() call to it. Alternatively, there is a chance computer-wide COM default properties (in DCOMCNFG) will have effect.
 +
 
 +
If the steps you took required you to switch from using an STA thread to an MTA thread, and you find that for some reason the remainder of your code has a problem running under an MTA thread, use the {{Style=Identifier|ComManagement.Instance.AssureSecurityInitializationAndRunOnStaThread}}() to run the remainder on your code on an STA thread.
 +
 
 +
== COM development ==
 +
 
 +
; JScript in Windows Script Host (CScript or WScript) : COM security is initialized by the WSH and cannot be subsequently changed. Try to change Default Properties in DCOMCNFG.
 +
 
 +
; VBScript in Windows Script Host (CScript or WScript) : COM security is initialized by the WSH and cannot be subsequently changed. Try to change Default Properties in DCOMCNFG.
 +
 
 +
== Connectivity Explorer ==
 +
 
 +
Use the Tools -> COM Management command to view and configure the current COM management settings. You will need to restart the Connection Explorer for most of the settings to take effect.
 +
 
 +
== OpcCmd Utility ==
 +
 
 +
Set proper authentication level for CVE-2021-26414 update:
 +
 
 +
{{Style=keyboard|interopServices comManagement set Configuration.SecurityParameters.EnsureDataIntegrity [Boolean]True}}
 +
 
 +
Display current COM management settings:
 +
 
 +
{{Style=keyboard|interopServices comManagement get}}
  
If the steps you took required you to switch from using an STA thread to an MTA thread, and you find that for some reason the remainder of your code has a problem running under an MTA thread, use the ComManagement.Instance.AssureSecurityInitializationAndRunOnStaThread() to run the remainder on your code on an STA thread.
+
Assure that COM security is initialized:
  
== Dealing with CVE-2021-26414 ==
+
{{Style=keyboard|interopServices comManagement assureSecurityInitialization}}
  
= !!! =
+
== Other QuickOPC-based software  ==
 +
 
 +
For software built with QuickOPC 2022.1 or later, which does not have controls for COM management of its own, you can use external means (configuration files) to influence their default COM parameters (for security initialization, and object instantiation). See [[Intrinsic Component Configuration]]. This method may not work if the software has specifically hard-coded the settings, or if the COM security gets initialized too early in the process, e.g. by the application host.
 +
 
 +
When the COM security is initialized by the application host:
 +
* If the host does not call {{Style=Identifier|CoInitializeSecurity}}: the COM security is initialized implicitly, and its parameters are taken from system-wide DCOM settings. Use DCOMCNFG (Default Properties tab) to change them.
 +
* If the host calls {{Style=Identifier|CoInitializeSecurity}} explicitly: Consult the host documentation about whether the parameters the host uses can be changed and how. If not, there is currently no solution that would allow that.
 +
 
 +
= Q&A =
 +
{| class="mw-collapsible mw-collapsed wikitable"
 +
! If I am using version 2021.3 or earlier, and have an existing app, are the QuickOPC binaries hard coding the DCOM security settings or will they follow whatever the user has set in DCOM Config for their client application, or was it possible in older versions through other settings to overwrite DCOM Config settings?
 +
|-
 +
|Unless the developer explicitly sets UseCustomSecurity to ‘false’ in the code, the settings that are hard-coded in QuickOPC will be used ([https://kb.opclabs.com/COM_settings_in_OPC_Classic_client_components#QuickOPC_versions_up_to_2021.3]). It should be possible to use settings from DCOM config in versions up to 2021.3 by setting UseCustomSecurity to ‘false’. (Note: this answer applies to NativeClient implementation only).
 +
|}
 +
{| class="mw-collapsible mw-collapsed wikitable"
 +
! With the 2022.1 version or later, if ComSecurityParameters.EnsureDataIntegrity property is false, will the users client application follow DCOM Config?
 +
|-
 +
|In version 2022.1 and later, with all other settings at their defaults, settings from OPC Data Client and not from DCOM config will be used ([https://kb.opclabs.com/COM_settings_in_OPC_Classic_client_components#QuickOPC_version_2022.1_and_later]), *BUT ONLY IF OPC DATA CLIENT GETS A CHANCE TO DO SO* . EnsureDataIntegrity defaults to ‘false’, which for compatibility with the “old” systems (before DCOM hardening update). The developer can change EnsureDataIntegrity to ‘true’ for compatibility with DCOM hardening update. The developer can also change UseCustomSecurity to ‘false’ (albeit in an object different from previous versions) to instruct the application to take settings from DCOM Config.
 +
 
 +
This big warning *BUT ONLY IF OPC DATA CLIENT GETS A CHANCE TO DO SO* is the subject of all the explanations in [[#COM_security_initialization|COM security initialization]] and [[#Guidance_for_projects_types_and_tools|Guidance for projects types and tools]].
 +
 
 +
What we were trying to achieve is to allow developing an application that will make the settings from inside - the settings that are assured to work for it, that is. One could argue that an approach that leaves everything to the configuration on the target computer (DCOM Config) could be easier to support – in which case you need to set UseCustomSecurity to ‘false’ with newer versions. Which one is better is hard to decide: If the app gets deployed many times, you probably want to spend some time coding it well and do not have to configure anything on site. But if you have a one-off deployment, tweaking DCOM Config might be the easier way.
 +
|}
 +
 
 +
= Related reading =
  
 
[[COM settings in OPC Classic client components]]
 
[[COM settings in OPC Classic client components]]
Line 79: Line 182:
  
 
[[What's new in QuickOPC 2022.1]]
 
[[What's new in QuickOPC 2022.1]]
 +
 +
[https://opcfoundation.org/forum/classic-opc-da-ae-hda-xml-da-etc/microsoft-dcom-update Microsoft DCOM update]
 +
 +
<br/>

Revision as of 07:56, 31 March 2022


This article applies to QuickOPC 2022.1 and later.

Concepts

This article describes the features present in QuickOPC applications that relate to management of Microsoft COM/DCOM, for the purpose of connecting to OPC Classic servers. The effective COM/DCOM behavior is given by a combination of settings made in the system (Windows - mainly by the DCOMCNFG tool), and the code or settings inside the QuickOPC-based application.

QuickOPC applications can perform tasks related to COM management using the ComManagement class. Although you can create your own instances of this class, you would normally be using an instance provided to you by a static read-only field, ComManagement.Instance. The ComManagement class is a semi-singleton, therefore even if you had multiple instances of it, they would all work on and share the same underlying data.

Configuring COM

In QuickOPC applications, COM is configured using the parameters in ComManagement.Instance.Configuration object. There are two main areas that can be configured:

The configuration parameters default to a reasonable set of values for many application. Upon program start, these defaults are optionally overridden by Intrinsic Component Configuration, which means that you can change them without needing to change the program code itself, which comes handy e.g. in scenarios where the software is already deployed in production environment, and its code cannot be easily changed. After the initial values are determined in this way, your code can further modify them, by setting the corresponding property values.

COM security initialization

In Windows, major part of parameters that influence the COM security can only be set globally for the whole process, and only once. As soon as the COM security is initialized in a process, its parameters cannot be changed.

Furthermore, COM security initialization can either be invoked either

  • Explicitly: Some code in the process (your own code, QuickOPC, or execution infrastructure code) calls the CoInitializeSecurity Win32 API. In the explicit initialization case, the code specifies the COM security parameters to be used.
  • Implicitly: Some code in the process (your own code, QuickOPC, or execution infrastructure code) marshalls or unmarshalls a COM interface. This can happen e.g. when the code uses some COM object and does not initialize its thread apartment state to MTA[1]. In the implicit initialization case, parameters from computer-wide COM default properties (in DCOMCNFG) are used.

As you can see, whoever initializes the COM security first in the process, either intentionally or intentionally, is the winner for the remainder of the existence of the process. This is unfortunate and unavoidable, although understandable aspect of how COM on Windows work. The danger here is that the initialization made "for you", earlier than you intended, would not have the parameters you want. Large parts of this article deal with approaches that aim at assuring that the COM security parameters can be set "your way".

Security initialization by QuickOPC

In QuickOPC applications, the COM security is controlled by the properties in the ComManagement.Instance.Configuration.SecurityParameters object. Your code can set them to desired values, and QuickOPC will use them at the moment it initializes the COM security. Further changes to those parameters have no effect.

By default, the UseCustomSecurity property is set to true, which means that QuickOPC attempts to set the COM security according to the parameters specified. If you do not want QuickOPC to attempt to initialize the COM security, set the UseCustomSecurity property to false.

If the UseCustomSecurity property is set to true (the default), QuickOPC applications will attempt to initialize the COM security

  • when ComManagement.Instance.AssureSecurityInitialization (or AssureSecurityInitializationAndRunOnStaThread) method is called, or
  • when a first OPC Classic (i.e. OPC-DA or OPC A&E) operation is performed.

Your code can thus use any of following approaches, depending on its needs:

  • Have no code related to COM security. In this case, QuickOPC will attempt to initialize the COM security with its default parameters when a first OPC Classic operation is performed.
  • Put in code that modifies the parameters in ComManagement.Instance.Configuration.SecurityParameters before a first OPC Classic operation is performed. In this case, QuickOPC will attempt to initialize the COM security with the parameters your code has specified, when a first OPC Classic operation is performed. With this approach, you have good control over the COM security parameters, but the moment of COM security initialization is postponed and not quite apparent.
  • Put in code that modifies the parameters in ComManagement.Instance.Configuration.SecurityParameters somewhere near the beginning of your program, and call the ComManagement.Instance.AssureSecurityInitialization method. In this case, QuickOPC will attempt to initialize the COM security with the parameters your code has specified, and will do so in the AssureSecurityInitialization method. With this approach, you have best control over when the COM security initialization happens, and its parameters.

The AssureSecurityInitialization method

The ComManagement.AssureSecurityInitialization method (or its variant, AssureSecurityInitializationAndRunOnStaThread) assures that COM security is initialized in the QuickOPC application. If your program does not call this method, the COM security will still be initialized. However, using this method can give you better control over when the COM security initialization happens and with which security parameters. Calling it soon enough may prevent unwanted COM security initialization made implicitly or explicitly by other parts of the program.

Executing this method multiple times, with the same values in ComManagement.Instance.AppID and ComManagement.Instance.Configuration.SecurityParameters, is not an error. Such subsequent calls have no effect, though.

The method returns a "value result" (ValueResult) object, indicating success or failure of the operation. In case of success, the value is true if the operation has initialized the COM security; the value is false if COM security has been initialized earlier, with the same parameters.

Execution flow

Here is what generally happens (with regard to COM security initialization) in a program that uses QuickOPC.

  1. Your program's process starts. In some cases (e.g. standalone console apps, Windows Forms apps, WPF apps), this is a process dedicated to you app. In other cases (e.g. Web apps or Web services, or Windows services), this is some kind of hosting process over which you have less or no control.
  2. Before you own code gets a chance to execute, the process may (or may not) initialize COM security, either explicitly (by calling the CoInitializeSecurity function in Win32 API), or implicitly (usually by using some COM object from an STA thread). In case of explicit initialization, the hosting process determines the parameters used. In case of implicit initialization, the computer-wide COM default properties (from DCOMCNFG) are used.
  3. A code in you project runs. In some project types, there is a "main" method that gets executed. In other project types, there can be just objects that get instantiated and their constructors and other methods get executed.
  4. A code in your project may (usually unintentionally, by using some COM object from an STA thread) cause an implicit COM security initialization, if COM security has not been initialized yet.
  5. When your code makes a ComManagement.Instance.AssureSecurityInitialization() call, or it makes a first OPC "Classic" operation, QuickOPC attempts to initialize the COM security, using the parameters provided in ComManagement.Instance.Configuration.SecurityParameters. This attempt will succeed if the COM security has not been initialized earlier; otherwise, it will fail with error RPC_E_TOO_LATE.

In short, QuickOPC allows you to initialize the COM security and specify the parameters that should be used for it, but it only works if some other code in the process has not already initialized the COM security earlier.

Determining what happens

QuickOPC applications write an entry to the Windows Application log whenever they attempt to explicitly initialize the COM security, indicating a success or failure, and additional relevant information. In order to view this log, start "Event Viewer" from the Windows search box, and in the "Event Viewer" window, select the Event Viewer (Local) -> Windows Logs -> Application node in the tree. The Source of the events is "OPCLabs-ComInterop".

A successful COM security initialization is indicated by an event with Level "Information" and ID 1. The message text will be something like

COM security initialization (process name "...", Id ...) for requestor 'ComSecurityInitializingEasyDAClient' succeeded; the initialization object was: (Default).
Made system call: yes, current thread name: "", from thread pool: no, apartment state: MTA.

A failed COM security initialization is indicated by an event with Level "Error" and ID 2. The message text will be something like

COM security initialization (process name "…", Id …) for requestor 'ComSecurityInitializingEasyDAClient' failed; the initialization object was: (Default).
CoInitializeSecurity failure (0x80010119): Security must be initialized before any interfaces are marshalled or unmarshalled. It cannot be changed once initialized.
+ This error (RPC_E_TOO_LATE) is not uncommon in hosted .NET applications. Depending on various factors, it might be possible to prevent it, or safely ignore it (if COM works as intended). Consult the product documentation.
+ Current thread name: "…", from thread pool: yes, managed thread Id: …, apartment state: MTA.

There may be different reasons for COM security initialization to fail (and the message text will differ); however, the failure with code 0x80010119 (RPC_E_TOO_LATE) is by far the most common, and it is the one we will focus on here. It means that the COM security has already been initialized, before QuickOPC code got a chance to do it.

A failure in COM security initialization does not mean that COM will not work at all. It simply means that it will not have the parameters specified, which can cause problems in connecting to or communication with OPC servers, depending on many other factors.

COM object instantiation

Default parameters that influence how COM objects are instantiated by QuickOPC are in ComManagement.Instance.Configuration.InstantiationParameters. These parameters can then be overridden for specific purposes, such as for OPCEnum instantiation, or instantiation of specific OPC servers, using additional properties elsewhere.

Dealing with CVE-2021-26414

As a mitigation for Windows DCOM Server Security Feature Bypass vulnerability (CVE-2021-26414), newer Microsoft Windows systems enforce a minimum activation authentication level of RPC_C_AUTHN_LEVEL_PKT_INTEGRITY. This can cause OPC Classic interoperability problems.

If you are affected by this issue, you should configure the COM security in your QuickOPC-based client application accordingly. The corresponding setting is in the ComManagement.Instance.Configuration.SecurityParameters.EnsureDataIntegrity property (defaults to false). Set this property to true to deal with CVE-2021-26414 mitigation. This should be done before QuickOPC attempts to initialize the COM security, at the beginning of your program. It may then look similar to this (C#):

[MTAThread]
static void Main()
{
    ComManagement.Instance.Configuration.SecurityParameters.EnsureDataIntegrity = true;
    ComManagement.Instance.AssureSecurityInitialization();

    ...
}

In cases when the execution framework initializes the COM security for you (as indicated by the RPC_E_TOO_LATE error in the Windows Application event log) and you cannot prevent that using the approaches described in this article (from the code), try setting the computer-wide COM default properties (in DCOMCNFG) - this will work unless the executing environment has explicitly initialized the COM security to some parameters of its own making. To do so:

  1. Type DCOMCNFG into Windows Search box, and start the DCOMCNFG application.
  2. In the "Component Services" window, right-click on Console Root -> Component Services -> Computers -> My Computer, and select Properties.
  3. In "My Computer Properties" dialog, switch to "Default Properties" tab, and under Default Distributed COM Communication Properties -> Default Authentication Level, select "Packet Integrity".
  4. Press OK to close the "My Computer Properties" dialog.
  5. Close the "Component Services" window.
  6. You may have to restart your computer for the setting to take effect (probably not necessary, but we are really not sure about this).

Guidance for projects types and tools

Use the steps below to for proper COM security initialization in various project types and tools.

.NET development

Console apps in C# 
The main method runs on an MTA thread, unless specified otherwise. This is generally good, because it prevents premature COM security initialization. If COM security initialization fails with RPC_E_TOO_LATE, and there is an [STAThread] attribute on the program's main method, remove it (and optionally replace with an [MTAThread] attribute). If it still fails with RPC_E_TOO_LATE, add a ComManagement.Instance.AssureSecurityInitialization() call to the very beginning of the program's main method.
Console apps in VB.NET 
The main method runs on an STA thread, unless specified otherwise. This causes a premature COM security initialization. If there is an <STAThread> attribute on the program's main method, remove it. Then, add an <MTAThread> attribute to the program's main method. If the COM security initialization still fails with RPC_E_TOO_LATE, add a ComManagement.Instance.AssureSecurityInitialization() call to the very beginning of the program's main method.
Web apps and Web services 
The COM security will most likely be initialized before your code gets a chance to do so, and further attempts will fail with RPC_E_TOO_LATE. In this case, the parameters used for the COM security initialization depend on the host (Web server), and you need to figure out whether the host allows them to be configured. If not, there is a chance that a DCOM configuration for the app (in DCOMCNFG) that represents the Web server, or the computer-wide COM default properties, will have effect.
Windows Forms apps in C# 
The main method runs on an MTA thread, unless specified otherwise. This is generally good, because it prevents premature COM security initialization. If COM security initialization fails with RPC_E_TOO_LATE, and there is an [STAThread] attribute on the program's main method, remove it (and optionally replace with an [MTAThread] attribute). If it still fails with RPC_E_TOO_LATE, add a ComManagement.Instance.AssureSecurityInitialization() call to the very beginning of the program's main method.
Windows Forms apps in VB.NET 
The main method runs on an STA thread, unless specified otherwise. This causes a premature COM security initialization. If there is an <STAThread> attribute on the program's main method, remove it. Then, add an <MTAThread> attribute to the program's main method. If the COM security initialization still fails with RPC_E_TOO_LATE, add a ComManagement.Instance.AssureSecurityInitialization() call to the very beginning of the program's main method. Note: Depending on how your project has been created, there may be no Program.vb file with the main method. In this case, create or locate Program.vb in some other Windows Forms project, copy it over to your project and edit accordingly (the namespace etc.). Then, go to project Properties -> Application, uncheck "Enable application framework", and set "Startup object" to "Sub Main".
Windows services 
COM security is not initialized implicitly, therefore the initialization provided by QuickOPC works well. You can add ComManagement.Instance.AssureSecurityInitialization() call to the Main() method in Program.cs or Program.vb.
WPF apps
COM security initialization fails with RPC_E_TOO_LATE in the generated project. It should be possible to customize the program's main method (search the Web for "WPF main method" for hints on how to do that). Make the program's main method use the MTA thread and add a ComManagement.Instance.AssureSecurityInitialization() call to it. Alternatively, there is a chance computer-wide COM default properties (in DCOMCNFG) will have effect.

If the steps you took required you to switch from using an STA thread to an MTA thread, and you find that for some reason the remainder of your code has a problem running under an MTA thread, use the ComManagement.Instance.AssureSecurityInitializationAndRunOnStaThread() to run the remainder on your code on an STA thread.

COM development

JScript in Windows Script Host (CScript or WScript) 
COM security is initialized by the WSH and cannot be subsequently changed. Try to change Default Properties in DCOMCNFG.
VBScript in Windows Script Host (CScript or WScript) 
COM security is initialized by the WSH and cannot be subsequently changed. Try to change Default Properties in DCOMCNFG.

Connectivity Explorer

Use the Tools -> COM Management command to view and configure the current COM management settings. You will need to restart the Connection Explorer for most of the settings to take effect.

OpcCmd Utility

Set proper authentication level for CVE-2021-26414 update:

interopServices comManagement set Configuration.SecurityParameters.EnsureDataIntegrity [Boolean]True

Display current COM management settings:

interopServices comManagement get

Assure that COM security is initialized:

interopServices comManagement assureSecurityInitialization

Other QuickOPC-based software

For software built with QuickOPC 2022.1 or later, which does not have controls for COM management of its own, you can use external means (configuration files) to influence their default COM parameters (for security initialization, and object instantiation). See Intrinsic Component Configuration. This method may not work if the software has specifically hard-coded the settings, or if the COM security gets initialized too early in the process, e.g. by the application host.

When the COM security is initialized by the application host:

  • If the host does not call CoInitializeSecurity: the COM security is initialized implicitly, and its parameters are taken from system-wide DCOM settings. Use DCOMCNFG (Default Properties tab) to change them.
  • If the host calls CoInitializeSecurity explicitly: Consult the host documentation about whether the parameters the host uses can be changed and how. If not, there is currently no solution that would allow that.

Q&A

If I am using version 2021.3 or earlier, and have an existing app, are the QuickOPC binaries hard coding the DCOM security settings or will they follow whatever the user has set in DCOM Config for their client application, or was it possible in older versions through other settings to overwrite DCOM Config settings?
Unless the developer explicitly sets UseCustomSecurity to ‘false’ in the code, the settings that are hard-coded in QuickOPC will be used ([1]). It should be possible to use settings from DCOM config in versions up to 2021.3 by setting UseCustomSecurity to ‘false’. (Note: this answer applies to NativeClient implementation only).
With the 2022.1 version or later, if ComSecurityParameters.EnsureDataIntegrity property is false, will the users client application follow DCOM Config?
In version 2022.1 and later, with all other settings at their defaults, settings from OPC Data Client and not from DCOM config will be used ([2]), *BUT ONLY IF OPC DATA CLIENT GETS A CHANCE TO DO SO* . EnsureDataIntegrity defaults to ‘false’, which for compatibility with the “old” systems (before DCOM hardening update). The developer can change EnsureDataIntegrity to ‘true’ for compatibility with DCOM hardening update. The developer can also change UseCustomSecurity to ‘false’ (albeit in an object different from previous versions) to instruct the application to take settings from DCOM Config.

This big warning *BUT ONLY IF OPC DATA CLIENT GETS A CHANCE TO DO SO* is the subject of all the explanations in COM security initialization and Guidance for projects types and tools.

What we were trying to achieve is to allow developing an application that will make the settings from inside - the settings that are assured to work for it, that is. One could argue that an approach that leaves everything to the configuration on the target computer (DCOM Config) could be easier to support – in which case you need to set UseCustomSecurity to ‘false’ with newer versions. Which one is better is hard to decide: If the app gets deployed many times, you probably want to spend some time coding it well and do not have to configure anything on site. But if you have a one-off deployment, tweaking DCOM Config might be the easier way.

Related reading

COM settings in OPC Classic client components

CVE-2021-26414

KB5004442—Manage changes for Windows DCOM Server Security Feature Bypass (CVE-2021-26414)

What's new in QuickOPC 2022.1

Microsoft DCOM update