VB6: приложение одного экземпляра во всех сеансах пользователя - PullRequest
5 голосов
/ 26 января 2011

У меня есть приложение, которое должно быть приложением для одного экземпляра во всех сеансах пользователя на ПК с Windows. Мои исследования до сих пор были сосредоточены вокруг использования мьютекса для достижения этой цели, но у меня возникла проблема, которая, я не уверен, действительно является проблемой, я считаю, что это действительно вопрос наилучшей практики.

Вот код в первую очередь:

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'

Теперь, основываясь на этой статье, я полагаю, что понимаю, что, передав NULL-указатель функции CreateMutex, как я выше, я в основном назначаю любой дескриптор безопасности, связанный с текущим вошедшим в систему пользователем.

Если это означает, что, как мне кажется, это происходит (мне, возможно, потребуется больше указаний здесь), которое говорит мне, что другие пользователи, которые войдут в систему, не смогут "видеть" мьютекс, созданный в ходе сеанса исходного пользователя, и не смогут создать мьютекс с тем же именем.

Теперь, эмпирические доказательства, кажется, подтверждают это. Я использовал окно сообщения для всплытия «LastDLLError», которое я получал, и когда другой пользователь пытался запустить приложение (пока оно уже работало под другой учетной записью пользователя), я получал код ERROR_ACCESS_DENIED. Я в порядке с тестированием против этого вместе с кодом ERROR_ALREADY_EXISTS и просто выход из или / или. Тем не менее, это выглядит как хакерский, и мне интересно, если кто-то может предложить альтернативу. Похоже, «правильная» вещь состоит в том, чтобы передать правильный указатель на функцию CreateMutex так, чтобы у любого пользователя были соответствующие разрешения для просмотра любых существующих мьютексов (мьюкиз?), Но я не уверен, что это возможно без текущего пользователь вошел в систему как администратор (что недопустимо). Любая помощь / руководство с благодарностью. Заранее спасибо!

Ответы [ 3 ]

4 голосов
/ 26 января 2011

Вам не нужны привилегии администратора для установки защиты ваших собственных мьютексов.Вот простое демо-приложение, которое дает мьютексу все / полный контроль.

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 голос
/ 26 января 2011

Я искал подобное решение в VB6 в конце прошлого года. В то время я не мог найти никаких примеров приложений VB6, взаимодействующих через границу пользователя, поэтому мне пришлось написать свое собственное.

См .: Межпроцессное взаимодействие через семафоры

Вы можете использовать класс для создания и проверки глобального семафора, который сообщит вам, работает ли ваше приложение под каким-либо пользователем. Я не смотрел на API Mutex, но их использование очень похоже. Функция GetSecurityDescriptor - это то, что вы захотите транспонировать, если у вас уже написан код Mutex.

1 голос
/ 26 января 2011

Я думаю, что ваши инстинкты совершенно верны. Я не знаю ни одной причины, по которой из 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...