Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++: Let user process write to LOCAL_SYSTEM named pipe - Custom Security Descriptor

I have a service running as LocalSystem which creates a Processes in the logged on users' session. Then the service creates a named pipe to which the client connects to read and write. According to https://msdn.microsoft.com/en-us/library/aa365600%28v=vs.85%29.aspx the client can only read from the pipe (It's No Admin, not the Creator, neither LocalSystem ).

I created a security descriptor to grant the user read & write access. But this didn't work. So i tried giving read & write-access to the Everyone-Group. But this also does not work. The error code my client returns is always ACCESS_DENIED (5).

I would be glad to know what i am doing wrong.

EDIT: if i don't create a custom security descriptor and just open the pipe with GENERIC_READ it works (but only reading).

EDIT2 I want to learn how to do this right. And i still want to be only the logged on user able to write. Not everyone (this was just for testing).

Service-Code (commented-out my code for getting the user sid):

PSID EveryoneSID                      = nullptr;
        SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
        if(!AllocateAndInitializeSid(&SIDAuthWorld, 1,
                                 SECURITY_WORLD_RID,
                                 0, 0, 0, 0, 0, 0, 0,
                                 &EveryoneSID))
        {
            throw(std::runtime_error("Failed to initialize group sid: " + std::to_string(GetLastError())));
        }

        //TOKEN_USER* tokeninfo     = nullptr;
        //DWORD tokeninfolen        = 0;
        //DWORD outlen              = 0;
        //// Query token info size
        //if(!GetTokenInformation(UserToken,
        //                  TokenUser,
        //                  tokeninfo,
        //                  tokeninfolen,
        //                  &outlen))
        //{
        //  throw(std::runtime_error("Failed to obtain user token size: " + std::to_string(GetLastError())));
        //}
        //// Allocate enough space to hold token user information
        //tokeninfo    = (TOKEN_USER*) LocalAlloc(LPTR, outlen);
        //tokeninfolen = outlen;

        //// Get SID from user token
        //if(!GetTokenInformation(UserToken,
        //                  TokenUser,
        //                  tokeninfo,
        //                  tokeninfolen,
        //                  &outlen))
        //{
        //  throw(std::runtime_error("Failed to obtain user token info: " + std::to_string(GetLastError())));
        //}

        //auto UserSID = tokeninfo->User.Sid;
        //LocalFree(tokeninfo);

        SECURITY_ATTRIBUTES sa = {0};
        ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
        sa.nLength = sizeof(SECURITY_ATTRIBUTES);
        sa.bInheritHandle = FALSE;

        // Set up ACE
        EXPLICIT_ACCESS ace      = {0};
        ace.grfAccessMode        = SET_ACCESS;
        ace.grfAccessPermissions = PIPE_ACCESS_DUPLEX;  // GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE
        ace.grfInheritance       = NO_INHERITANCE;
        ace.Trustee.TrusteeForm  = TRUSTEE_IS_SID;
        ace.Trustee.TrusteeType  = TRUSTEE_IS_WELL_KNOWN_GROUP;
        ace.Trustee.ptstrName    = (LPTSTR) EveryoneSID;

        PACL acl = nullptr;
        if(ERROR_SUCCESS != SetEntriesInAcl(1, &ace, nullptr, &acl))
            throw(std::runtime_error("Failed to set acl entries: " + std::to_string(GetLastError())));

        // Create security descriptor.
        auto sd = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
        if(!InitializeSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION))
            throw(std::runtime_error("Failed to initialize security descriptor: " + std::to_string(GetLastError())));
        if(!SetSecurityDescriptorDacl(sd, TRUE, acl, FALSE))
            throw(std::runtime_error("Failed to set DACL: " + std::to_string(GetLastError())));

        // Set security descriptor in security attributes
        sa.lpSecurityDescriptor = sd;

        // Create a named pipe to which the user-session application
        // connects.
        auto pipe = CreateNamedPipe(LOCAL_PIPE_NAME,
                         PIPE_ACCESS_DUPLEX,
                         PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS | PIPE_WAIT,
                         1,
                         256,
                         256,
                         NULL,
                         &sa);
        if(pipe == INVALID_HANDLE_VALUE)
            throw std::runtime_error("Failed to create named pipe");

        LocalFree(acl);
        LocalFree(sd);

User-Process-Code:

auto pipe = CreateFile(LOCAL_PIPE_NAME,
                           GENERIC_READ | GENERIC_WRITE,    // read access 
                            0,              // no sharing 
                            NULL,           // default security attributes
                            OPEN_EXISTING,  // opens existing pipe 
                            0,              // default attributes 
                            NULL);          // no template file 
    if (pipe == INVALID_HANDLE_VALUE)
        throw(std::runtime_error("Failed to open local pipe: " + std::to_string(GetLastError())));
like image 703
Juarrow Avatar asked Sep 28 '22 02:09

Juarrow


1 Answers

This is the first problem:

    ace.grfAccessPermissions = PIPE_ACCESS_DUPLEX;

The PIPE_ACCESS_DUPLEX constant is only meant to be used as an argument to CreateNamedPipe(), it is not a valid access permission. (By coincidence, it is equal to FILE_READ_DATA|FILE_WRITE_DATA but those access rights do not by themselves allow you to connect to a pipe.)

According to Named Pipe Security and Access Rights, the following access rights are assigned to the server end of a duplex pipe, implying that they are also sufficient to open the client end:

    ace.grfAccessPermissions = FILE_GENERIC_READ | FILE_GENERIC_WRITE | SYNCHRONIZE;

However, FILE_GENERIC_WRITE is too broad to grant to the client; in particular, it allows the client to create a new instance of the server end of the pipe. This is unlikely to be desirable. Instead, for a duplex pipe, you should use

    ace.grfAccessPermissions = FILE_GENERIC_READ | FILE_WRITE_DATA;

Of course, the access you request when opening the client end must be consistent:

auto pipe = CreateFile(LOCAL_PIPE_NAME,
                       GENERIC_READ | FILE_WRITE_DATA,    // read-write access 
                       0,              // no sharing 
                       NULL,           // default security attributes
                       OPEN_EXISTING,  // opens existing pipe 
                       0,              // default attributes 
                       NULL);          // no template file 

Details

Experimentally, on Windows 7 SP1 x64, in order to connect to a pipe (even if you request no access in the call to CreateFile) you must have the READ_ATTRIBUTES and the SYNCHRONIZE rights. Note that the FILE_GENERIC_READ constant incorporates both of these.

To read data from the pipe you must have (and request) FILE_READ_DATA. (This is incorporated in FILE_GENERIC_READ.)

To write data to the pipe you must have (and request) FILE_WRITE_DATA.

like image 88
Harry Johnston Avatar answered Oct 12 '22 01:10

Harry Johnston