Undefeatable files & folders in Windows XP SP2 – a bug in SHFileOperationW

Recently I was surprised with one interesting behavior of my Windows XP box. I was playing with long name files and noticed that major part of my shell extensions do not work with files, whose path is longer then 260 symbols. I also noticed, that Windows Shell does not allow me to create long file names. When you create a folder, you’re allowed to specify 260 symbols and that’s all! Here is what I see when I try to create text document in “extra long” folder:

Fig 1. (impossible to create a text document in long named folder)

However, theoretically we’re allowed to create “long paths”, here is what MSDN says about long file names(CreateFile documentation): In the ANSI version of this function, the name is limited to MAX_PATH characters. To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend “\\?\” to the path.

OK, I have a chance to test this feature. Let’s create a test application that creates several folders with really long names. The application creates a folder on drive C, let’s call it “A”. Then it creates another folder placed in “A”, let’s call it “A1”, and finally it creates three files in “A1”.

I run the application on my computer, and … folder is created, but windows does not allow me browse the files in it. I see the following msgbox when trying to access the A1:

Fig. 2. (impossible to access “A1” folder and see files)

The most interesting thing is that Windows can’t delete these files. When you delete the “A” folder Windows says “Cannot delete … The filename you specified is not valid or too long. Specify a different name.”

Things are getting more and more interesting. Setting breakpoint in SoftIce to DeleteFileW && DeleteFileA functions:

: bpx DeleteFileW
: bpx DeleteFileA

Gives me nothing, functions are not called when I try to delete the folder via explorer context menu. At the same time, it’s worth to check what happens in explorer.exe before files and folders are going to be deleted! Let’s set breakpoints in SoftIce to SHFileOperationW, SHFileOperationA functions:

: bd *
: bpx SHFileOperationA
: bpx SHFileOperationW

Then, let’s delete folder once again, and … SHFileOperationW is invoked by explorer.exe. This already gives me a hint, that SHFileOperationW has a bug, and shows strange msgbox without actually removing the folders & files.

Closer look at the function determines the following. Function can be divided into two logical parts. First creates a thread using SHCreateThread in order to display UI (for example, the dialog that tells you : “Do you really want to delete the _item_name”) (I think there is not need to cover this direction, everything is pretty simple there). Second part (which is executed in the explorer thread) is actually the logics that enumerates all folders and files and forms the list of items to delete.

Enumeration is done … of course, using FindFirstFile and simular Find* functions. Let’s take a look at MSDN regarding the internals of WIN32_FIND_DATA structure:

typedef struct _WIN32_FIND_DATA
{
[...]
TCHAR cFileName[MAX_PATH];
[...]
}WIN32_FIND_DATA, *PWIN32_FIND_DATA, *LPWIN32_FIND_DATA;

Seems like cFileName member cannot hold more then 260 symbols (MAX_PATH). However, this fact is not the actual answer to my question: where the buggy code fails.

Let’s return back to SoftIce. Make sure that breakpoint to SHFileOperationW is set (just write the : bl command and see the list of breakpoints). Delete the folder once again, and step by step continue analysis:

[Inside the SHFileOperationW] Here is the creation of UI thread:[/Inside the SHFileOperationW]

.text:7CA6FDDE 68 2B 5C A9 7C              push    offset sub_7CA95C2B
.text:7CA6FDE3 6A 00                             push    0
.text:7CA6FDE5 56                                  push    esi
.text:7CA6FDE6 68 C3 F1 A6 7C               push    offset loc_7CA6F1C3
.text:7CA6FDEB FF 15 74 1B 9C 7C           call    ds:SHCreateThread
.text:7CA6FDF1 EB 0F                             jmp     short loc_7CA6FE02    (jump to additional checks, and finally it goes to 7CA6F64A)

[Inside the SHFileOperationW, 7CA6F64A] Here is the top level routine (7CA6F64A) that after different checks calls enumeration of files & folders [/Inside the SHFileOperationW]

