64位Windows API结构对齐导致命名管道上的Access Denied错误

时间:2015-04-08 14:54:45

标签: c++ winapi struct

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

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

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

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

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

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个字节以查看错误。

1 个答案:

答案 0 :(得分:2)

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

  

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

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

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

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