我的应用程序需要是Windows PC上所有用户会话的单实例应用程序。到目前为止,我的研究主要集中在使用互斥体实现这一目标,但我遇到了一个问题,我不确定这是一个真正的问题,这确实是我认为的最佳实践问题。
首先是代码:
Private Const AppVer = "Global\UNIQUENAME" ' This is not what i am using but the name is unique
Public Sub Main()
Dim mutexValue As Long
mutexValue = CreateMutex(ByVal 0&, 1, AppVer)
If (Err.LastDllError = ERROR_ALREADY_EXISTS) Then
SaveTitle$ = App.Title
App.Title = "... duplicate instance."
MsgBox "A duplicate instance of this program exists."
CloseHandle mutexValue
Exit Sub
End If
' Else keep on truckin'
现在,基于this文章,我相信我理解通过将NULL指针传递给CreateMutex函数,就像我上面一样,我基本上分配了与当前登录用户相关联的安全描述符。
如果这意味着我的想法(我可能需要更多指导)告诉我其他登录的用户将无法“看到”在原始用户会话下创建的互斥锁,也无法使用创建一个具有相同名称的互斥锁。
现在,经验证据似乎支持了这一点。我用一个消息框弹出我收到的“LastDLLError”,当另一个用户试图启动应用程序时(虽然它已经在另一个用户帐户下运行),我会收到一个ERROR_ACCESS_DENIED代码。我可以对此进行测试以及ERROR_ALREADY_EXISTS代码并且只是退出/或。然而,这感觉有点hackish,我想知道是否有人可以提出替代方案。要做的“正确”事情似乎是将正确的指针传递给CreateMutex函数,这样任何用户都有权查看任何现有的互斥体(mutices?),但我不确定这是否可能没有当前登录用户是管理员(不易察觉)。非常感谢任何协助/指导。提前谢谢!
答案 0 :(得分:4)
您不需要管理员权限即可为自己的互斥锁设置安全性。这是一个简单的演示应用程序,基本上将Everyone / Full控件提供给互斥锁。
Option Explicit
Private Const STANDARD_RIGHTS_REQUIRED As Long = &HF0000
Private Const SYNCHRONIZE As Long = &H100000
Private Const MUTANT_QUERY_STATE As Long = &H1
Private Const MUTANT_ALL_ACCESS As Long = (STANDARD_RIGHTS_REQUIRED Or SYNCHRONIZE Or MUTANT_QUERY_STATE)
Private Const SECURITY_DESCRIPTOR_REVISION As Long = 1
Private Const DACL_SECURITY_INFORMATION As Long = 4
Private Declare Function CreateMutex Lib "kernel32" Alias "CreateMutexA" (lpMutexAttributes As Any, ByVal bInitialOwner As Long, ByVal lpName As String) As Long
Private Declare Function OpenMutex Lib "kernel32" Alias "OpenMutexA" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal lpName As String) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function InitializeSecurityDescriptor Lib "advapi32.dll" (pSecurityDescriptor As Any, ByVal dwRevision As Long) As Long
Private Declare Function SetSecurityDescriptorDacl Lib "advapi32.dll" (pSecurityDescriptor As Any, ByVal bDaclPresent As Long, pDacl As Any, ByVal bDaclDefaulted As Long) As Long
Private Declare Function SetKernelObjectSecurity Lib "advapi32.dll" (ByVal Handle As Long, ByVal SecurityInformation As Long, pSecurityDescriptor As SECURITY_DESCRIPTOR) As Long
Private Type SECURITY_DESCRIPTOR
Revision As Byte
Sbz1 As Byte
Control As Long
Owner As Long
Group As Long
pSacl As Long
pDacl As Long
End Type
Private Const MUTEX_NAME As String = "Global\20b70e57-1c2e-4de9-99e5-20f3961e6812"
Private m_hCurrentMutex As Long
Private Sub Form_Load()
Dim hMutex As Long
Dim uSec As SECURITY_DESCRIPTOR
hMutex = OpenMutex(MUTANT_ALL_ACCESS, 0, MUTEX_NAME)
If hMutex <> 0 Then
Call CloseHandle(hMutex)
MsgBox "Already running", vbExclamation
Unload Me
Exit Sub
End If
m_hCurrentMutex = CreateMutex(ByVal 0&, 1, MUTEX_NAME)
Call InitializeSecurityDescriptor(uSec, SECURITY_DESCRIPTOR_REVISION)
Call SetSecurityDescriptorDacl(uSec, 1, ByVal 0, 0)
Call SetKernelObjectSecurity(m_hCurrentMutex, DACL_SECURITY_INFORMATION, uSec)
End Sub
Private Sub Form_Unload(Cancel As Integer)
If m_hCurrentMutex <> 0 Then
Call CloseHandle(m_hCurrentMutex)
m_hCurrentMutex = 0
End If
End Sub
答案 1 :(得分:1)
我认为你的直觉是完全正确的。我不知道为什么从ERROR_ACCESS_DENIED推断某些其他进程具有互斥锁是不安全的,所以实际上它与ERROR_ALREADY_EXISTS(在此上下文中)相同。但同时,它感觉不到非常正确。
正如您所建议的那样,设置适当的安全描述符确实是正确的方法。 MSDN表示授予MUTEX_ALL_ACCESS权限会增加用户必须成为管理员的风险,我认为你确实需要MUTEX_ALL_ACCESS。但根据我的经验,它适用于非管理员。
你的问题引起了我的兴趣,做了一个快速测试。这意味着我有一些源代码,所以这里是:
int wmain(int argc, wchar_t* argv[])
{
ACL *existing_dacl = NULL;
ACL *new_dacl = NULL;
PSECURITY_DESCRIPTOR security_descriptor = NULL;
bool owner = false;
HANDLE mutex = CreateMutex(NULL,FALSE,L"Global\\blah");
if(mutex == NULL)
wprintf(L"CreateMutex failed: 0x%08x\r\n",GetLastError());
if(GetLastError() == ERROR_ALREADY_EXISTS)
wprintf(L"Got handle to existing mutex\r\n");
else
{
wprintf(L"Created new mutex\r\n");
owner = true;
}
if(owner)
{
// Get the DACL on the mutex
HRESULT hr = GetSecurityInfo(mutex,SE_KERNEL_OBJECT,
DACL_SECURITY_INFORMATION,NULL,NULL,
&existing_dacl,NULL,
&security_descriptor);
if(hr != S_OK)
wprintf(L"GetSecurityInfo failed: 0x%08x\r\n",hr);
// Add an ACE to the ACL
EXPLICIT_ACCESSW ace;
memset(&ace,0,sizeof(ace));
ace.grfAccessPermissions = MUTEX_ALL_ACCESS;
ace.grfAccessMode = GRANT_ACCESS;
ace.grfInheritance = NO_INHERITANCE;
ace.Trustee.pMultipleTrustee = NULL;
ace.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
ace.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
ace.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
ace.Trustee.ptstrName = L"EVERYONE";
hr = SetEntriesInAcl(1,&ace,existing_dacl,&new_dacl);
if(hr != S_OK)
wprintf(L"SetEntriesInAcl failed: 0x%08x\r\n",hr);
// Set the modified DACL on the mutex
hr = SetSecurityInfo(mutex,SE_KERNEL_OBJECT,
DACL_SECURITY_INFORMATION,NULL,NULL,new_dacl,NULL);
if(hr != S_OK)
wprintf(L"SetSecurityInfo failed: 0x%08x\r\n",hr);
else
wprintf(L"Changed ACL\r\n");
LocalFree(existing_dacl);
LocalFree(new_dacl);
LocalFree(security_descriptor);
}
wprintf(L"Press any key...");
_getch();
CloseHandle(mutex);
return 0;
}
答案 2 :(得分:1)
去年年底,我在VB6中寻找类似的解决方案。当时我无法找到任何跨用户边界进行通信的VB6应用程序示例,因此我必须自己编写。
请参阅:Interprocess Communication via Semaphores
您可以使用该类创建和检查全局信号量,该信号量将告诉您您的应用是否已在任何用户下运行。我没有看Mutex API,但它们的用法非常相似。如果您已经编写了一些Mutex代码,那么 GetSecurityDescriptor 函数就是您想要转置的函数。