2
votes

My app has 2 processes, one requires elevation, another doesn’t, but otherwise they’re running under the same user account on the same desktop.

I need to create a file (not on disk, other type of file) in elevated process which reads from the file, but have my non-elevated process write access to the file.

With nullptr SECURITY_ATTRIBUTES, non-elevated process can’t open the file, CreateFile fails with access denied code. That’s expected and the SetSecurityDescriptorDacl workaround similar to this answer works OK.

However, I don’t like that workaround. I don’t want to give write access to that file to Everyone. I only want to give access to the current user. This is kinda sensitive, the elevated reader process will run for hours and I don’t want Everyone to be able to write to that file.

How do I get/construct SECURITY_ATTRIBUTES that would be the default security for non-elevated process running on the same desktop?

1
The c++ tag is reserved to ask about problems specifically with c++ code. Your question doesn't contain any code example, hence I removed it (again).πάντα ῥεῖ
You are referring to CreateFile, but want a file, that's not backed by storage on the disk. It's not really clear, what you are asking for. Can you clarify? @πάν: That's not, what the c++ tag is for. From the info: "Use this tag for questions about code (to be) compiled with a C++ compiler." That does not imply, that the tag should be used for C++ specific problems only (although that would probably make for better tag searchability).IInspectable
@IInspectable CreateFile can open many other types of named Win32 objects. In my app the file’s a mailslot, but that’s not too important, it would be same for named pipes of some other Win32 things.Soonts
CreateFile creates or opens only a File object. NT has 3 types of create calls for the kernel function IoCreateFile: CreateFileTypeNone (i.e. NtCreateFile system call for a regular file or device), CreateFileTypeNamedPipe (i.e. NtCreateNamedPipeFile), and CreateFileTypeMailslot (i.e. NtCreateMailslotFile). The latter two use the reserved InternalParameters to create a server-side named pipe or mailslot.Eryk Sun
I was addressing the claim that "CreateFile can open many other types of named Win32 objects". This call is implemented as NtCreateFile => IoCreateFile : CreateFileTypeNone, which creates a File object that references either a device directly or a file on a device (e.g. a file in a file system or in a device namespace). Named pipes and mailslots are created in rudimentary file systems (e.g. "\Device\NamedPipe\" is listable via NtQueryDirectoryFile), in which a file is created by the first server-side File object reference.Eryk Sun

1 Answers

4
votes

if we run elevated process (by admin user) - it have not elevated linked session. (and non elevated process have elevated linked session) you need:

  1. open process token
  2. query linked session token for this token via TokenLinkedToken
  3. query default dacl for this linked token via TokenDefaultDacl
  4. initialize security descriptor with this DACL

code for get default dacl for not elevated session:

ULONG BOOL_TO_ERROR(BOOL f)
{
    return f ? 0 : GetLastError();
}

ULONG GetNotElevatedDefaultDacl(PTOKEN_DEFAULT_DACL* DefaultDacl)
{
    HANDLE hToken;
    ULONG err = BOOL_TO_ERROR(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken));

    if (!err)
    {
    ULONG cb;
        union {
            TOKEN_LINKED_TOKEN tlt;
            TOKEN_ELEVATION_TYPE tet;
        };

        err = BOOL_TO_ERROR(GetTokenInformation(hToken, TokenElevationType, &tet, sizeof(tet), &cb));

        if (!err)
        {
            if (tet == TokenElevationTypeFull)
            {
                err = BOOL_TO_ERROR(GetTokenInformation(hToken, TokenLinkedToken, &tlt, sizeof(tlt), &cb));
            }
            else
            {
                err = ERROR_ELEVATION_REQUIRED;
            }
        }
        CloseHandle(hToken);

        if (!err)
        {
            union {
                PTOKEN_DEFAULT_DACL p;
                PVOID buf;
            };

            cb = 0x100;

            do 
            {
                if (buf = LocalAlloc(0, cb))
                {
                    if (err = BOOL_TO_ERROR(GetTokenInformation(
                        tlt.LinkedToken, TokenDefaultDacl, buf, cb, &cb)))
                    {
                        LocalFree(buf);
                    }
                    else
                    {
                        *DefaultDacl = p;
                    }
                }
                else
                {
                    err = GetLastError();
                    break;
                }

            } while (err == ERROR_INSUFFICIENT_BUFFER);

            CloseHandle(tlt.LinkedToken);
        }
    }

    return err;
}

and using it (this for any object which take SECURITY_ATTRIBUTES on create)

PTOKEN_DEFAULT_DACL DefaultDacl;
ULONG err = GetNotElevatedDefaultDacl(&DefaultDacl);

SECURITY_DESCRIPTOR sd;
SECURITY_ATTRIBUTES sa = { sizeof(sa), &sd, FALSE };

InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);

if (!err)
{
    SetSecurityDescriptorDacl(&sd, TRUE, DefaultDacl->DefaultDacl, FALSE);
}

HANDLE hObject = CreateMailslot(
        L"\\\\?\\Global\\MailSlot\\12345678", 0, MAILSLOT_WAIT_FOREVER, &sa);

