[Win32] minimum permissions needed for CreateProcess

Started by
14 comments, last by the_edd 15 years, 3 months ago
CreateProcess() fails. GetLastError() returns ERROR_ACCESS_DENIED. I'd like to find out a little bit more. I aim to gather enough information to throw an instance of the following exception:

class insufficient_permissions : public process_launch_failure
{
    public:
        enum entity { file, directory, other };
        enum { read = 1, write = 2, execute = 4 };

        insufficient_permissions(entity e, unsigned requires, const std::string &path, const std::string &action);

        ~insufficient_permissions() throw();

        entity subject() const;

        unsigned requires() const; // bitfield of read|write|execute

        const char *action() const; // the action for which the permissions were insufficient

        // ...
};
I'm guessing that the error could mean that the user doesn't have appropriate permissions for one or more of the following: 1. The executable file itself 2. The directory that contains the executable 3. An ancestor of the directory that contains the executable 4. The working directory (passed to CreateProcess) 5. An ancestor of the working directory Does anyone know the absolute minimum (NTFS) permissions required of these things for CreateProcess to succeed? Otherwise I'm going to have to cook up a program that creates files and directories with all possible permissions combinations and tries them. I realize that NTFS permissions are more complicated than read/write/execute, but I'll use a 'sensible' mapping to decide which of the three each NTFS permission is closest to.
Advertisement
Could be LogonUser() (you might need to use: LOGON32_LOGON_NEW_CREDENTIALS) that you need? Or this VB6 sample?
Quote:Original post by gan
Could be LogonUser() (you might need to use: LOGON32_LOGON_NEW_CREDENTIALS) that you need? Or this VB6 sample?


I don't want to create the process under another user. I just want to know the minimum permissions needed of the listed entities for user X if user X wants to launch a process.

In other words, which of these boxes needs to be ticked for the application, it's containing folder, any parent folders of the containing folder, the working directory and any parent folders of the working directory such that the given user can run the application?
Quote:Original post by the_edd


In other words, which of these boxes needs to be ticked for the application, it's containing folder, any parent folders of the containing folder, the working directory and any parent folders of the working directory such that the given user can run the application?


http://msdn.microsoft.com/en-us/library/ms684880.aspx
Quote:The handle returned by the CreateProcess function has PROCESS_ALL_ACCESS access to the process object.
So I would assume you would need all access permissions to create?

Quote:Original post by dmail
Quote:Original post by the_edd


In other words, which of these boxes needs to be ticked for the application, it's containing folder, any parent folders of the containing folder, the working directory and any parent folders of the working directory such that the given user can run the application?


http://msdn.microsoft.com/en-us/library/ms684880.aspx


Thanks. But unless I'm mistaken, that page details information about processes (i.e. instances of running applications), rather than executable files on disk.

Quote:
Quote:The handle returned by the CreateProcess function has PROCESS_ALL_ACCESS access to the process object.
So I would assume you would need all access permissions to create?


I think that means that the parent process has all access rights to that child process once it has been created through CreateProcess. If my understanding is correct, then this is different from the permissions needed of the user for the executable file on disk.
Quote:Original post by the_edd
1. The executable file itself


Just FILE_EXECUTE. It might be worth noting that GENERIC_EXECUTE doesn't include this permission, that is, unless the doc page has an oversight.

Quote:
2. The directory that contains the executable
3. An ancestor of the directory that contains the executable
4. The working directory (passed to CreateProcess)
5. An ancestor of the working directory


You don't need anything special here. Technically you'd need FILE_TRAVERSE access to each directory, but the default security policy disables enforcement of this so it's generally a non-issue.
Quote:Original post by adeyblue
Quote:Original post by the_edd
1. The executable file itself


Just FILE_EXECUTE. It might be worth noting that GENERIC_EXECUTE doesn't include this permission, that is, unless the doc page has an oversight.


Perfect, thanks. I can confirm that an executable with only FILE_EXECUTE can indeed be launched by the corresponding user. That'll save me *a lot* of time.

Quote:
Quote:
2. The directory that contains the executable
3. An ancestor of the directory that contains the executable
4. The working directory (passed to CreateProcess)
5. An ancestor of the working directory


You don't need anything special here. Technically you'd need FILE_TRAVERSE access to each directory, but the default security policy disables enforcement of this so it's generally a non-issue.


What do you mean by anything special? I have a case that doesn't work:

I've put hello.exe in C:\Documents and Settings\Admin\My Documents
I've given the user EDD\Dev FILE_EXECUTE permission and nothing else.
Now I log in as EDD\Dev and run the following code from the command line:

// runhello.cpp#include <iostream>#include <cstring>#include <windows.h>int main(){    STARTUPINFO si;    std::memset(&si, 0, sizeof si);    si.cb = sizeof si;    PROCESS_INFORMATION pi;    std::memset(π, 0, sizeof pi);    char cmdline[] = "\"C:\\Documents and Settings\\Admin\\My Documents\\hello.exe\"";    const char *cwd = "C:\\Documents and Settings\\Admin\\My Documents";    if (CreateProcessA(0, cmdline, 0, 0, false, 0, 0, cwd, &si, π) == 0)        std::cout << "error: " << GetLastError() << '\n';    else    {        WaitForSingleObject(pi.hProcess, INFINITE);        CloseHandle(pi.hThread);        CloseHandle(pi.hProcess);    }    return 0;}


