記一次CurrentDirectory致使的問題

如今項目裏須要實現一個功能以下:cookie

A.exe把B.exe複製到臨時目錄,而後A.exe退出,B.exe負責把A.exe所在的整個目錄刪除。函數

實現:this

A.exe用CreateProcess建立B.exe時,把所在目錄做爲命令行參數傳遞到B.exe。而後B.exe中對這個目錄進行遞歸刪除。atom

A.exe建立進程的代碼。大概以下spa

 1         if( !::CreateProcessW(
 2             nullptr,                                                    // No module name (use command line)
 3             pathTmp.m_strPath.GetBuffer(pathTmp.m_strPath.GetLength()), // Command line
 4             nullptr,                                                    // Process handle not inheritable
 5             nullptr,                                                    // Thread handle not inheritable
 6             FALSE,                                                      // Set handle inheritance to FALSE
 7             0,                                                          // No creation flags
 8             nullptr,                                                    // Use parent's environment block
 9             nullptr,                                    // Use parent's starting directory
10             &si,                                                        // Pointer to STARTUPINFO structure
11             &pi )                                                       // Pointer to PROCESS_INFORMATION structure
12             )
13         {
14             _DTL_LOG_OUT(_T("CreateProcess failed (%d).\n"), GetLastError() );
15             return 1;
16         }

假設如今A.exe所在的目錄爲C:\test,B.exe在C:\test2,而後經過以上代碼建立了B.exe。命令行

開始執行遞歸刪除文件及目錄,刪除到最後的剩下C:\test這個空目錄時。發現這個文件夾沒有被刪除掉,RemoveDirectoryW返回FALSE,error code 爲ERROR_SHARING_VIOLATION 32 (0x20),用Procexp查看進程打開句柄,發現句柄列表中居然有這個路徑。調試

第一直覺是FindFile時有句柄沒關閉,因而立刻去檢查FindFile部分的代碼,並無任何問題。code

原來的刪除文件夾是使用的SHFileOperation去刪除,卻沒有這個問題。再次用SHFileOperation去刪除,用Procexp看句柄,雖然也是佔用着,可是卻能夠刪除掉。blog

究竟SHFileOperation是怎麼處理的呢?遞歸

下面是個人分析過程:

先在Windbg裏看看吧。附加到B進程。而後

bp SHELL32!SHFileOperationW,

用uf SHELL32!SHFileOperationW進行反彙編,而後單步跟蹤,發現最終是調用了一個

SHELL32!SHFileOperationW+0x168:
7744d321 8b1d70193a77    mov     ebx,dword ptr [SHELL32!_imp__SetThreadExecutionState (773a1970)]
7744d327 6801000080      push    80000001h
7744d32c ffd3            call    ebx
7744d32e 56              push    esi
7744d32f e8c5f7ffff      call    SHELL32!MoveCopyDriver (7744caf9)

F11繼續步入到MoveCopyDriver

跟到下面這塊

 1 SHELL32!MoveCopyDriver+0x3c2:
 2 7744cebb 6a00            push    0
 3 7744cebd 8d8514f9ffff    lea     eax,[ebp-6ECh]
 4 7744cec3 50              push    eax
 5 7744cec4 8d8564fbffff    lea     eax,[ebp-49Ch]
 6 7744ceca 50              push    eax
 7 7744cecb 6804010000      push    104h
 8 7744ced0 8d86fc020000    lea     eax,[esi+2FCh]
 9 7744ced6 50              push    eax
10 7744ced7 53              push    ebx
11 7744ced8 56              push    esi
12 7744ced9 e8c7d6ffff      call    SHELL32!EnterDir_Move (7744a5a5)
13 7744cede e9bb000000      jmp     SHELL32!MoveCopyDriver+0x4a5 (7744cf9e)
14 
15 SHELL32!MoveCopyDriver+0x3ea:
16 7744cee3 53              push    ebx
17 7744cee4 56              push    esi
18 7744cee5 e884acffff      call    SHELL32!LeaveDir_Delete (77447b6e)
19 7744ceea e9af000000      jmp     SHELL32!MoveCopyDriver+0x4a5 (7744cf9e)

