# HG changeset patch # User Andre Heinecke # Date 1397154002 -7200 # Node ID c0eac5c8c24545f510125afa26b8881123345dc9 # Parent f6ce186cebc26922595d02844dea3ee1de1d3256 Keep working on nssstore_win and clarify its specification diff -r f6ce186cebc2 -r c0eac5c8c245 cinst/nssstore_win.c --- a/cinst/nssstore_win.c Thu Apr 10 17:50:44 2014 +0200 +++ b/cinst/nssstore_win.c Thu Apr 10 20:20:02 2014 +0200 @@ -2,21 +2,57 @@ /* @file @brief Windows implementation of nssstore process control. + + The windows process will write an instructions file for + the mozilla process into the current users temp directory + (%APPDATA%/Local/Temp/) and start the NSS installation process to + exectute those instructions. If the current process is elevated + the NSS process is run with a restricted token. + The execution of the mozilla process is not monitored. + You have to refer to the system log to check which certificates were + installed / removed by it. + + If the installation process is running elevated it + will create the file in the ProgramData directory in + a subdirectory with the defined application name. + %PROGRAMDATA%/$APPLICATION_NAME + with the file name: + current_selection.txt + The folder will have restricted permissions so + that only Administrators are allowed to access it. + + Additionally if this process is Elevated it also starts the + NSS installation process in default profile mode once to change + the default NSS certificate databases for new profiles. + + The process then adds a new RunOnce registry key + for each user on the system that executes the NSS installation + process on login to make sure it is launched once in the + security context of that user. */ #include #include -#include #include #include #include +#include +#include +#include #include "logging.h" #include "util.h" +#include "strhelp.h" -#define PROCESS_TIMEOUT 30000 /* In milliseconds */ +#ifndef APPNAME +#define APPNAME L"cinst" +#endif -#define BUFSIZE 4096 /* used for reading childs stdout */ +#ifndef SELECTION_FILE_NAME +#define SELECTION_FILE_NAME L"currently_selected.txt" +#endif + +#define PROCESS_TIMEOUT 30000 #define PRINTLASTERROR(msg) \ char *my_error = getLastErrorMsg(); \ @@ -81,178 +117,391 @@ /**@brief Start the process to install / remove * -* Creates a child process with the Security handle specified in hToken -* sends the instructions and then waits for the process to finish. +* Starts the NSS installation process for the current user * -* If the process is not done in PROCESS_TIMEOUT seconds this assumes an -* unknown error happened. -* -* @param [in] to_install strv of DER encoded certificates to be added. -* @param [in] to_remove strv of DER encoded certificates to be remvoed. -* @param [in] hToken handle to the primary token that is used to install -* certificates. +* @param [in] selection_file filename of the file containing +* the users installall / remove selection. * * @returns true on success, false on error. */ static bool -start_procces_for_user (char **to_install, char **to_remove, - HANDLE hToken) +start_procces_for_user (wchar_t *selection_file) { - HANDLE h_stdin_child_r = NULL, - h_stdin_child_w = NULL, - h_stdout_child_r = NULL, - h_stdout_child_w = NULL; - SECURITY_ATTRIBUTES saAttr = {0}; + HANDLE hToken = NULL;/*, + hChildToken = NULL;*/ /* TODO get this as absolute path based on current module location */ - LPWSTR lpApplicationName = L"mozilla.exe"; + LPWSTR lpApplicationName = L"mozilla.exe", + lpCommandLine; PROCESS_INFORMATION piProcInfo = {0}; STARTUPINFOW siStartInfo = {0}; - LPVOID lpEnvironment = NULL; BOOL success = FALSE; - int retval = -1; - - saAttr.nLength = sizeof (SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - saAttr.lpSecurityDescriptor = NULL; /* Use default */ - - /* Create a pipe for the child process's STDIN. */ - if (!CreatePipe (&h_stdin_child_r, &h_stdin_child_w, &saAttr, 0)) - { - PRINTLASTERROR ("Create pipe failed.\n"); - return -1; - } + size_t cmd_line_len = 0; - /* Create a pipe for the child process's STDOUT. */ - if (!CreatePipe (&h_stdout_child_r, &h_stdout_child_w, &saAttr, 0)) + if (!selection_file) { - PRINTLASTERROR ("Create pipe failed.\n"); - return -1; - } - - /* Ensure that read/write is properly inherited */ - if (!SetHandleInformation (h_stdin_child_w, HANDLE_FLAG_INHERIT, 0) || - !SetHandleInformation (h_stdout_child_r, HANDLE_FLAG_INHERIT, 0)) - { - PRINTLASTERROR ("SetHandleInformation failed.\n"); - goto closepipes; - } - - /* Create the environment for the user */ - if (!CreateEnvironmentBlock (&lpEnvironment, hToken, FALSE)) - { - PRINTLASTERROR ("Failed to create the environment.\n"); - goto closepipes; + ERRORPRINTF ("Invalid call\n"); + return false; } /* set up handles. stdin and stdout go to the same stdout*/ siStartInfo.cb = sizeof (STARTUPINFO); - siStartInfo.hStdError = h_stdout_child_w; - siStartInfo.hStdOutput = h_stdout_child_w; - siStartInfo.hStdInput = h_stdin_child_r; - siStartInfo.dwFlags = STARTF_USESTDHANDLES; + + if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken)) + { + PRINTLASTERROR("Failed to get current handle."); + return false; + } + /* TODO! if (is_elevated()) + restrict token -> hChildToken + */ + + cmd_line_len = wcslen (lpApplicationName) + wcslen(selection_file) + 1; + lpCommandLine = xmalloc (cmd_line_len * sizeof(wchar_t)); + + wcscpy (lpCommandLine, lpApplicationName); + wcscat (lpCommandLine, selection_file); success = CreateProcessAsUserW (hToken, lpApplicationName, - NULL, /* Commandline */ + lpCommandLine, /* Commandline */ NULL, /* Process attributes. Take hToken */ NULL, /* Thread attribues. Take hToken */ - TRUE, /* Inherit Handles */ - CREATE_UNICODE_ENVIRONMENT, /* Creation flags. */ - lpEnvironment, + FALSE, /* Inherit Handles */ + 0, /* Creation flags. */ + NULL, /* Inherit environment */ NULL, /* Current working directory */ &siStartInfo, &piProcInfo); + xfree (lpCommandLine); if (!success) { PRINTLASTERROR ("Failed to create process.\n"); - goto closepipes; - } - - if (!write_instructions (to_install, h_stdin_child_w, false)) - { - ERRORPRINTF ("Failed to write install instructions.\n"); - goto closepipes; - } - if (!write_instructions (to_remove, h_stdin_child_w, true)) - { - ERRORPRINTF ("Failed to write remove instructions.\n"); - goto closepipes; + return false; } - /* Close the Handle so that the child knows we are finished - telling it what to do */ - CloseHandle (h_stdin_child_w); - h_stdin_child_w = NULL; - -#ifndef RELEASE_BUILD - /* print childs stdout / stderr to parents stdout */ - { - HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); - DWORD read_bytes = 0; - DWORD written = 0; - bool success; - char buf[BUFSIZE]; - - if (!stdout_handle || stdout_handle == INVALID_HANDLE_VALUE) - { - /* Should not happen */ - ERRORPRINTF("Failed to get stdout handle.\n"); - goto closeprocess; - } - - for (;;) - { - success = ReadFile (h_stdout_child_r, buf, BUFSIZE, &read_bytes, NULL); - if(!success || read_bytes == 0) - break; - - success = WriteFile (stdout_handle, buf, - read_bytes, &written, NULL); - if (!success || written != read_bytes) - break; - } - } -#endif - if (WaitForSingleObject (piProcInfo.hProcess, PROCESS_TIMEOUT) != WAIT_OBJECT_0) { /* Should not happen... */ ERRORPRINTF ("Failed to wait for process.\n"); + if (piProcInfo.hProcess) + CloseHandle (piProcInfo.hProcess); + if (piProcInfo.hThread) + CloseHandle (piProcInfo.hThread); + return false; } - -closeprocess: if (piProcInfo.hProcess) CloseHandle (piProcInfo.hProcess); if (piProcInfo.hThread) CloseHandle (piProcInfo.hThread); + return true; +} -closepipes: - if (lpEnvironment) - DestroyEnvironmentBlock (lpEnvironment); - if (h_stdin_child_w) - CloseHandle (h_stdin_child_w); - if (h_stdin_child_r) - CloseHandle (h_stdin_child_r); - if (h_stdout_child_w) - CloseHandle (h_stdout_child_w); - if (h_stdout_child_r) - CloseHandle (h_stdout_child_r); +/**@brief Create a directory with restricted access rights + * + * This creates a security attributes structure that restricts + * write access to the Administrators group but allows everyone to read files + * in that directory. + * Basically a very complicated vetrsion of mkdir path -m 604 + * + * If the directory exists the permissions of that directory are checked if + * they are acceptable and true or false is returned accordingly. + * + * Code based on msdn example: + * http://msdn.microsoft.com/en-us/library/windows/desktop/aa446595%28v=vs.85%29.aspx + * + * @param[in] path Path of the directory to create + * + * @returns true on success of if the directory exists, false on error + */ +bool +create_restricted_directory (LPWSTR path) +{ + bool retval = false; + PSID everyone_SID = NULL, + admin_SID = NULL; + PACL access_control_list = NULL; + PSECURITY_DESCRIPTOR descriptor = NULL; + EXPLICIT_ACCESS explicit_access[2]; + SID_IDENTIFIER_AUTHORITY world_identifier = {SECURITY_WORLD_SID_AUTHORITY}, + admin_identifier = {SECURITY_NT_AUTHORITY}; + SECURITY_ATTRIBUTES security_attributes; + + ZeroMemory(&security_attributes, sizeof(security_attributes)); + ZeroMemory(&explicit_access, 2 * sizeof(EXPLICIT_ACCESS)); + + /* Create a well-known SID for the Everyone group. */ + if(!AllocateAndInitializeSid(&world_identifier, /* top-level identifier */ + 1, /* subauthorties count */ + SECURITY_WORLD_RID, /* Only one authority */ + 0, 0, 0, 0, 0, 0, 0, /* No other authorities*/ + &everyone_SID)) + { + PRINTLASTERROR ("Failed to allocate world sid.\n"); + return false; + } + + /* Initialize the first EXPLICIT_ACCESS structure for an ACE. + to allow everyone read access */ + explicit_access[0].grfAccessPermissions = GENERIC_READ; /* Give read access */ + explicit_access[0].grfAccessMode = SET_ACCESS; /* Overwrite other access for all users */ + explicit_access[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; /* make it stick */ + explicit_access[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + explicit_access[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; + explicit_access[0].Trustee.ptstrName = (LPTSTR) everyone_SID; + + /* Create the SID for the BUILTIN\Administrators group. */ + if(!AllocateAndInitializeSid(&admin_identifier, + 2, + SECURITY_BUILTIN_DOMAIN_RID, /*BUILTIN\ */ + DOMAIN_ALIAS_RID_ADMINS, /*\Administrators */ + 0, 0, 0, 0, 0, 0, /* No other */ + &admin_SID)) + { + PRINTLASTERROR ("Failed to allocate admin sid."); + goto done; + } + + /* explicit_access[1] grants admins full rights for this object and inherits + it to the children */ + explicit_access[1].grfAccessPermissions = GENERIC_ALL; + explicit_access[1].grfAccessMode = SET_ACCESS; + explicit_access[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; + explicit_access[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; + explicit_access[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP; + explicit_access[1].Trustee.ptstrName = (LPTSTR) admin_SID; + + /* Set up the ACL structure. */ + if (ERROR_SUCCESS != SetEntriesInAcl(2, explicit_access, NULL, &access_control_list)) + { + PRINTLASTERROR ("Failed to set up Acl."); + goto done; + } + + /* Initialize a security descriptor */ + descriptor = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, + SECURITY_DESCRIPTOR_MIN_LENGTH); + if (descriptor == NULL) + { + PRINTLASTERROR("Failed to allocate descriptor."); + goto done; + } + + if (!InitializeSecurityDescriptor(descriptor, + SECURITY_DESCRIPTOR_REVISION)) + { + PRINTLASTERROR("Failed to initialize descriptor."); + goto done; + } + + /* Now we add the ACL to the the descriptor */ + if (!SetSecurityDescriptorDacl(descriptor, + TRUE, /* bDaclPresent flag */ + access_control_list, + FALSE)) /* not a default DACL */ + { + PRINTLASTERROR("Failed to set security descriptor."); + goto done; + } + + /* Finally set up the security attributes structure */ + security_attributes.nLength = sizeof (SECURITY_ATTRIBUTES); + security_attributes.lpSecurityDescriptor = descriptor; + security_attributes.bInheritHandle = FALSE; + + /* Use the security attributes to create the directory */ + if (!CreateDirectoryW(path, &security_attributes)) + { + DWORD err = GetLastError(); + if (err == ERROR_ALREADY_EXISTS) + { + /* Verify that the directory has the correct rights */ + // TODO + retval = true; + goto done; + } + ERRORPRINTF ("Failed to create directory. Err: %lu", err); + } + retval = true; + +done: + + if (everyone_SID) + FreeSid(everyone_SID); + if (admin_SID) + FreeSid(admin_SID); + if (access_control_list) + LocalFree(access_control_list); + if (descriptor) + LocalFree(descriptor); return retval; } +/**@brief Writes the selection file containing the instructions + * + * If the process is running elevated the instructions are + * written to the global ProgramData directory otherwise + * they are written in the temporary directory of the current user. + * + * If the return value is not NULL it needs to be freed by the caller. + * The returned path will contain backslashes as directory seperators. + * + * @param[in] to_install Certificates that should be installed + * @param[in] to_remove Certificates that should be removed + * @returns pointer to the absolute filename of the selection file or NULL + */ +wchar_t * +write_selection_file (char **to_install, char **to_remove) +{ + wchar_t *folder_name = NULL, + *path = NULL; + bool elevated = is_elevated(); + HRESULT result = E_FAIL; + HANDLE hFile = NULL; + size_t path_len; + + if (!elevated) + { + /* TODO */ + } + +#if 0 + result = SHGetKnownFolderPath (FOLDERID_ProgramData, /* Get program data dir */ + KF_FLAG_CREATE | /* Create if it does not exist */ + KF_FLAG_INIT, /* Initialize it if created */ + -1, /* Get it for the default user */ + &folder_name); +#endif + /* TODO figure out how to do this with mingw */ + result = S_OK; + folder_name = L"C:\\ProgramData"; + + if (result != S_OK) + { + PRINTLASTERROR ("Failed to get folder path"); + return NULL; + } + + path_len = wcslen (folder_name) + wcslen (APPNAME) + 2; /* path + dirsep + \0 */ + path_len += wcslen (SELECTION_FILE_NAME) + 1; /* filename + dirsep */ + + if (path_len >= MAX_PATH) + { + /* We could go and use the full 32,767 characters but this + should be a very weird setup if this is neccessary. */ + ERRORPRINTF ("Path too long.\n"); + return NULL; + } + + path = xmalloc (path_len * sizeof (wchar_t)); + if (wcscpy (path, folder_name) != 0) + { + ERRORPRINTF ("Failed to copy folder name.\n"); +#if 0 + CoTaskMemFree (folder_name); +#endif + return NULL; + } + +#if 0 + CoTaskMemFree (folder_name); +#endif + + if (wcscat (path, L"\\") != 0) + { + ERRORPRINTF ("Failed to cat dirsep.\n"); + xfree(path); + return NULL; + } + + if (wcscat (path, APPNAME) != 0) + { + ERRORPRINTF ("Failed to cat appname.\n"); + xfree(path); + return NULL; + } + + /* Security: if someone has created this directory before + it might be a symlink to another place that a users + wants us to grant read access to or makes us overwrite + something */ + if(!create_restricted_directory (path)) + { + ERRORPRINTF ("Failed to create directory\n"); + xfree(path); + return NULL; + } + + if (wcscat (path, L"\\") != 0) + { + ERRORPRINTF ("Failed to cat dirsep.\n"); + xfree(path); + return NULL; + } + + if (wcscat (path, SELECTION_FILE_NAME) != 0) + { + ERRORPRINTF ("Failed to cat filename.\n"); + xfree(path); + return NULL; + } + + hFile = CreateFileW(path, + GENERIC_WRITE, + 0, /* don't share */ + NULL, /* use the security attributes from the folder */ + OPEN_ALWAYS, + 0, + NULL); + + if (hFile == INVALID_HANDLE_VALUE) + { + ERRORPRINTF ("Failed to create file\n"); + xfree(path); + return NULL; + } + if (!write_instructions (to_install, hFile, false)) + { + ERRORPRINTF ("Failed to write install instructions.\n"); + CloseHandle(hFile); + xfree(path); + return NULL; + } + if (!write_instructions (to_remove, hFile, true)) + { + ERRORPRINTF ("Failed to write remove instructions.\n"); + CloseHandle(hFile); + xfree(path); + return NULL; + } + CloseHandle(hFile); + + return path; +} + int write_stores_nss (char **to_install, char **to_remove) { - HANDLE hToken = NULL; - OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken); + wchar_t *selection_file_name = NULL; - /* TODO loop over all users */ - if (!start_procces_for_user (to_install, to_remove, hToken)) + selection_file_name = write_selection_file (to_install, to_remove); + if (!selection_file_name) + { + ERRORPRINTF ("Failed to write instructions.\n"); + return -1; + } + + DEBUGPRINTF ("Wrote selection file. Loc: %S\n", selection_file_name); + + /* TODO loop over all users create startup entries for them*/ + + if (!start_procces_for_user (selection_file_name)) { ERRORPRINTF ("Failed to run NSS installation process.\n"); + xfree(selection_file_name); return -1; } + xfree(selection_file_name); return 0; }