if (!err)
{
    LocalFree(DefaultDacl);
}

if (hObject)
{
    // CheckObjectSD(hObject);
    CloseHandle(hObject);
}

if create object(mailslot in this case) from elevated process with default dacl - security DACL will be look like:

T FL AcessMsK Sid
A 00 001F01FF S-1-5-32-544 'Administrators'
A 00 001F01FF S-1-5-18 'SYSTEM'
A 00 001200A9 S-1-5-5-0-x 'LogonSessionId_0_x'

so all access for SYSTEM and Administrators and read+execute access for current Logon Session. as result not elevated process from same logon session have only read access.

if use explicit DACL from not elevated session - result:

T FL AcessMsK Sid
A 00 001F01FF S-1-5-21-a-b-c-d 'SomeUser'
A 00 001F01FF S-1-5-18 'SYSTEM'
A 00 001200A9 S-1-5-5-0-x 'LogonSessionId_0_x'

so all access for SYSTEM and SomeUser and read+execute access for current Logon Session.

note because elevated process have SomeUser as TokenUser he have all access for this object

for check object security descriptor we can use for example next code:

void CheckObjectSD(HANDLE hObject)
{
    union {
        PSECURITY_DESCRIPTOR psd;
        PVOID buf;
    };

    ULONG cb = 0, rcb = 0x30;
    volatile static UCHAR guz;
    buf = alloca(guz);
    PVOID stack = alloca(guz);

    ULONG err;
    do 
    {
        if (cb < rcb)
        {
            cb = (ULONG)((ULONG_PTR)stack - (ULONG_PTR)(buf = alloca(rcb - cb)));
        }

        if (!(err = BOOL_TO_ERROR(GetKernelObjectSecurity(hObject, 
            DACL_SECURITY_INFORMATION|LABEL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION, psd, cb, &rcb))))
        {
            PWSTR psz;
            if (ConvertSecurityDescriptorToStringSecurityDescriptorW(psd, SDDL_REVISION, 
                DACL_SECURITY_INFORMATION|LABEL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION, &psz, 0))
            {
                DbgPrint("%S\n", psz);
                LocalFree(psz);
            }
        }

    } while (err == ERROR_INSUFFICIENT_BUFFER);
}

if we have not admin user account, elevation was via another user account. elevated process in this case have no more linked session (if we try query linked token we got error - A specified logon session does not exist. It may already have been terminated. ). possible solution here(and in generic case too) in next:

for users process default DACL usually grant GENERIC_ALL for System and UserSid and GENERIC_READ | GENERIC_EXECUTE for logon session SID. we can query process token, get it default DACL, found LogonSession SID in DACL and change it access mask to GENERIC_ALL. this can be done by next code:

ULONG GetDaclForLogonSession(HANDLE hToken, PTOKEN_DEFAULT_DACL* DefaultDacl)
{
    ULONG err;

    ULONG cb = 0x100;

    union {
        PTOKEN_DEFAULT_DACL p;
        PVOID buf;
    };

    do 
    {
        if (buf = LocalAlloc(0, cb))
        {
            if (!(err = BOOL_TO_ERROR(GetTokenInformation(hToken, TokenDefaultDacl, buf, cb, &cb))))
            {
                err = ERROR_NOT_FOUND;

                if (PACL Dacl = p->DefaultDacl)
                {
                    if (USHORT AceCount = Dacl->AceCount)
                    {
                        union {
                            PVOID pv;
                            PBYTE pb;
                            PACE_HEADER pah;
                            PACCESS_ALLOWED_ACE paaa;
                        };

                        pv = Dacl + 1;

                        static const SID_IDENTIFIER_AUTHORITY NtAuth = SECURITY_NT_AUTHORITY;
                        do 
                        {
                            switch (pah->AceType)
                            {
                            case ACCESS_ALLOWED_ACE_TYPE:
                                PSID Sid = &paaa->SidStart;
                                if (*GetSidSubAuthorityCount(Sid) == SECURITY_LOGON_IDS_RID_COUNT &&
                                    *GetSidSubAuthority(Sid, 0) == SECURITY_LOGON_IDS_RID &&
                                    !memcmp(GetSidIdentifierAuthority(Sid), &NtAuth, sizeof(NtAuth)))
                                {
                                    paaa->Mask = GENERIC_ALL;
                                    *DefaultDacl = p;
                                    return 0;
                                }

                                break;
                            }
                            pb += pah->AceSize;

                        } while (--AceCount);
                    }
                }
            }

            LocalFree(buf);
        }
        else
        {
            return GetLastError();
        }

    } while (err == ERROR_INSUFFICIENT_BUFFER);

    return err;
}

ULONG GetDaclForLogonSession(PTOKEN_DEFAULT_DACL* DefaultDacl)
{
    HANDLE hToken;
    ULONG err = BOOL_TO_ERROR(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken));

    if (!err)
    {
        err = GetDaclForLogonSession(hToken, DefaultDacl);

        CloseHandle(hToken);
    }

    return err;
}

as result we got DACL we grant all access to current logon session. usage the same - simply replace call from GetNotElevatedDefaultDacl to GetDaclForLogonSession