.text:7CA6F6BC 74 28                            jz      short loc_7CA6F6E6
.text:7CA6F6BE 48                                dec     eax
.text:7CA6F6BF 74 1E                            jz      short loc_7CA6F6DF
.text:7CA6F6C1 48                                dec     eax
.text:7CA6F6C2 74 14                            jz      short loc_7CA6F6D8
.text:7CA6F6C4 48                                dec     eax
.text:7CA6F6C5 8D 45 C4                       lea     eax, [ebp+var_3C]
.text:7CA6F6C8 74 07                            jz      short loc_7CA6F6D1
.text:7CA6F6CA 68 74 3B 9D 7C             push    offset aCopy?   ; "Copy? "
.text:7CA6F6CF EB 1D                           jmp     short loc_7CA6F6EE
.text:7CA6F6D1                   ; ---------------------------------------------------------------------------
.text:7CA6F6D1
.text:7CA6F6D1                   loc_7CA6F6D1:                           ; CODE XREF: DoEnumeration+7Ej
.text:7CA6F6D1 68 64 3B 9D 7C              push    offset aRename  ; "Rename"
.text:7CA6F6D6 EB 16                            jmp     short loc_7CA6F6EE
.text:7CA6F6D8                   ; ---------------------------------------------------------------------------
.text:7CA6F6D8
.text:7CA6F6D8                   loc_7CA6F6D8:                           ; CODE XREF: DoEnumeration+78j
.text:7CA6F6D8 68 A4 4E 9D 7C              push    offset aDelete  ; "Delete"
.text:7CA6F6DD EB 0C                            jmp     short loc_7CA6F6EB
.text:7CA6F6DF                   ; ---------------------------------------------------------------------------
.text:7CA6F6DF
.text:7CA6F6DF                   loc_7CA6F6DF:                           ; CODE XREF: DoEnumeration+75j
.text:7CA6F6DF 68 54 3B 9D 7C               push    offset aCopy    ; "Copy  "
.text:7CA6F6E4 EB 05                             jmp     short loc_7CA6F6EB

[/Inside the SHFileOperationW, 7CA6F64A] Here is the top level routine (7CA6F64A) that after different checks calls enumeration of files & folders [/Inside the SHFileOperationW]

The most interesting things happens when CPU executes this line:

7CA6EC3B   E8 5EFCFFFF      CALL SHELL32.7CA6E89E

7CA6E89E is the proc that enumerates files & folders, and if something went wrong, it returns non-zero number in EAX, which is mapped to shell error. If this function returnes zero, it means that everything is OK. Let’s take a look deeper into it. First looks shows that there is common and well-known sequence of FindFirstFile->FindNextFile->FindClose->recursive calling itself:

[Inside the SHFileOperationW, 7CA6E89E] enumeration of files & folders [/Inside the SHFileOperationW]

.text:7CA6E8AE FF 75 10                          push    [ebp+pszPath]   ; lpFileName
.text:7CA6E8B1 C7 45 F8 01 00 00+           mov     [ebp+var_8], 1
.text:7CA6E8B8 FF 15 EC 15 9C 7C            call    ds:FindFirstFileW

[...]

.text:7CA6E96D 57                                push    edi             ; lpFindFileData
.text:7CA6E96E FF 75 FC                       push    [ebp+hFindFile] ; hFindFile
.text:7CA6E971 FF 15 F0 15 9C 7C          call    ds:FindNextFileW
.text:7CA6E977 85 C0                            test    eax, eax
.text:7CA6E979 75 8E                            jnz     short loc_7CA6E909
[...]

.text:7CA6EA41                   loc_7CA6EA41:                           ; CODE XREF: EnumerateFiles+15Dj
.text:7CA6EA41 FF 75 FC                          push    [ebp+hFindFile] ; hFindFile
.text:7CA6EA44 FF 15 F4 15 9C 7C            call    ds:FindClose
.text:7CA6EA4A 8B C7                             mov     eax, edi
.text:7CA6EA4C E9 A0 00 00 00                jmp     loc_7CA6EAF1

[...]

; two calls to PathAppendW in order to form the path and recursively pass it to 7CA6E89E:

.text:7CA6EA79 50                                push    eax             ; pMore
.text:7CA6EA7A 53                                push    ebx             ; pszPath
.text:7CA6EA7B FF 15 80 1C 9C 7C         call    ds:PathAppendW
.text:7CA6EA81 85 C0                            test    eax, eax
.text:7CA6EA83 74 44                            jz      short loc_7CA6EAC9
.text:7CA6EA85 68 50 87 9C 7C              push    offset a_       ; "*.*"
.text:7CA6EA8A 53                                push    ebx             ; pszPath
.text:7CA6EA8B FF 15 80 1C 9C 7C         call    ds:PathAppendW

[Inside the SHFileOperationW, 7CA6E89E] enumeration of files & folders [/Inside the SHFileOperationW]

And the problem is in the second call to PathAppendW. When the path exceeds the MAX_PATH, the PathAppendW failes. The following code fails in my case:

[inside the buggy shell code]

.text:7CA6EA85 68 50 87 9C 7C              push    offset a_       ; "*.*"
.text:7CA6EA8A 53                                push    ebx             ; pszPath
.text:7CA6EA8B FF 15 80 1C 9C 7C         call    ds:PathAppendW
.text:7CA6EA91 85 C0                            test    eax, eax
.text:7CA6EA93 74 24                            jz      short loc_7CA6EAB9

[/inside the buggy shell code]

And the execution is passed to 7CA6EAB9 which sets the 47Ch error and return from function. I hope this bug will be fixed (btw, it’s worth to check it on vista).

P.S. You can use the source file attached to this post to repeat my test case.

1,398 views

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

Identify yourself * Time limit is exhausted. Please reload CAPTCHA.