1

Description

As per the title, WinInet's FindNextUrlCacheEntry() results in an undocumented error code of 2 (ERROR_FILE_NOT_FOUND) instead of ERROR_NO_MORE_ITEMS=295. Can someone explain why or suggest other ways to investigate?

Environment

  • Windows 10
  • IE 11
  • VisualStudio 2006 (but can rebuild on VS2010)

Synopsis

Details further below, but the crux of the issue is that inside a loop, this code fragment:

/ 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 ....>

results in this trace output:

>>[12536] getNextEntry - FindNExtUrlCacheEntry returned 2: (null) ==> (null)

If we hack our code to treat this condition similarly to ERROR_NO_MORE_ITEMS, things work as expected. If we ignore this and continue to try and get more entries, we get an infinite loop.

Overview

Note: for this post, I'm treating an error code of 2 as synonymous with ERROR_FILE_NOT_FOUND, but since it's an undocumented code for the API call in question the 2 may actually be from something else.

We have a C++ ActiveX control circa VisualStudio 2006 that runs in IE. It's purpose is to do a partial IE cache cleanup for files that match the hostname of our application server. (This action is controlled by comparing version numbers between a request cookie value from the server against the one last stored on the client. The deletion only occurs when a version mismatch is detected.)

The ActiveX control has done it's job fine for many years. But recently started failing as mentioned above. We don't know if it's due to a KB windows update or recent security initiatives, or something else. Our code properly handles the ERROR_NO_MORE_ITEMS case and terminates its work. But it treats the 2 as an error and the app doesn't continue.

The documentation for FindNextUrlCacheEntry() is here: https://docs.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-findnexturlcacheentrya The call returns true/false and sets the error code to either ERROR_NO_MORE_ITEMS or ERROR_INSUFFICIENT_BUFFER.

Our logic:

I can provide details but hesitate to post proprietary code. Briefly, the logic follows recommended steps for this. An example is here https://support.microsoft.com/en-us/help/815718/how-to-clear-the-cache-when-your-application-hosts-a-webbrowser-contro except that we don't deal with "cache groups". You can scroll do the the code that starts after the comment

// Start to delete URLs that do not belong to any group.

Our logic anyway is as follows:

  1. Get a handle to the cache via FindFirstUrlCacheEntry(), reallocating memory as needed for the entry buffer, and checking GetNextError() for success
  2. Loop over this and subsequent entries via FindNextUrlCacheEntry(), reallocating memory as needed for the entry buffer, checking for true/false return value
  3. If true: Call DeleteUrlCacheEntry() if the hostnames match and the entry isn't of type: COOKIE_CACHE_ENTRY or HISTORY_CACHE_ENTRY. Otherwise skip the entry
  4. If false: we check the value of GetNextError() and do the following:
  5. ERROR_NO_MORE_ITEMS(=259): terminate the loop and exit the ActiveX entry point successfully
  6. Set the return code of the ActiveX control entry point to the error code (2 in our case). The JavaScript caller tests for 0 and stops alert()'s the user of the error with instructions to call Support

Work-around:

The one work-around to allow the application to function is to have the user delete the entire IE cache. At this point the FindFirstUrlCacheEntry() finds nothing and the deletion loop is never entered. (Actually, this is speculation since we can't test this case explicitly. See debugging notes below.) In any case, the error doesn't occur

Debugging efforts:

We can force the cookie version mismatch by setting a breakpoint in the JavaScript code and manually modifying the version string before the comparison is done. This "fools" the code into calling the ActiveX control delete entry point.

We're extremely limited here. We have only occasional access to a single PC where the problem reproduces. Efforts to remotely attach a VisualStudio debugger via msvsmon.exe allow us to see ATLTRACE (aka printf) output, but we can't get breakpoints to fire. We're instead reduced to ATLTRACE() and Sysinternals' DebugView.

What we see is that the ERROR_FILE_NOT_FOUND acts like ERROR_NO_MORE_FILES. THat is, if we hack the C++ code to terminate the find-next/delete loop on eithe condition, it always deletes some expected files and completes without error. It's difficult to know, however, if there are really no more files or not: Manually interrogating the IE cache is tricky at best, and security measures on our PCs make it even more so. E.g. sub-folders of C:\Users\<usr>\AppData\Local\Microsoft\Windows\INetCache\Low\IE\... don't show up in a cmd or power-shell dir or ls command. Still, it does seem that the more pages of our app that we hit in the client, the more files are cleared by the ActiveX control before it finishes. A working case of this looks like this in 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

We also tried a hack that assumes that ERROR_FILE_NOT_FOUND simply means there's a cache entry there but somehow we're blocked from seeing it. That is, instead of breaking out of the loop, we instead continue in hopes of encountering other "findable" entries to possibly delete. But in this case, the loop never terminates. Here "never" means several minutes and over 2 million cases of:

[5712] getNextEntry - FindNExtUrlCacheEntry returned 2: (null) ==> (null)
[5712] deleteCacheEntriesFor(10.3.31.28:9080) - getNextEntry returned 2. CONTINUING!
...<forever>...

Speculation:

Here are some admittedly weak speculations on what might be going on

  • Some security software or Windows KB patch is intervening in the C++ execution and causing SetLastError(2) to be called, smashing the documented values being set by FindNextUrlCacheEntry()
  • Our code is linking to older versions of the VisualStudio SDK and we're hitting some code skew
  • We thought about stack or register corruption but the behavior seems too reproducible for this. Still...?
  • There really is something there for FindNextUrlCacheEntry(), but security is preventing it from being visible somehow and it's "cursor" in the cache never advances

1 Answer 1

0

This turns out to be due to a defect in the Micrsoft WinINet API. Full details can be found in the thread for this on their devcommunity/visualstudio forum here. (Answer is in my final posts at the bottom.)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .