.net 시스템의 handle leak이 발생하는 경우 iis 재생시 cpu 사용률이 급증하는 이슈가 발생할 수 있습니다.

 

특히, CPU사용률이 5~20%수준의 시스템이 재생시간 90% 이상 사용률이 증가한다면, handle leak을 의심해볼만 합니다.

 

다양한 이슈들이 있겠지만 그 중 한가지 발생원인에 대해 확인해보겠습니다.

 

HANDLE은 작업관리자에서 쉽게 확인 할 수 있습니다.

세부정보에서 우클릭 후 열선택을 누르고

 

 

 

아래와 같이 핸들 값을 확인 할 수 있습니다.

이 핸들은 프로세스별로 생성,삭제를 반복하게 되는데요. 이 핸들이 정상적으로 삭제되지 않고

누적되는 경우 handle leak이 발생 할 수 있습니다.

 

 

윈도우 서버에는 Lsass.exe라는 프로세스가 실행되고 있습니다.

서버 인증관련 처리를 하며 엑세스 토큰을 생성하게 됩니다.

이때, 생성된 엑세스 토큰이 제대로 처리되지 않고 남아있다면, 이슈가 발생할 확률이 높습니다.

 


Lsass.exe 란?

로컬 보안 인증 하위 시스템 서비스(Local Security Authority Subsystem Service, LSASS)는 시스템의 보안 정책을 강화를 위한 윈도우의 프로세스이다. 이것은 윈도우 컴퓨터나 서버에 접속하는 유저들의 로그인을 검사하며, 비밀번호 변경을 관리하고, 액세스 토큰(access tokens)을 생성한다.[1] 또한 Windows Security Log를 작성한다.


 

특히 win32 api를 활용해 원격서버로 로그인해야되는 로직이 필요한 경우 

아래 코드와 같이 구현할 수 있습니다. C#코드로 원격서버에 접근하기 위한 코드인데요.

이때, handle처리를 정확하게 해주지 않으면, handle leak이 발생 할 수 있습니다.

 

LogonUser라는 win32 api를 사용하게 되는데요. win32 api는 c코드로 작성된 dll을 c#에서 사용하기 위한 함수입니다.

 

LogonUser API 사용시 토큰을 생성해야합니다.

이로인해 앞서 인증관련 처리를 해주는 Lsass.exe 프로세스에 handle이 증가하게됩니다.

이 증가된 handle은 코드상에서 CloseHandle처리를 해주어야 합니다.

 

close처리되지 않은 핸들은 iis재생 전까지 계속 남아있게 됩니다.

특히나, 파일을 계속해서 업로드해야되거나 하는 등의 원격서버 로그인이 많이 필요한 시스템의 경우에

closehandle처리를 하지 않으면 handle지속적으로 쌓이게 됩니다. 

iis 재생 텀이 길고 원격서버 로그인 요청이 많아진다면 handle은 계속해서 누적됩니다.

 

IIS재생전까지는 CPU사용률의 특이사항을 감지하지 못할 수 있습니다.

하지만, iis 재생시점에 그동안의 쓰레기 작업들을 처리하며

lsass.exe프로세스가 잡고있는 handle을 대량으로 처리하게 됩니다.

이때 누적된 handle량에 따라 CPU 사용률이 급증할 수 있습니다.

 

