LookupAccountSid() 会损坏内存堆



我花了几天时间追逐表现为结构化异常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:66:7 -> 5:6之类的东西,一切都是花花公子。基本上,第一次调用返回所需的缓冲区大小(包括终止NUL(,第二次调用返回写入(不包括NUL(到提供的缓冲区中的符号数量(过度分配1,顺便说一句(。

现在,一个特定的 SID 会产生6:3 -> 5:2输出。但是当我查看缓冲区(长度为 4 个符号(时pDomain我看到截断的域名ABCD(实际域名是 6 个符号(,后跟NUL(这会损坏堆控制结构(。所以LookupAccountSidW声称它只写入了 2(+1( 个符号,而实际上它已将 4(+1( 个符号写入一个 4 个符号长的缓冲区中。

从我的角度来看,这是一个明显的错误LookupAccountSidW,但我真的很想弄清楚该 SID 与其他 SID 有何不同。也许它是从另一个(较短的(域迁移而来的?

附言它是视窗 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 使用错误的数组位置来设置域数据所需的大小。如果没记错的话,它会在应该不是其他位置的地方使用位置零。因此,如果位置 0 中的域的名称短于与要查找的 SID 关联的名称,则 LookupAccountSidA 将自行覆盖堆。

唯一的解决方法是传递两个 256 个字符的缓冲区,并将它们填充初始化为"\0"。为您的名称和域数组传递这些内容,并且不要信任为域返回的大小。将这些内容从返回的缓冲区中复制出来,并注意不要在读取时溢出缓冲区。

当然,更好的选择是使用 LsaLookupSids2,并在必要时进行 Unicode 到多字节的转换。

PS:Microsoft的一位代表告诉我,这是Windows 7和8之间引入的错误。我不知道这是否会影响LookupAccountSidW,但肯定是可能的。

最新更新