为什么 WinInet 的 FindNextUrlCacheEntry() 返回 ERROR_FILE_NOT_FOUND=2 而不是 ERROR_NO_MORE_ITEMS=295

为什么 WinInet 的 FindNextUrlCacheEntry() 返回 ERROR_FILE_NOT_FOUND=2 而不是 ERROR_NO_MORE_ITEMS=295

描述

根据标题,WinInet 的FindNextUrlCacheEntry()结果为未记录的错误代码 2 (ERROR_FILE_NOT_FOUND),而不是 ERROR_NO_MORE_ITEMS=295。有人可以解释原因或建议其他调查方法吗?

环境

  • Windows 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 中运行的 C++ ActiveX 控件(大约是 VisualStudio 2006)。其目的是对与我们的应用服务器主机名匹配的文件进行部分 IE 缓存清理。(此操作通过比较来自服务器的请求 cookie 值与客户端上最后存储的 cookie 值之间的版本号来控制。仅当检测到版本不匹配时才会发生删除。)

多年来,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.

无论如何,我们的逻辑如下:

  1. 通过获取缓存句柄FindFirstUrlCacheEntry(),根据需要重新分配条目缓冲区的内存,并检查GetNextError()是否成功
  2. 通过循环遍历此条目和后续条目FindNextUrlCacheEntry(),根据需要重新分配条目缓冲区的内存,检查返回值为真/假
  3. DeleteUrlCacheEntry()如果为真:如果主机名匹配且条目不是类型,则调用:COOKIE_CACHE_ENTRYHISTORY_CACHE_ENTRY。否则跳过条目
  4. 如果为假:我们检查值GetNextError()并执行以下操作:
  5. ERROR_NO_MORE_ITEMS(=259):终止循环并成功退出 ActiveX 入口点
  6. 将 ActiveX 控件入口点的返回代码设置为错误代码(在我们的例子中为 2)。JavaScript 调用者测试 0 并停止 alert() 的错误用户,并指示他们调用 Support

解决方法:

允许应用程序运行的一个解决方法是让用户删除整个 IE 缓存。此时 FindFirstUrlCacheEntry() 找不到任何内容,因此永远不会进入删除循环。(实际上,这只是猜测,因为我们无法明确测试这种情况。请参阅下面的调试说明。)无论如何,错误不会发生

调试工作:

我们可以在 JavaScript 代码中设置断点,并在比较完成之前手动修改版本字符串,从而强制 Cookie 版本不匹配。这会“欺骗”代码调用 ActiveX 控件删除入口点。

我们在这方面的能力非常有限。我们只能偶尔访问一台问题重现的 PC。通过 msvsmon.exe 远程连接 VisualStudio 调试器的努力使我们能够看到 ATLTRACE(又名 printf)输出,但我们无法触发断点。我们只能使用 ATLTRACE() 和 Sysinternals 的 DebugView。

我们看到 ERROR_FILE_NOT_FOUND 的行为类似于 ERROR_NO_MORE_FILES。也就是说,如果我们破解 C++ 代码以终止任一条件下的 find-next/delete 循环,它总是会删除一些预期的文件并完成而不会出现错误。但是,很难知道是否真的没有更多文件:手动查询 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 仅仅意味着那里有一个缓存条目,但不知何故我们被阻止看到它。也就是说,我们不是跳出循环,而是继续,希望遇到其他“可找到”的条目并可能将其删除。但在这种情况下,循环永远不会终止。这里的“永远不会”意味着几分钟和超过 200 万个案例:

[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 论坛上的相关主题中找到。这里(答案在我的最后一篇帖子底部。)

相关内容