前面沒發現什麼特殊的地方。當執行過SHELL32!LeaveDir_Delete時。watch窗口裏@$peb發現CurrentDirectory發生改變。因而果斷的把這個函數反彙編

 1 0:000> uf SHELL32!LeaveDir_Delete
 2 SHELL32!LeaveDir_Delete:
 3 77447b6e 8bff            mov     edi,edi
 4 77447b70 55              push    ebp
 5 77447b71 8bec            mov     ebp,esp
 6 77447b73 57              push    edi
 7 77447b74 8b7d0c          mov     edi,dword ptr [ebp+0Ch]
 8 77447b77 57              push    edi
 9 77447b78 ff153c213a77    call    dword ptr [SHELL32!_imp__PathIsRootW (773a213c)]
10 77447b7e 85c0            test    eax,eax
11 77447b80 7404            je      SHELL32!LeaveDir_Delete+0x18 (77447b86)
12 
13 SHELL32!LeaveDir_Delete+0x14:
14 77447b82 33c0            xor     eax,eax
15 77447b84 eb2a            jmp     SHELL32!LeaveDir_Delete+0x42 (77447bb0)
16 
17 SHELL32!LeaveDir_Delete+0x18:
18 77447b86 56              push    esi
19 77447b87 57              push    edi
20 77447b88 e883e8ffff      call    SHELL32!AvoidCurrentDirectory (77446410)
21 77447b8d 57              push    edi
22 77447b8e e8cf8f0000      call    SHELL32!Win32RemoveDirectory (77450b62)

這裏先判斷了是否爲根目錄,否就調用SHELL32!AvoidCurrentDirectory,而後才刪除目錄,從名字上這個函數應該就是我要找的了。繼續反彙編該函數

 1 0:000> uf SHELL32!AvoidCurrentDirectory
 2 SHELL32!AvoidCurrentDirectory:
 3 77446410 8bff            mov     edi,edi
 4 77446412 55              push    ebp
 5 77446413 8bec            mov     ebp,esp
 6 77446415 81ec0c020000    sub     esp,20Ch
 7 7744641b a108c55977      mov     eax,dword ptr [SHELL32!__security_cookie (7759c508)]
 8 77446420 56              push    esi
 9 77446421 8b7508          mov     esi,dword ptr [ebp+8]
10 77446424 8945fc          mov     dword ptr [ebp-4],eax
11 77446427 8d85f4fdffff    lea     eax,[ebp-20Ch]
12 7744642d 50              push    eax
13 7744642e 6804010000      push    104h
14 77446433 ff15a4193a77    call    dword ptr [SHELL32!_imp__GetCurrentDirectoryW (773a19a4)]
15 77446439 56              push    esi
16 7744643a 8d85f4fdffff    lea     eax,[ebp-20Ch]
17 77446440 50              push    eax
18 77446441 ff15401a3a77    call    dword ptr [SHELL32!_imp__lstrcmpiW (773a1a40)]
19 77446447 85c0            test    eax,eax
20 77446449 5e              pop     esi
21 7744644a 751a            jne     SHELL32!AvoidCurrentDirectory+0x56 (77446466)
22 
23 SHELL32!AvoidCurrentDirectory+0x3c:
24 7744644c 8d85f4fdffff    lea     eax,[ebp-20Ch]
25 77446452 50              push    eax
26 77446453 ff15f4203a77    call    dword ptr [SHELL32!_imp__PathRemoveFileSpecW (773a20f4)]
27 77446459 8d85f4fdffff    lea     eax,[ebp-20Ch]
28 7744645f 50              push    eax
29 77446460 ff15a8193a77    call    dword ptr [SHELL32!_imp__SetCurrentDirectoryW (773a19a8)]

