LookupAccountSid() 损坏内存堆
LookupAccountSid() corrupts memory heap
我花了几天时间追查表现为结构化异常的崩溃 0xC0000374
(堆损坏)...当然,只能在客户环境中重现。
将它缩小到这个(非常简化的)代码:
DWORD cchName = 0, cchDomain = 0;
SID_NAME_USE type;
if (!LookupAccountSidW(NULL, pSid, NULL, &cchName, NULL, &cchDomain, &type))
{
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
<bail via exception or return>;
}
cout << cchName << ":" << cchDomain << " -> ";
DWORD cchName1 = (cchName + 1), cchDomain1 = (cchDomain + 1);
LPWSTR pName = ... allocate cchName1 WCHARs ...;
LPWSTR pDomain = ... allocate cchDomain1 WCHARs ...;
if (!LookupAccountSidW(NULL, pSid, pName, &cchName1, pDomain, &cchDomain1, &type))
<bail via exception or return>;
cout << cchName1 << ":" << cchDomain1 << endl;
... deallocate pDomain; // <- here Application Verifier detects corrupted block
... deallocate pName;
请忽略内存泄漏的可能性(代码已简化)。另外,请注意,根据 MSDN,不需要过度分配 1 个符号。但是让我描述一下我在调试器中看到的...
对于遇到的所有(除了一个)SID,它会打印出诸如 16:7 -> 15:6
或 6:7 -> 5:6
之类的内容,并且一切正常。基本上,第一次调用 returns 所需的缓冲区大小(包括终止 NUL
),第二次调用 returns 写入的符号数量(不包括 NUL
)到提供的缓冲区(过度分配) 1,顺便说一句)。
现在,一个特定的 SID 导致 6:3 -> 5:2
输出。但是当我查看 pDomain
缓冲区(长度为 4 个符号)时,我看到截断的域名 ABCD
(实际域名为 6 个符号)后跟 NUL
(这会破坏堆控制结构) .所以 LookupAccountSidW
声称它只写入了 2(+1) 个符号,而实际上是将 4(+1) 个符号写入了 4 个符号长的缓冲区。
从我的角度来看,这是 LookupAccountSidW
中的一个明显错误,但我真的很想弄清楚该 SID 与其他 SID 有何不同。也许它是从另一个(更短的)域迁移过来的?
P.S。它是 Windows 10 (10.0.14393.2969)
P.P.S。 SID 是 S-1-5-21-<3-part domain id>-<user id>
我遇到了这个问题,所以我正在记录我的发现,以供不幸落入此陷阱的人使用。
在某些环境下,LookupAccountSidA 似乎会导致堆溢出。
可以将用户从一个域迁移到另一个域。由于历史原因,SID 可以保留为域的一部分。 LookupAccountSidA 应该支持查找这些 SID,它确实这样做了,除了它会在内部溢出堆,当旧域名的名称比新域名长时。
在幕后,LookupAccountSidA 调用 LsaLookupSids2,后者获取 PLSA_TRANSLATED_NAME(名称数组)和 PLSA_REFERENCED_DOMAIN_LIST。这里的问题是 LookupAccountSidA 使用错误的数组位置来设置域数据所需的大小。如果有记忆,它会使用位置零,而它应该不是那个位置。因此,如果位置为零的域的名称短于与您正在查找的 SID 关联的名称,LookupAccountSidA 将自行覆盖堆。
唯一的解决方法是传递两个 256 个字符的缓冲区,并将它们填充初始化为“\0”。将这些传递给您的名称和域数组,不要相信为域返回的大小。将这些从您返回的缓冲区中复制出来,注意不要在读取时溢出缓冲区。
当然,更好的选择是使用 LsaLookupSids2,并在必要时进行 Unicode 到多字节的转换。
PS:Microsoft 的一位代表告诉我,这是 Windows 7 和 8 之间引入的错误。我不知道这是否会影响 LookupAccountSidW,但它最当然可以。
我花了几天时间追查表现为结构化异常的崩溃 0xC0000374
(堆损坏)...当然,只能在客户环境中重现。
将它缩小到这个(非常简化的)代码:
DWORD cchName = 0, cchDomain = 0;
SID_NAME_USE type;
if (!LookupAccountSidW(NULL, pSid, NULL, &cchName, NULL, &cchDomain, &type))
{
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
<bail via exception or return>;
}
cout << cchName << ":" << cchDomain << " -> ";
DWORD cchName1 = (cchName + 1), cchDomain1 = (cchDomain + 1);
LPWSTR pName = ... allocate cchName1 WCHARs ...;
LPWSTR pDomain = ... allocate cchDomain1 WCHARs ...;
if (!LookupAccountSidW(NULL, pSid, pName, &cchName1, pDomain, &cchDomain1, &type))
<bail via exception or return>;
cout << cchName1 << ":" << cchDomain1 << endl;
... deallocate pDomain; // <- here Application Verifier detects corrupted block
... deallocate pName;
请忽略内存泄漏的可能性(代码已简化)。另外,请注意,根据 MSDN,不需要过度分配 1 个符号。但是让我描述一下我在调试器中看到的...
对于遇到的所有(除了一个)SID,它会打印出诸如 16:7 -> 15:6
或 6:7 -> 5:6
之类的内容,并且一切正常。基本上,第一次调用 returns 所需的缓冲区大小(包括终止 NUL
),第二次调用 returns 写入的符号数量(不包括 NUL
)到提供的缓冲区(过度分配) 1,顺便说一句)。
现在,一个特定的 SID 导致 6:3 -> 5:2
输出。但是当我查看 pDomain
缓冲区(长度为 4 个符号)时,我看到截断的域名 ABCD
(实际域名为 6 个符号)后跟 NUL
(这会破坏堆控制结构) .所以 LookupAccountSidW
声称它只写入了 2(+1) 个符号,而实际上是将 4(+1) 个符号写入了 4 个符号长的缓冲区。
从我的角度来看,这是 LookupAccountSidW
中的一个明显错误,但我真的很想弄清楚该 SID 与其他 SID 有何不同。也许它是从另一个(更短的)域迁移过来的?
P.S。它是 Windows 10 (10.0.14393.2969)
P.P.S。 SID 是 S-1-5-21-<3-part domain id>-<user id>
我遇到了这个问题,所以我正在记录我的发现,以供不幸落入此陷阱的人使用。
在某些环境下,LookupAccountSidA 似乎会导致堆溢出。
可以将用户从一个域迁移到另一个域。由于历史原因,SID 可以保留为域的一部分。 LookupAccountSidA 应该支持查找这些 SID,它确实这样做了,除了它会在内部溢出堆,当旧域名的名称比新域名长时。
在幕后,LookupAccountSidA 调用 LsaLookupSids2,后者获取 PLSA_TRANSLATED_NAME(名称数组)和 PLSA_REFERENCED_DOMAIN_LIST。这里的问题是 LookupAccountSidA 使用错误的数组位置来设置域数据所需的大小。如果有记忆,它会使用位置零,而它应该不是那个位置。因此,如果位置为零的域的名称短于与您正在查找的 SID 关联的名称,LookupAccountSidA 将自行覆盖堆。
唯一的解决方法是传递两个 256 个字符的缓冲区,并将它们填充初始化为“\0”。将这些传递给您的名称和域数组,不要相信为域返回的大小。将这些从您返回的缓冲区中复制出来,注意不要在读取时溢出缓冲区。
当然,更好的选择是使用 LsaLookupSids2,并在必要时进行 Unicode 到多字节的转换。
PS:Microsoft 的一位代表告诉我,这是 Windows 7 和 8 之间引入的错误。我不知道这是否会影响 LookupAccountSidW,但它最当然可以。