5月25日小品一则
MSDN 里有一个文件操作相关的例程叫做 MoveFileEx。这个例程的一段声明引起了我的注意:
Windows 2000 : If you specify the MOVEFILE_DELAY_UNTIL_REBOOT flag for dwFlags,
you cannot also prepend the filename that is specified by lpExistingFileName with "\\?".
由于我们往往不能假定调用者的输入,\\?\ 前缀也属基本需求(且为什么只是 lpExistingFileName 不让加前缀),我便很好奇这则声明的深层次原因。
可以想见的是,由于 Delayed Operations 操作时网络还不可用,所以 MoveFile 的操作源不应属于 remote 类型。这一点泄露的 Win-2K 源码交代的也很清楚:
......
//
// Check to see if the existing file is on a remote share. If it
// is, flag the error rather than let the operation silently fail
// because the delayed operations are done before the net is
// available. Rather than open the file and do a hard core file type,
// we just check for UNC in the file name. This isn't perfect, but it is
// pretty good. Chances are we can not open and manipulate the file. That is
// why the caller is using the delay until reboot option !
//
if ( RtlDetermineDosPathNameType_U(lpExistingFileName) == RtlPathTypeUncAbsolute ) {
Status = STATUS_INVALID_PARAMETER;
}
......
那么,微软人是如何判断操作源属于 remote share 的呢?用他们自己的话说是调用了“pretty”的 RtlDetermineDosPathNameType_U 函数。
RtlDetermineDosPathNameType_U 函数位于 ntdll 目录的 curdir.c 文件,判断的依据很简单,读取路径的前几个字节就够了:
RTL_PATH_TYPE
RtlDetermineDosPathNameType_U (
IN PCWSTR DosFileName
)
{
RTL_PATH_TYPE ReturnValue;
if ( IS_PATH_SEPARATOR_U(*DosFileName) ) {
if ( IS_PATH_SEPARATOR_U(*(DosFileName+1)) ) {
if ( DosFileName[2] == '.' ) {
if ( IS_PATH_SEPARATOR_U(*(DosFileName+3)) ){
ReturnValue = RtlPathTypeLocalDevice;
}
else if ( (*(DosFileName+3)) == UNICODE_NULL ){
ReturnValue = RtlPathTypeRootLocalDevice;
}
else {
ReturnValue = RtlPathTypeUncAbsolute;
}
}
else {
ReturnValue = RtlPathTypeUncAbsolute; // 可惜的是,这里存在 Bug
}
}
else {
ReturnValue = RtlPathTypeRooted;
}
}
else if (*DosFileName && *(DosFileName+1)==L':') {
if (IS_PATH_SEPARATOR_U(*(DosFileName+2))) {
ReturnValue = RtlPathTypeDriveAbsolute;
}
else {
ReturnValue = RtlPathTypeDriveRelative;
}
}
else {
ReturnValue = RtlPathTypeRelative;
}
return ReturnValue;
}
可惜的是,作者 Mark Lucovsky 前辈把情况想简单了。他认为两个 '\' (或'/') 符之后如果不跟 '.' 号的路径必定就是 UNC,可事实是 "\\?\" 被误判了。
路径 \\?\C:\A.txt 等 会被错误的认为是 RtlPathTypeUncAbsolute,进而 MoveFileEx 例程拒绝了这类参数 —— STATUS_INVALID_PARAMETER。
呵~ Mark Lucovsky 前辈也犯错误了...
这个 API 错误 Windows 2000 SP4 补丁都未予解决,类似的问题还存在于 Windows 2000 的 CreateHardLinkW 等例程内部。
(Mark Lucovsky 前辈在 26-Sep-1990 写过一个文件的测试小程序:base\client\tfile.c,看来案例的强度还不够,呵呵)
终于,Windows XP 解决了这个问题。“pretty”的 RtlDetermineDosPathNameType_U 函数被重写 —— 专门加入了 \\?\ 情况的判断,感兴趣您可以逆向对比。
而可爱的 ReactOS 团队依旧“沿袭”了这个 Bug:
http://doxygen.reactos.org/d9/d6e/lib_2rtl_2path_8c_source.html
也许他们还没有意识到“Bug 侵权” :P
回过头再看 MSDN,连则 Bug 的解释写的都和 “特性” 似的,险些被唬住...
霸气啊,微软。
keenjoy95
2011年5月25日18:21:56