7

My app needs to write to a file in \ProgramData that could be protected. This only happens once after installation.

Is there an API function that would take ACL info and prompt the user to authorize the app to access the file? In other words the app would ask Windows to prompt the user for confirmation and authorization. This should happen interactively, and allow the app to resume execution after access to the file has been authorized. The app runs as Standard User, does not require Admin privilege.

The file gets opened with CreateFile().

Edit: There is a subtle difference between my query and the others said to be duplicates. I am asking for permission to access one specific object, a file. The others are asking to elevate the privileges of the whole process. Anyway, I am grateful for all responses which include a solution.

Pierre
  • 4,114
  • 2
  • 34
  • 39
  • But... isn't there some combination of GetNamedSecurityInfo, AllocateAndInitializeSid, SetEntriesInAcl, SetNamedSecurityInfo, etc that would allow me access to just that one file? I don't want the whole process to run as Admin. CreateFile() references a SECURITY_ATTRIBUTES thing. It seems that I could obtain a SECURITY_ATTRIBUTES object, pass it to CreateFile, and presto! – Pierre Aug 06 '15 at 00:10
  • Per the [`CreateFile()` documentation](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858.aspx): "*A pointer to a SECURITY_ATTRIBUTES structure that contains two separate but related data members: an optional security descriptor, and a Boolean value that determines whether the returned handle can be inherited by child processes. ... **CreateFile ignores the lpSecurityDescriptor member when opening an existing file** or device, but continues to use the bInheritHandle member.*" – Remy Lebeau Aug 06 '15 at 00:42
  • There's no difference at all. This is a dupe many times over. If you disagree then you still don't understand UAC. The reopen voters clearly don't understand this either. – David Heffernan Aug 08 '15 at 06:34
  • I simply reopened it so I could post my solution, I promptly closed it again. Please show me another Stackoverflow post with the same solution, including ShellExecuteEx, lpFile = "cmd" and the double command. – Pierre Aug 09 '15 at 13:33
  • 20020450 doesn't count, it doesn't mention UAC, and you can only find it after you've already found the solution. – Pierre Aug 09 '15 at 13:39

3 Answers3

20

If you don't want to elevate your entire app, you have a few options:

  1. spawn a separate elevated process just to access the file. Use ShellExecute/Ex() with the runas verb, or CreateProcessElevated(), to run a second copy of your app, or another helper app, with command-line parameters to tell it what to do. The main process can wait for the second process to exit, if needed.

  2. create a COM object to access the file, and then use the COM Elevation Moniker to run the COM object in an elevated state.

  3. prompt the user for credentials using CredUIPromptForCredentials() or CredUIPromptForWindowsCredentials() (see Asking the User for Credentials for more details), then logon to the specified account using LogonUser() to get a token, impersonate that token using ImpersonateLoggedOnUser(), access the file as needed, and then stop impersonating using RevertToSelf() and close the token with CloseHandle().

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • #3 is very ingenious, but if I understand correctly it requires the app to be aware of the Admin password? I'm afraid that most users would not agree to this. I will try #2. (My COM is rudimentary, it's going to take a while). Thank you. – Pierre Aug 06 '15 at 00:29
  • #1 is easier to implement, as everything can remain in a single exe. For #3, the user would need to know the admin password, as the dialog would be asking for both username AND password, which your app would then use to login to that account. Not much different than UAC elevation prompting a standard user for admin credentials. If an admin is running your app, they probably already have access to the file. So, you should try to access the file once, and then ask for credentials and try again only if you get an error code like `ERROR_ACCESS_DENIED` or `ERROR_ELEVATION_REQUIRED`. – Remy Lebeau Aug 06 '15 at 00:39
  • I think I'll try #1 because it is the simplest. Thanks very much. – Pierre Aug 06 '15 at 12:37
  • I don't think #3 will work, because you won't have impersonation privileges. (Also LogonUser gives you a restricted token by default, but you can correct that by using the "logon as batch" option.) – Harry Johnston Oct 13 '17 at 22:56
7

Thanks to @Remy for the ShellExecuteEx suggestion, here are the sordid details. Note the use of 'cmd' and the double-command, so the user only has to reply once. Also, [1] must wait for process completion otherwise you could find yourself creating the file before it was deleted, and [2] don't wait for the process if it failed.

// delete file with Admin privilege
// 'file_name' is path of file to be deleted
SHELLEXECUTEINFO shex;
char param[512];
char *cmd = "/C \"attrib -H \"%s\" && del /F /Q \"%s\"\""; // double command

_snprintf(param, sizeof(param), cmd, file_name, file_name);
ZeroMemory(&shex, sizeof(shex));
shex.cbSize = sizeof(shex);
shex.lpVerb = "runas";  // runas, open
shex.lpFile = "cmd";    // not 'del'
shex.lpParameters = param;
shex.nShow = SW_HIDE;
shex.fMask = SEE_MASK_NOCLOSEPROCESS;
BOOL retshx = ShellExecuteEx(&shex);
// wait otherwise could return before completed
if(retshx)
{   time_t st = clock();
    DWORD exitCode;
    do
    {   if(!GetExitCodeProcess(shex.hProcess, &exitCode))
            break;
        if(clock() - st > CLOCKS_PER_SEC * 5)       // max 5 seconds
            break;
    } while(exitCode != STATUS_WAIT_0); // STILL_ACTIVE
    CloseHandle(shex.hProcess);
}
Pierre
  • 4,114
  • 2
  • 34
  • 39
  • 2
    Why the "-1"? This code took several hours to get right. I wish someone had posted something like this when I was searching for an answer. I hope it will help whoever has the same problem. How is "-1" helping the community? – Pierre Aug 09 '15 at 13:27
5

Processes can only be launched with an elevated token, they can't gain it after the fact. So you can either re-launch your app elevated with a command line argument telling it what to do (simple solution), or implement an out-of-proc COM server that you can create elevated and pass instructions to it (harder).

A third solution is to leverage the built-in UAC support of the IFileOperation interface, but this doesn't let you read/write, only copy. So you could make a copy of the file you need to modify, modify the copy and then use IFileOperation to copy the temporary over the original.

Jonathan Potter
  • 36,172
  • 4
  • 64
  • 79