終於看到了真相。這個函數判斷當前目錄是否和刪除的目錄相等,若是相等,則將當前目錄設置爲上一級目錄。

因而我想到了刪除失敗可能和當前目錄有關係,在MSDN裏看到關於CreateProcess關於lpCurrentDirectory參數的介紹,若是爲NULL,則把當前目錄做爲子進程的當前目錄,此時基本能夠肯定,是CurrentDirectory佔用了文件句柄,致使目錄沒法刪除,那麼解決辦法已經很明顯,建立子進程時把lpCurrentDirectory設置成子進程文件的目錄便可。

最後爲了驗證。在nt4的源碼裏找到SHFileOperation的實現。SHFileOperation中使用了一個COPY_STATE結構體來存儲各類設置信息。

 1 typedef struct {
 2     int          nSourceFiles;
 3     LPTSTR        lpCopyBuffer; // global file copy buffer
 4     UINT         uSize;         // size of this buffer
 5     FILEOP_FLAGS fFlags;        // from SHFILEOPSTRUCT
 6     UINT         wFunc;         // FO_ function type
 7     HWND         hwndProgress;  // dialog/progress window
 8     HWND         hwndCaller;    // window to do stuff on
 9     HWND         hwndDlgParent; // parent window for message boxes
10     CONFIRM_DATA cd;            // confirmation stuff
11     DWORD        dwStartTime;   // start time to implement progress timeout
12     DWORD        dwShowTime;     // when did the dialog get shown
13 
14     LPUNDOATOM  lpua;           // the undo atom that this file operation will make
15     BOOL        fNoConfirmRecycle;
16     BOOL        bAbort;
17     BOOL        fNonCopyProgress;
18     BOOL        fMerge;   // are we doing a merge of folders
19 
20         // folowing fields are used for giving estimated time for completion
21         // feedback to the user during longer than MINTIME4FEEDBACK operations
22     BOOL  fShowTime;            //
23     DWORD dwTimeLeft;       // last reported time left, necessary for histerisis
24     DWORD dwBytesLeft;
25     DWORD dwPreviousTime;       // calculate transfer rate
26     DWORD dwBytesRead;          // Bytes read in the interval dwNewTime-dwPreviousTime
27     DWORD dwBytesPerSec;
28     LPCTSTR lpszProgressTitle;
29     LPSHFILEOPSTRUCT lpfo;
30 
31     BOOL        fMove;
32     BOOL        fInitialize;
33     WIN32_FIND_DATA wfd;
34 
35 } COPY_STATE, *LPCOPY_STATE;

SHFileOperation會將SHFILEOPSTRUCT轉換成COPY_STATE,而後調用一個MoveCopyDriver的函數,將COPY_STATE傳到MoveCopyDriver中,

其中呢會調用一個AvoidCurrentDirectory的函數,這個函數實現以下

 1 // make sure we aren't operating on the current dir to avoid
 2 // ERROR_CURRENT_DIRECTORY kinda errors
 3 
 4 void AvoidCurrentDirectory(LPCTSTR p)
 5 {
 6    TCHAR szTemp[MAX_PATH];
 7 
 8    GetCurrentDirectory(ARRAYSIZE(szTemp), szTemp);
 9    if (lstrcmpi(szTemp, p) == 0)
10    {
11        DebugMsg(DM_TRACE, TEXT("operating on current dir(%s), cd .."), p);
12        PathRemoveFileSpec(szTemp);
13        SetCurrentDirectory(szTemp);
14     }
15 }

基本上和以前經過windbg調試看到的差不了多少。

總結:進程的CurrentDirectory也會佔用該文件句柄,因此若是要對該目錄進行刪除的話,就須要先把當前目錄設成和這個沒有關係的其餘目錄。

相關文章
相關標籤/搜索