아래와 같은 WIN32 API사용시에는 무조건 CLOSEHANDLE처리를 해주어야 합니다.

        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern bool LogonUser(string pszUsername, string pszDomain, string pszPassword,
            int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public extern static bool CloseHandle(IntPtr handle);

        public void logonAndImpersonNate()
        {
            WindowsImpersonationContext impersonationContext = null;
            IntPtr userHandle = IntPtr.Zero;
            const int LOGON32_PROVIDER_DEFAULT = 0;
            const int LOGON32_LOGON_INTERACTIVE = 2;

            try
            {
                Console.WriteLine("windows identify before impersonation: " + WindowsIdentity.GetCurrent().Name);


                bool loggedOn = LogonUser("접근계정",
                                            "domain주소",
                                            "패스워드",
                                            LOGON32_LOGON_INTERACTIVE,
                                            LOGON32_PROVIDER_DEFAULT,
                                            ref userHandle);

                if (!loggedOn)
                {
                    return;
                }

                // Begin impersonating the user
                impersonationContext = WindowsIdentity.Impersonate(userHandle);
               
                //
                //원격서버 로그인 후 필요한 작업 수행
                //파일업로드 파일 읽기 등
                //

            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception impersonating user: " + ex.Message);
            }
            finally
            {
                // Clean up
                if (impersonationContext != null)
                {
                    impersonationContext.Undo();
                }

                if (userHandle != IntPtr.Zero)
                {
                    CloseHandle(userHandle); //이렇게 처리해주지 않으면 leak발생으로 인한 CPU급상승을 야기함
                }
            }
        }

 

c#에서 C코드를 사용하는 WIN32 API는 이런 복잡한 처리가 필요하므로 원격서버 로그인이 필요한 경우 

WIN32 API보다는 WEB.CONFIG에서 인증 처리를 하거나, 윈도우 자격증명을 통해 서버에서 인증을 사전에 처리해두고

소스상에서는 인증 구간을 삭제하는 것이 좋을 듯 합니다.

 

아래는 WIN32 API사용 및 handle 처리 관련 msdn 가이드 내용입니다.

 

#MSDN의 핸들 사용  후 closehandle 함수 실행 권고 내용

-요약 : 핸들 사용 후 매번 Closehandle 함수 호출 필요함

The documentation for the functions that create these objects indicates that CloseHandle should be used when you are finished with the object, and what happens to pending operations on the object after the handle is closed. In general, CloseHandle invalidates the specified object handle, decrements the object's handle count, and performs object retention checks. After the last handle to an object is closed, the object is removed from the system. For a summary of the creator functions for these objects, see Kernel Objects.

Generally, an application should call CloseHandle once for each handle it opens. It is usually not necessary to call CloseHandle if a function that uses a handle fails with ERROR_INVALID_HANDLE, because this error usually indicates that the handle is already invalidated. However, some functions use ERROR_INVALID_HANDLE to indicate that the object itself is no longer valid. For example, a function that attempts to use a handle to a file on a network might fail with ERROR_INVALID_HANDLE if the network connection is severed, because the file object is no longer available. In this case, the application should close the handle.

 

출처 : https://docs.microsoft.com/ko-kr/windows/desktop/api/handleapi/nf-handleapi-closehandle

 

 

#MSDN win32 api LogonUser 함수 사용시 가이드

요약 : LogonUser 함수를 통한 로그인을 위해선 핸들 형태의 토큰이 파라미터로 필요하며, 사용이 완료되면 CloseHandle 처리 필요

 

LogonUserA function                                                                    

                                                                                                                     

The LogonUser function attempts to log a user on to the local computer. The local computer is the computer from which LogonUserwas called. You cannot use LogonUser to log on to a remote computer. You specify the user with a user name and domain and authenticate the user with a plaintext password. If the function succeeds, you receive a handle to a token that represents the logged-on user. You can then use this token handle to impersonate the specified user or, in most cases, to create a process that runs in the context of the specified user.

 

Parameters

 

phToken phToken 

 

A pointer to a handle variable that receives a handle to a token that represents the specified user.

You can use the returned handle in calls to the ImpersonateLoggedOnUser function.

In most cases, the returned handle is a primary token that you can use in calls to the CreateProcessAsUser function. However, if you specify the LOGON32_LOGON_NETWORK flag, LogonUser returns an impersonation token that you cannot use in CreateProcessAsUser unless you call DuplicateTokenEx to convert it to a primary token.

When you no longer need this handle, close it by calling the CloseHandle function.

 

출처 : https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-logonusera

LIST
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기