설명
제목에 따라 WinInet에서는 FindNextUrlCacheEntry()
ERROR_NO_MORE_ITEMS=295 대신 문서화되지 않은 오류 코드 2(ERROR_FILE_NOT_FOUND)가 발생합니다. 누군가 이유를 설명하거나 다른 조사 방법을 제안할 수 있습니까?
환경
- 윈도우 10
- IE 11
- VisualStudio 2006(그러나 VS2010에서 다시 빌드 가능)
개요
자세한 내용은 아래에 나와 있지만 문제의 핵심은 루프 내부에 다음 코드 조각이 있다는 것입니다.
/ Get the next entry
if (!FindNextUrlCacheEntry(hCache, *plpCacheEntry, &bufSize))
{
DWORD d = GetLastError();
ATLTRACE(_T("getNextEntry - FindNExtUrlCacheEntry returned %d: %s ==> %s"), d, (*plpCacheEntry)->lpszSourceUrlName, (*plpCacheEntry)->lpszLocalFileName);
// Are we done?
// <.... more error handling logic ....>
결과는 다음과 같습니다.
>>[12536] getNextEntry - FindNExtUrlCacheEntry returned 2: (null) ==> (null)
이 조건을 ERROR_NO_MORE_ITEMS와 유사하게 처리하기 위해 코드를 해킹하면 모든 것이 예상대로 작동합니다. 이를 무시하고 계속해서 더 많은 항목을 얻으려고 시도하면 무한 루프가 발생합니다.
개요
참고: 이 게시물에서는 오류 코드 2를 ERROR_FILE_NOT_FOUND와 동의어로 취급하지만 문제의 API 호출에 대한 문서화되지 않은 코드이므로 2는 실제로 다른 코드에서 발생한 것일 수 있습니다.
IE에서 실행되는 VisualStudio 2006 경에 C++ ActiveX 컨트롤이 있습니다. 그 목적은 애플리케이션 서버의 호스트 이름과 일치하는 파일에 대해 부분적인 IE 캐시 정리를 수행하는 것입니다. (이 작업은 서버의 요청 쿠키 값과 클라이언트에 마지막으로 저장된 값 간의 버전 번호를 비교하여 제어됩니다. 삭제는 버전 불일치가 감지된 경우에만 발생합니다.)
ActiveX 컨트롤은 수년 동안 제대로 작동해 왔습니다. 그러나 최근 위에서 언급한 것처럼 실패하기 시작했습니다. KB Windows 업데이트 때문인지, 최근 보안 계획 때문인지, 아니면 다른 이유 때문인지는 알 수 없습니다. 우리 코드는 ERROR_NO_MORE_ITEMS 사례를 적절하게 처리하고 해당 작업을 종료합니다. 하지만 2를 오류로 처리하고 앱이 계속 실행되지 않습니다.
FindNextUrlCacheEntry()에 대한 설명서는 다음과 같습니다. https://docs.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-findnexturlcacheentrya 호출은 true/false를 반환하고 오류 코드를 ERROR_NO_MORE_ITEMS 또는 ERROR_INSUFFICIENT_BUFFER로 설정합니다.
우리의 논리:
자세한 내용을 제공할 수 있지만 독점 코드를 게시하는 것은 망설여집니다. 간단히 말해서 논리는 이에 대한 권장 단계를 따릅니다. 예는 여기에 있습니다.https://support.microsoft.com/en-us/help/815718/how-to-clear-the-cache-when-your-application-hosts-a-webbrowser-contro"캐시 그룹"을 다루지 않는다는 점만 빼면요. 주석 다음에 시작되는 코드를 스크롤할 수 있습니다.
// Start to delete URLs that do not belong to any group.
어쨌든 우리의 논리는 다음과 같습니다.
- 를 통해 캐시에 대한 핸들을 얻고
FindFirstUrlCacheEntry()
, 항목 버퍼에 필요한 만큼 메모리를 재할당하고,GetNextError()
성공 여부를 확인합니다. - 를 통해 이 항목과 후속 항목을 반복하고
FindNextUrlCacheEntry()
, 항목 버퍼에 필요한 만큼 메모리를 재할당하고, true/false 반환 값을 확인합니다. - true인 경우:
DeleteUrlCacheEntry()
호스트 이름이 일치하고 항목 유형이COOKIE_CACHE_ENTRY
또는 유형이 아닌 경우 호출합니다HISTORY_CACHE_ENTRY
. 그렇지 않으면 항목을 건너뜁니다. - 거짓인 경우: 값을 확인
GetNextError()
하고 다음을 수행합니다. ERROR_NO_MORE_ITEMS
(=259): 루프를 종료하고 ActiveX 진입점을 성공적으로 종료합니다.- ActiveX 컨트롤 진입점의 반환 코드를 오류 코드(이 경우 2)로 설정합니다. JavaScript 호출자는 0을 테스트하고 지원팀에 전화하라는 지침과 함께 사용자의 오류에 대한 경고()를 중지합니다.
해결 방법:
애플리케이션이 작동할 수 있도록 하는 한 가지 해결 방법은 사용자가 전체 IE 캐시를 삭제하도록 하는 것입니다. 이 시점에서 FindFirstUrlCacheEntry()는 아무것도 찾지 못하며 삭제 루프가 시작되지 않습니다. (사실 이 경우는 명시적으로 테스트할 수 없기 때문에 추측입니다. 아래 디버깅 참고사항을 참고하세요.) 어쨌든 오류는 발생하지 않습니다.
디버깅 노력:
JavaScript 코드에 중단점을 설정하고 비교가 완료되기 전에 버전 문자열을 수동으로 수정하여 쿠키 버전 불일치를 강제할 수 있습니다. 이는 코드를 "속여서" ActiveX 컨트롤 삭제 진입점을 호출하게 합니다.
여기서는 극히 제한적입니다. 문제가 재현되는 단일 PC에 가끔씩만 액세스할 수 있습니다. msvsmon.exe를 통해 VisualStudio 디버거를 원격으로 연결하려는 노력을 통해 ATLTRACE(일명 printf) 출력을 볼 수 있지만 실행할 중단점을 얻을 수는 없습니다. 대신 ATLTRACE() 및 Sysinternals의 DebugView로 축소되었습니다.
우리가 보는 것은 ERROR_FILE_NOT_FOUND가 ERROR_NO_MORE_FILES처럼 작동한다는 것입니다. 즉, C++ 코드를 해킹하여 두 조건 모두에서 다음 찾기/삭제 루프를 종료하면 항상 예상된 파일 중 일부를 삭제하고 오류 없이 완료됩니다. 그러나 실제로 더 이상 파일이 없는지 여부는 알기 어렵습니다. IE 캐시를 수동으로 조사하는 것은 기껏해야 까다로우며 PC의 보안 조치로 인해 더욱 그렇습니다. 예를 들어 하위 폴더는 C:\Users\<usr>\AppData\Local\Microsoft\Windows\INetCache\Low\IE\...
cmd, power-shell dir 또는 ls 명령에 표시되지 않습니다. 그럼에도 불구하고 클라이언트에서 우리 앱의 더 많은 페이지를 방문할수록 앱이 완료되기 전에 ActiveX 컨트롤에 의해 더 많은 파일이 지워지는 것 같습니다. 이에 대한 작업 사례는 DebugView에서 다음과 같습니다.
[12536] DeleteCacheEntries(10.3.31.28:9080) BEGIN: hr=0 pErrCode=0
[12536] deleteCacheEntriesFor(10.3.31.28:9080) BEGIN
[12536] deleteCacheEntriesFor(10.3.31.28:9080) - getFirstEntry returned 0
[12536] deleteCacheEntryIf - (lpdErrCd now=0) deleting: http://10.3.31.28:9080/images/our.jpg ==> C:\Users\<user>\AppData\Local\Microsoft\Windows\INetCache\Low\IE\Q6HU0E35\our[1].jpg
....<lots more "deleting" ...>
[12536] getNextEntry - FindNExtUrlCacheEntry returned 2: (null) ==> (null)
[12536] getNextEntry - ERROR=2: (null) ==> (null)
[12536] deleteCacheEntriesFor(10.3.31.28:9080) - getNextEntry returned 2. Breaking out of loop!
[12536] deleteCacheEntriesFor(10.3.31.28:9080) - Complete. Closing URL cache handle current. errCd=0
[12536] deleteCacheEntriesFor(10.3.31.28:9080) - Done. Returning 0
또한 ERROR_FILE_NOT_FOUND는 단순히 캐시 항목이 있지만 어떻게든 볼 수 없다는 의미라고 가정하는 해킹도 시도했습니다. 즉, 루프를 벗어나는 대신 삭제할 수 있는 다른 "찾을 수 있는" 항목을 만나기를 희망하면서 계속 진행합니다. 하지만 이 경우 루프는 절대 종료되지 않습니다. 여기서 "절대로"는 다음과 같은 경우가 몇 분 동안 2백만 건 이상 발생함을 의미합니다.
[5712] getNextEntry - FindNExtUrlCacheEntry returned 2: (null) ==> (null)
[5712] deleteCacheEntriesFor(10.3.31.28:9080) - getNextEntry returned 2. CONTINUING!
...<forever>...
추측:
다음은 무슨 일이 일어나고 있는지에 대한 약한 추측입니다.
- 일부 보안 소프트웨어 또는 Windows KB 패치가 C++ 실행에 개입하여 SetLastError(2)를 호출하여 FindNextUrlCacheEntry()에 의해 설정되는 문서화된 값을 깨뜨립니다.
- 우리 코드가 이전 버전의 VisualStudio SDK에 연결되어 있어 일부 코드 왜곡이 발생했습니다.
- 우리는 스택이나 레지스터 손상에 대해 생각해 보았지만 이에 비해 동작이 너무 재현 가능한 것 같습니다. 아직...?
- FindNextUrlCacheEntry()에는 실제로 뭔가가 있지만 보안으로 인해 어떻게든 표시되지 않으며 캐시의 "커서"가 더 이상 진행되지 않습니다.
답변1
이는 Micrsoft WinINet API의 결함으로 인한 것으로 밝혀졌습니다. 자세한 내용은 devcommunity/visualstudio 포럼의 스레드에서 확인할 수 있습니다.여기. (답변은 마지막 포스팅 하단에 있습니다.)