WinInet の FindNextUrlCacheEntry() が ERROR_NO_MORE_ITEMS=295 ではなく ERROR_FILE_NOT_FOUND=2 を返すのはなぜですか?

WinInet の FindNextUrlCacheEntry() が ERROR_NO_MORE_ITEMS=295 ではなく ERROR_FILE_NOT_FOUND=2 を返すのはなぜですか?

説明

タイトルのとおり、WinInet のFindNextUrlCacheEntry()結果は、ERROR_NO_MORE_ITEMS=295 ではなく、文書化されていないエラー コード 2 (ERROR_FILE_NOT_FOUND) になります。理由を説明していただくか、調査する他の方法を提案していただけますか?

環境

  • ウィンドウズ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 は実際には別の原因によるものである可能性があります。

IE で実行される VisualStudio 2006 頃の C++ ActiveX コントロールがあります。その目的は、アプリケーション サーバーのホスト名に一致するファイルの IE キャッシュを部分的にクリーンアップすることです。(このアクションは、サーバーからの要求 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. trueの場合:DeleteUrlCacheEntry()ホスト名が一致し、エントリがタイプ:COOKIE_CACHE_ENTRYまたはでない場合に呼び出しますHISTORY_CACHE_ENTRY。それ以外の場合はエントリをスキップします。
  4. false の場合: の値をチェックしGetNextError()、次の操作を実行します。
  5. ERROR_NO_MORE_ITEMS(=259): ループを終了し、ActiveX エントリ ポイントを正常に終了します
  6. ActiveXコントロールのエントリポイントの戻りコードをエラーコード(この場合は2)に設定します。JavaScriptの呼び出し元は0をテストし、エラーのユーザーにサポートに電話するように指示するalert()を停止します。

回避策:

アプリケーションを機能させるための唯一の回避策は、ユーザーに IE キャッシュ全体を削除させることです。この時点では、FindFirstUrlCacheEntry() は何も見つけられず、削除ループに入ることはありません。(実際には、このケースを明示的にテストできないため、これは推測です。以下のデバッグ ノートを参照してください。) いずれにしても、エラーは発生しません。

デバッグの取り組み:

JavaScript コードにブレークポイントを設定し、比較が完了する前にバージョン文字列を手動で変更することで、Cookie バージョンの不一致を強制できます。これにより、コードが「騙されて」ActiveX コントロールの削除エントリ ポイントが呼び出されます。

ここでは非常に制限があります。問題が再現する 1 台の 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 は単にキャッシュ エントリが存在するが、何らかの理由でそれを見ることができないことを意味すると仮定するハックも試しました。つまり、ループから抜け出す代わりに、削除できる可能性のある他の「見つけられる」エントリに遭遇することを期待して続行します。ただし、この場合、ループは決して終了しません。ここで「決して」とは、数分と 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

これは、Microsoft WinINet API の欠陥が原因であることが判明しました。詳細については、devcommunity/visualstudio フォーラムのスレッドを参照してください。ここ(答えは私の最後の投稿の一番下にあります。)

関連情報