The output is "error: 267". 267 is ERROR_DIRECTORY, described on MSDN as "The directory name is invalid.".

The directory certainly exists. I also tried setting cwd to "C:\\Documents and Settings\\Admin\\abc" (which also exists). This doesn't work either.

However, it works if I change cwd to "C:\\Documents and Settings\\Admin".

Is there something wrong with my code, or should I investigate the permissions of these directories further? (I would have done so already if it wasn't such a PITA to log in as an administrator on XP home).
I think I've got the requirements now, this picture shows what permissions are requested on XP. On all the parent directories back to the root, the FS requests permission 0x100001 (SYNCHRONIZE | FILE_READ_DATA) but on the working directory itself 0x100020 is required (SYNCHRONIZE | FILE_TRAVERSE). No doubt if the user or the one of the users' groups isn't allowed to bypass traversal checking, the parent directories will be queried for 0x100021. Don't worry about SYNCHRONIZE, its added automatically.

Quote:
(I would have done so already if it wasn't such a PITA to log in as an administrator on XP home).

Fast User Switching can help with this, you log on each account once and switch between them via the log off button. It's on the Control Panel->User Accounts->Change the way users log on or off link. Also, there is a command line viewer for file permissions (cacls.exe in system32) but it doesn't show the specific permissions, so I wrote one that does. It covers all other securable objects as well so it might be useful if you're going to be doing a lot of this sort of thing.
Quote:Original post by adeyblue
I think I've got the requirements now, this picture shows what permissions are requested on XP. On all the parent directories back to the root, the FS requests permission 0x100001 (SYNCHRONIZE | FILE_READ_DATA) but on the working directory itself 0x100020 is required (SYNCHRONIZE | FILE_TRAVERSE). No doubt if the user or the one of the users' groups isn't allowed to bypass traversal checking, the parent directories will be queried for 0x100021. Don't worry about SYNCHRONIZE, its added automatically.


That's absolutely fantastic! Thank you very much indeed.

I looked and found that both C:\Documents and Settings\Admin and C:\Documents and Settings\Admin\My Documents have exactly the same permissions. The only difference is that the permissions for the latter are inherited from the former. Do you know what might be happening here? Perhaps SYNCHRONIZE isn't added automatically for directories whose permissions are inherited (this is a complete guess!!)?

Quote:
Quote:
(I would have done so already if it wasn't such a PITA to log in as an administrator on XP home).

Fast User Switching can help with this, you log on each account once and switch between them via the log off button. It's on the Control Panel->User Accounts->Change the way users log on or off link.


Ah, but the thing is on XP Home, you can't get the Administrator to show up on the log in screen unless you're in safe mode and it seems that only the Administrator can see the security tab in the dialog shown when you right-click-a-file->properties.

Quote:
Also, there is a command line viewer for file permissions (cacls.exe in system32) but it doesn't show the specific permissions, so I wrote one that does. It covers all other securable objects as well so it might be useful if you're going to be doing a lot of this sort of thing.


Great, thanks! I'll check that out. I also found this along my travels. So I can now in fact see the security tab when logged in on any account, so I don't have to switch users to edit permissions. I had to use the 2nd link to get to a download.

Thanks again, you've been a huge help.
Quote:Original post by adeyblue
I think I've got the requirements now, this picture shows what permissions are requested on XP. On all the parent directories back to the root, the FS requests permission 0x100001 (SYNCHRONIZE | FILE_READ_DATA) but on the working directory itself 0x100020 is required (SYNCHRONIZE | FILE_TRAVERSE). No doubt if the user or the one of the users' groups isn't allowed to bypass traversal checking, the parent directories will be queried for 0x100021. Don't worry about SYNCHRONIZE, its added automatically.


After further investigation I've narrowed this down a little more.

CWD: nothing (though I once could have sworn that Traverse Folder/Execute File | Synchronize was needed, as you suggested).
CWD's parent: List Folder/Read Data
CWD's (great) grand parents: nothing

This explains why, or is at least consistent with my observation that a working directory of "Docs and Settings\Admin" works fine but a working directory of "Docs and Settings\Admin\My Docs" fails; "Docs and Settings" has plenty of nice read permissions for Everyone but "Docs and Settings\Admin" doesn't have the List Folder/Read Data permission required when "Docs and Settings\Admin\My Docs" is specified as a working directory.

Does this make sense?

If so, here's my plan to predict whether CreateProcess will fail due to a permissions problem:

// Pseudo codeif (user has SeChangeNotifyPrivilege) // this is bypass traversal checking, I think?{    check_perms(FILE_EXECUTE | SYNCHRONIZE, executable);    check_perms(FILE_READ_DATA, parent_of(cwd));}else{    d = directory_containing(executable);    check_perms(FILE_READ_DATA | FILE_TRAVERSE | SYNCHRONIZE, d and parents_of(d));    check_perms(FILE_READ_DATA | FILE_TRAVERSE | SYNCHRONIZE, cwd and parents_of(cwd));    check_perms(FILE_EXECUTE | SYNCHRONIZE, executable);    check_perms(FILE_READ_DATA, parent_of(cwd));}


Of course, I might not have the permissions to check these permissions(!), in which case I'll blindly try the CreateProcess call anyway, and try to decipher any error as best I can.

This topic is closed to new replies.

Advertisement