【问题标题】:64-bit Windows API struct alignment caused Access Denied error on named pipe64 位 Windows API 结构对齐导致命名管道上的访问被拒绝错误
【发布时间】:2015-06-13 14:52:26
【问题描述】:

我花了两天时间,但在尝试将CallNamedPipe 视为结构对齐问题时,我终于缩小了ERROR_ACCESS_DENIED (5) 错误的来源。我们有一个 32 位服务和一个 32 位应用程序,我正在尝试将服务更新为 64 位服务。奇怪的是,在 32 位模式下一切正常,但在 64 位模式下,来自 32 位应用程序的 CallNamedPipe 报告访问被拒绝错误。

服务已经设置了SECURITY_ATTRIBUTES 结构并使用正确初始化的PSECURITY_DESCRIPTOR 填充lpSecurityDescriptor 成员。这在传递给CreateNamedPipe 时没有报告任何错误。我仍然不知道为什么它没有报告错误;也许糟​​糕的安全属性会默默地退回到默认值而不是失败。

通过许多回旋(包括早期更改结构对齐的一些不完整/不正确的尝试 - 调试服务启动代码并不容易),我开始意识到将默认结构对齐设置为 1 字节 (/Zp1) 的项目设置是造成问题。当我最终在所有出现#include <windows.h>#pragma pack(pop) 之前使用#pragma pack(push,8) 之后,事情就开始起作用了。

我现在的问题是为什么这是必要的。我看到 Windows API 中有许多头文件通过包括pshpack1.hpshpack2.hpshpack4.hpshpack8.hpoppack.h 来显式设置结构对齐。我如何知道 Windows API 何时控制自己的打包以及何时设置正确的打包级别成为我的责任?不应该每个关心结构对齐的 Windows API 声明都设置正确的打包,这样我就不必筛选系统中的所有代码,包括 Windows API 头文件?一种替代方法是将项目设置更改为使用默认结构对齐,但我必须假设这样做是因为我们的系统中依赖 1 字节结构对齐的代码比依赖 Windows API 的代码更多。

这是服务器端代码的样子:

BOOL OpenMyPipe()
{
   SECURITY_ATTRIBUTES sa;
   PSECURITY_DESCRIPTOR pSD;

   printf("sizeof(SECURITY_ATTRIBUTES) == %d\n", sizeof(SECURITY_ATTRIBUTES));
   pSD = (PSECURITY_DESCRIPTOR)GlobalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
   if (pSD == NULL)
      return FALSE;

   if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION))
      return FALSE;

   if (!SetSecurityDescriptorDacl(pSD, TRUE, (PACL)NULL, FALSE))
      return FALSE;

   sa.nLength = sizeof(sa);
   sa.lpSecurityDescriptor = pSD;
   sa.bInheritHandle = FALSE;

   char szPipeName[MAX_PATH];
   sprintf(szPipeName, "\\\\.\\pipe\\%s%s", "__SQLTST_",
      "MAINMR");

   hPipe = CreateNamedPipe(szPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
      PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
      1, 0, 0, NMPWAIT_WAIT_FOREVER, &sa);

   if (hPipe == INVALID_HANDLE_VALUE)
      return FALSE;
   return TRUE;
}

为简单起见,我用一个小型 VB.NET 客户端验证了这一点:

Sub Main()
  Dim pipes = System.IO.Directory.GetFiles("\\.\pipe\")

  Using pipe As New System.IO.Pipes.NamedPipeClientStream(".", "__SQLTST_MAINMR")
     Dim message(16) As Byte
     pipe.Connect(3000)
     Array.Copy(BitConverter.GetBytes(Process.GetCurrentProcess().Id), message, 4)
     pipe.Write(message, 0, 16)
  End Using
End Sub

我相信只有在服务器端代码在不同的帐户(如 SYSTEM 帐户)下运行时才会出现该错误。不过,我不知道如何轻松测试它。我所知道的是,当上面的代码与常规应用程序代码在同一帐户下运行时,即使没有设置SECURITY_ATTRIBUTES,上述代码也将不会失败。另外,当然,您必须将服务器代码中的结构对齐设置为 1 字节才能看到错误。

【问题讨论】:

  • 我的部分问题是这 Win API 中的一个错误,还是我错过了一些可能影响整个系统中数百个API 调用的巨大假设。
  • 也许问题不是你想的那样。我确实想知道你为什么要打包结构。为什么要错位数据?
  • 我不知道。我从一个超过 200 万行的系统继承了这段代码。我只能假设这是因为我们在 15 多年前的文件中存储了许多结构,需要按照很久以前的方式进行对齐。如果我知道如何在 SYSTEM 帐户下轻松运行服务器代码,我有理由确定上述内容可能会重现错误。

标签: c++ winapi struct


【解决方案1】:

Windows SDK 期望打包为 8 个字节。来自Using the Windows Headers

项目编译时应使用默认结构打包,目前为 8 字节,因为最大的整数类型为 8 字节。这样做可确保头文件中的所有结构类型都以 Windows API 期望的相同对齐方式编译到应用程序中。它还确保具有 8 字节值的结构正确对齐,并且不会在强制数据对齐的处理器上导致对齐错误。

这是确保数据结构按系统预期对齐所必需的。我怀疑没有明确这样做的原因是他们想要默认值,所以why ask for anything else。更换包装比较少见,应仅限于特定情况。如果微软在每个头文件中添加了#pragma pack(push,8),他们就会隐含地说改变对齐是正常的。

未对齐的结构可以节省空间,但会降低性能,因为在访问未对齐的成员时会生成alignment faults

出于多种原因,Windows SDK 确实会更改结构的对齐方式。一种可能是需要读取 32 位或 64 位数据结构的文件格式。例如,可以使用IMAGE_THUNK_DATA64IMAGE_THUNK_DATA32 读取PE 文件格式。前者需要 8 字节填充,而后者需要 4 字节填充。同样,Wininet.h 将根据是针对 32 位代码还是 64 位代码编译数据结构而对数据结构进行不同的打包。这些是包装方面的合法变化,但有特定的原因。

【讨论】:

  • 这让我想知道为什么在 Windows 8.1 SDK 包含文件目录中包含 pshpack8.h 的次数超过 80 次。
  • 我添加了更多关于为什么在头文件中使用pshpack8.h 的信息。有许多合法用途。 /Zp 标志的存在是为了将包装设置为您想要的任何内容。并非每个程序都必须使用 Windows SDK。
猜你喜欢
  • 1970-01-01
  • 2020-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多