Описание
Согласно заголовку, WinInet FindNextUrlCacheEntry()
выдает недокументированный код ошибки 2 (ERROR_FILE_NOT_FOUND) вместо ERROR_NO_MORE_ITEMS=295. Может ли кто-нибудь объяснить, почему, или предложить другие способы расследования?
Среда
- Виндовс 10
- IE11
- 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 на самом деле может быть чем-то другим.
У нас есть элемент управления ActiveX C++ около VisualStudio 2006, который работает в IE. Его цель — частичная очистка кэша IE для файлов, которые соответствуют имени хоста нашего сервера приложений. (Это действие контролируется путем сравнения номеров версий между значением cookie-файла запроса с сервера и последним, сохраненным на клиенте. Удаление происходит только при обнаружении несоответствия версий.)
Элемент управления ActiveX отлично справлялся со своей задачей много лет. Но недавно начал давать сбои, как упоминалось выше. Мы не знаем, связано ли это с обновлением базы знаний 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: Вызвать
DeleteUrlCacheEntry()
, если имена хостов совпадают и запись не имеет типа:COOKIE_CACHE_ENTRY
илиHISTORY_CACHE_ENTRY
. В противном случае пропустить запись - Если ложно: проверяем значение
GetNextError()
и делаем следующее: ERROR_NO_MORE_ITEMS
(=259): успешно завершить цикл и выйти из точки входа ActiveX- Установите код возврата точки входа элемента управления ActiveX на код ошибки (в нашем случае 2). Вызывающий JavaScript проверяет на 0 и останавливает alert() пользователя ошибки с инструкциями вызвать Support
Обходной путь:
Единственный способ обойти это, чтобы приложение работало, — заставить пользователя удалить весь кэш IE. На этом этапе FindFirstUrlCacheEntry() ничего не находит, и цикл удаления никогда не запускается. (На самом деле, это предположение, поскольку мы не можем проверить этот случай явно. См. примечания по отладке ниже.) В любом случае, ошибка не возникает
Усилия по отладке:
Мы можем принудительно вызвать несоответствие версий cookie, установив точку останова в коде JavaScript и вручную изменив строку версии до того, как будет выполнено сравнение. Это «обманывает» код, заставляя его вызывать точку входа удаления элемента управления ActiveX.
Здесь мы крайне ограничены. У нас есть только случайный доступ к одному ПК, на котором проблема воспроизводится. Попытки удаленно подключить отладчик VisualStudio через msvsmon.exe позволяют нам видеть вывод ATLTRACE (он же printf), но мы не можем заставить точки останова сработать. Вместо этого мы сведены к ATLTRACE() и DebugView от Sysinternals.
Мы видим, что ERROR_FILE_NOT_FOUND действует как ERROR_NO_MORE_FILES. То есть, если мы взломаем код C++, чтобы завершить цикл find-next/delete при любом условии, он всегда удалит некоторые ожидаемые файлы и завершится без ошибок. Однако трудно узнать, действительно ли больше нет файлов или нет: ручной опрос кэша IE в лучшем случае сложен, а меры безопасности на наших ПК делают это еще сложнее. Например, подпапки не 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
Оказывается, это из-за дефекта в API Microsoft WinINet. Полные подробности можно найти в теме на их форуме devcommunity/visualstudioздесь. (Ответ в моих последних сообщениях внизу.)