.NET Forum / .NET Framework / CLR / January 2006
Performance monitoring: strange error in .Net 2.0
|
|
Thread rating:  |
Lucvdv - 05 Jan 2006 15:18 GMT There seems to be a strange interaction/conflict between System.Diagnostics.PerformanceCounter objects and System.Security.Principal.WindowsIdentity.Impersonate() in CLR 2.0, that didn't exist in 1.1.
I arrived at this by letting VS.Net 2005 convert a VB.Net 2003 project and just starting it in the debugger after making a few minor modifications (all related to variable initialization): it compiled without errors, but started throwing exceptions that I believe it shouldn't, and that didn't occur in 1.1.
My actual project (or even the relevant part of it) is too large and too proprietary to post it in a newsgroup, but I created a sample program that reproduces the problem and I'll paste it at the bottom of this message (VB.Net 2003 .vb file, not converted to VS.2005 yet).
It uses LogonUser to log on to an account name/password as specified in two CONST's near the top of the file; DuplicateHandleEx to duplicate the handle, and the passes the duplicate to WindowsIdentity.Impersonate.
The account I tested it with has administator rights.
So first create a temporary local user account to test it with, and put that account's name and password in the two constants.
Start a debug build in VS.Net. When you click the START button it should start printing CPU use and system uptime in the debug output window every 500 msec until you click STOP.
After verifying that this works, create a copy of the project directory, and open the solution in VS.2005 and run it there.
VS.Net 2003: It just works as expected.
VS.Net 2005: One of the performance counters (CPU use) only works the first time its NextValue method is called; from the second call on it throws a Win32Exception "Access denied".
Now comment out the "Logon" line in Button1_Click, and the error doesn't occur anymore.
The code (the Win32 API declarations are copied & pasted from my real application, some parts may not be used here). I tried to avoid broken lines (word-wrap by newsreaders), but please don't shoot if...
'=================== Form1.vb ============================================
Public Class Form1 Inherits System.Windows.Forms.Form
Private Const ACCOUNT_NAME As String = "UserName" Private Const ACCOUNT_PASS As String = "PassWord"
#Region " Windows Form Designer generated code "
Public Sub New() MyBase.New()
'This call is required by the Windows Form Designer. InitializeComponent()
'Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list. Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub
'Required by the Windows Form Designer Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer 'It can be modified using the Windows Form Designer. 'Do not modify it using the code editor. Friend WithEvents Button1 As System.Windows.Forms.Button Friend WithEvents Button2 As System.Windows.Forms.Button Friend WithEvents Timer1 As System.Windows.Forms.Timer
<System.Diagnostics.DebuggerStepThrough()> _ Private Sub InitializeComponent() Me.components = New System.ComponentModel.Container Me.Button1 = New System.Windows.Forms.Button Me.Button2 = New System.Windows.Forms.Button Me.Timer1 = New System.Windows.Forms.Timer(Me.components) Me.SuspendLayout() ' 'Button1 ' Me.Button1.Location = New System.Drawing.Point(16, 24) Me.Button1.Name = "Button1" Me.Button1.TabIndex = 0 Me.Button1.Text = "Start" ' 'Button2 ' Me.Button2.Location = New System.Drawing.Point(104, 24) Me.Button2.Name = "Button2" Me.Button2.TabIndex = 1 Me.Button2.Text = "Stop" ' 'Timer1 ' Me.Timer1.Interval = 500 ' 'Form1 ' Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13) Me.ClientSize = New System.Drawing.Size(292, 273) Me.Controls.Add(Me.Button2) Me.Controls.Add(Me.Button1) Me.Name = "Form1" Me.Text = "Form1" Me.ResumeLayout(False)
End Sub
#End Region
Private Enum LOGONTYPE As Integer LOGON32_LOGON_INTERACTIVE = 2 LOGON32_LOGON_NETWORK = 3 LOGON32_LOGON_BATCH = 4 LOGON32_LOGON_SERVICE = 5 LOGON32_LOGON_NEW_CREDENTIALS = 9 End Enum
Private Enum LOGONPROVIDER As Integer LOGON32_PROVIDER_DEFAULT = 0 LOGON32_PROVIDER_WINNT35 = 1 LOGON32_PROVIDER_WINNT40 = 2 LOGON32_PROVIDER_WINNT50 = 3 End Enum
Private Enum SECURITY_IMPERSONATION_LEVEL As Integer SecurityAnonymous = 0 SecurityIdentification = 1 SecurityImpersonation = 2 SecurityDelegation = 3 End Enum
Private Enum TOKEN_TYPE tokenprimary = 1 tokenimpersonation End Enum
Private Declare Auto Function LogonUser Lib "advapi32" ( _ ByVal Username As String, _ ByVal Domain As String, _ ByVal Password As String, _ ByVal dwLogonType As LOGONTYPE, _ ByVal dwLogonProvider As LOGONPROVIDER, _ ByRef hToken As IntPtr _ ) As Boolean
Private Declare Auto Function DuplicateToken Lib "advapi32" ( _ ByVal ExistingTokenHandle As IntPtr, _ ByVal ImpersonationLevel As SECURITY_IMPERSONATION_LEVEL, _ ByRef DuplicateTokenHandle As IntPtr _ ) As Boolean
Private Declare Auto Function DuplicateTokenEx Lib "advapi32" ( _ ByVal hExistingToken As IntPtr, _ ByVal dwDesiredAccess As Int32, _ ByVal lpTokenAttributes As IntPtr, _ ByVal ImpersonationLevel As SECURITY_IMPERSONATION_LEVEL, _ ByVal TokenType As TOKEN_TYPE, _ ByRef hNewToken As IntPtr _ ) As Boolean
Private Declare Auto Function CloseHandle Lib "kernel32" ( _ ByVal hObject As IntPtr) As Boolean
Private hUserToken As IntPtr = IntPtr.Zero Private hFullToken As IntPtr = IntPtr.Zero
Private Impersonation _ As System.Security.Principal.WindowsImpersonationContext = Nothing
Public Enum LogonMode LogonImpersonate LogonDelegate End Enum
Private pdCPU, pdUPT As System.Diagnostics.PerformanceCounter
'----------------------------------------------------------------------
Private Sub LogOn() Try Dim LMode As LOGONTYPE LMode = LOGONTYPE.LOGON32_LOGON_INTERACTIVE
If LogonUser(ACCOUNT_NAME, System.Environment.MachineName, _ ACCOUNT_PASS, LMode, _ LOGONPROVIDER.LOGON32_PROVIDER_WINNT50, hUserToken) Then
If DuplicateTokenEx(hUserToken, 0, IntPtr.Zero, _ SECURITY_IMPERSONATION_LEVEL.SecurityDelegation, _ TOKEN_TYPE.tokenprimary, hFullToken) Then
Impersonation = _ System.Security.Principal.WindowsIdentity.Impersonate(hFullToken)
Debug.WriteLine("Logon OK") End If End If Catch ex As Exception Debug.WriteLine(ex.ToString()) End Try End Sub
Private Sub LogOff() If Not Impersonation Is Nothing Then Impersonation.Undo() Impersonation = Nothing End If If hFullToken.ToInt32 <> 0 Then CloseHandle(hFullToken) hFullToken = IntPtr.Zero End If If hUserToken.ToInt32 <> 0 Then CloseHandle(hUserToken) hUserToken = IntPtr.Zero End If End Sub
Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click LogOn() pdCPU = New System.Diagnostics.PerformanceCounter("Processor", _ "% Processor Time", "_Total") pdUPT = New System.Diagnostics.PerformanceCounter("System", _ "System Up Time") Timer1.Enabled = True End Sub
Private Sub Button2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button2.Click Timer1.Enabled = False pdCPU = Nothing pdUPT = Nothing LogOff() End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Timer1.Tick Dim s1 As Single, s2 As Single Try s1 = pdCPU.NextValue Catch ex As Exception s1 = -1.0 End Try Try s2 = pdUPT.NextValue Catch ex As Exception s2 = -1.0 End Try Debug.WriteLine(s1 & ", " & s2) End Sub
End Class
Lucvdv - 06 Jan 2006 10:06 GMT > There seems to be a strange interaction/conflict between > System.Diagnostics.PerformanceCounter objects and > System.Security.Principal.WindowsIdentity.Impersonate() in CLR 2.0, that > didn't exist in 1.1. <snip>
It's working through a workaround in my app now, but it still looks like a bug in CLR 2.0 to me.
My workaround is starting a separate thread before logging on to the other account. That thread creates the performance objects, reads the counter values at regular intervals, and sends them to the main code (form) through Invoke.
Willy Denoyette [MVP] - 06 Jan 2006 10:44 GMT | > There seems to be a strange interaction/conflict between | > System.Diagnostics.PerformanceCounter objects and [quoted text clipped - 9 lines] | values at regular intervals, and sends them to the main code (form) through | Invoke. It works for me, did you check the impersonating identity in your timer event handler? Note also that your code is impersonating an interactive logon type, why? This is not needed, you should use batch or network type logons, interactive is way too expensive and you need to create an impersonating token from the direct token which is another expensive action.
Willy.
Lucvdv - 06 Jan 2006 14:28 GMT > It works for me, did you check the impersonating identity in your timer > event handler? Did you try the code I posted, or construct your own?
It fails in VS.Net 2005 here, while it runs successfully in VS.Net 2003.
This is some output under the VS.2005 debugger:
| Logon OK | 0, 0 [quoted text clipped - 4 lines] | A first chance exception of type 'System.ComponentModel.Win32Exception' occurred in System.dll | -1, 26432.1 This is what it gives under VS.2003:
| Logon OK | 0, 0 [quoted text clipped - 3 lines] | 3.12376, 26522.88 | 1.561242, 26523.38 To test it, I started under an account with administrator rights and impersonated another account with administrator rights (neither of the two the 'original' administrator created by setup, but both new accounts that were removed from 'users' and put in the administrators group instead).
> Note also that your code is impersonating an interactive logon type, why? The real app uses either interactive or LOGON32_LOGON_NEW_CREDENTIALS, depending on settings.
It's not a web app, but a simple windows forms app that has to connect to a SQL server that may be running either locally or on another machine.
The user's own account will not always have access to the server, the app impersonates another account for that reason.
Remote works with NEW_CREDENTIALS, but it refused to work locally if I didn't use interactive logon. It might work if I force it to connect to the server differently (force TCP/IP), but the overhead for the new logon didn't look too high (you don't notice any delay).
I can still (and maybe will) change it later, the app is only half finished yet.
Willy Denoyette [MVP] - 06 Jan 2006 14:49 GMT Yep, your code. What I don't get is why it would throw an AV exception, your process runs as A and you impersonate using B's credentials, both are members of "administrators", if impersonation fails the process token (A) is used when it succeeds the thread token (B) is used, but both have administrative privileges so no exception should be throw'd.
Are you sure you logged off/logged on again after changing group membership? If not you were still using the old token!
Willy.
| > It works for me, did you check the impersonating identity in your timer | > event handler? [quoted text clipped - 47 lines] | I can still (and maybe will) change it later, the app is only half finished | yet. Lucvdv - 06 Jan 2006 15:58 GMT > Yep, your code. > What I don't get is why it would throw an AV exception, your process runs as [quoted text clipped - 5 lines] > Are you sure you logged off/logged on again after changing group membership? > If not you were still using the old token! Absolutely sure, both accounts were created sometime last year ;)
It wouldn't explain why it works in 1.1 and not in 2.0 either.
But I did find out that it's limited to my development machine alone.
I made some adjustments so the results (including the result of the logon attempt) can be seen outside of the debugger: release builds work on a test machine (Win2000 Pro), but on my development machine (XP Pro) they still fail.
All machines equipped with latest OS service packs etc.
A side effect of my virus scanner, MS anti-spyware, something...?
Willy Denoyette [MVP] - 06 Jan 2006 16:45 GMT | > Yep, your code. | > What I don't get is why it would throw an AV exception, your process runs as [quoted text clipped - 20 lines] | | A side effect of my virus scanner, MS anti-spyware, something...? Not sure if you are using the "VS hosting process" when running from within VS. When that's the case turn it off an try again, this is how I ran your code. The hosting process loads your application in a separate AD, that might be the reason for the strange behavior.
Willy.
Lucvdv - 09 Jan 2006 07:56 GMT > Not sure if you are using the "VS hosting process" when running from within > VS. When that's the case turn it off an try again, this is how I ran your > code. The hosting process loads your application in a separate AD, that > might be the reason for the strange behavior. > > Willy. The release build was running outside of the debugger (after changing the Debug.WriteLine to something more visible), and it still does it.
It must be a side effect of something else I installed, and security tools are my prime suspects because it relates to security.
Willy Denoyette [MVP] - 09 Jan 2006 14:25 GMT | > Not sure if you are using the "VS hosting process" when running from within | > VS. When that's the case turn it off an try again, this is how I ran your [quoted text clipped - 8 lines] | It must be a side effect of something else I installed, and security tools | are my prime suspects because it relates to security. No, it doesn't matter whether you are running a debug or retail build, when you are running in VS the default is "use the hosting process". Check your project properties you'll see what I mean.
Willy.
Lucvdv - 10 Jan 2006 07:19 GMT > | > Not sure if you are using the "VS hosting process" when running from > within [quoted text clipped - 14 lines] > you are running in VS the default is "use the hosting process". Check your > project properties you'll see what I mean. What I meant is, I also tried a release build without Visual Studio running, and the symptoms remain.
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|