Bug 708778 - Updater service used on Windows should drop as many permissions as possible. r=rstrong.
authorBrian R. Bondy <netzen@gmail.com>
Wed, 04 Jan 2012 23:19:16 -0500
changeset 85000 c20c8ef8f0f4d989105dbd10984090ffea14dcee
parent 84999 df4ba8bfc9caf08eb89c4dcd09561e394a8d2cfe
child 85001 bd49fc438b1bb13cc007dd282f5a0061b0c22a0a
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrstrong
bugs708778
milestone12.0a1
Bug 708778 - Updater service used on Windows should drop as many permissions as possible. r=rstrong.
toolkit/components/maintenanceservice/maintenanceservice.cpp
toolkit/mozapps/update/common/uachelper.cpp
toolkit/mozapps/update/common/uachelper.h
toolkit/mozapps/update/updater/updater.cpp
--- a/toolkit/components/maintenanceservice/maintenanceservice.cpp
+++ b/toolkit/components/maintenanceservice/maintenanceservice.cpp
@@ -40,16 +40,17 @@
 #include <stdio.h>
 #include <wchar.h>
 #include <shlobj.h>
 
 #include "serviceinstall.h"
 #include "maintenanceservice.h"
 #include "servicebase.h"
 #include "workmonitor.h"
+#include "uachelper.h"
 
 SERVICE_STATUS gSvcStatus = { 0 }; 
 SERVICE_STATUS_HANDLE gSvcStatusHandle = NULL; 
 HANDLE ghSvcStopEvent = NULL;
 BOOL gServiceStopping = FALSE;
 
 // logs are pretty small ~10 lines, so 5 seems reasonable.
 #define LOGS_TO_KEEP 5
@@ -241,16 +242,20 @@ SvcMain(DWORD dwArgc, LPWSTR *lpszArgv)
 {
   // Setup logging, and backup the old logs
   WCHAR updatePath[MAX_PATH + 1];
   if (GetLogDirectoryPath(updatePath)) {
     BackupOldLogs(updatePath, LOGS_TO_KEEP);
     LogInit(updatePath, L"maintenanceservice.log");
   }
 
+  // Disable every privilege we don't need. Processes started using
+  // CreateProcess will use the same token as this process.
+  UACHelper::DisablePrivileges(NULL);
+
   // Register the handler function for the service
   gSvcStatusHandle = RegisterServiceCtrlHandlerW(SVC_NAME, SvcCtrlHandler);
   if (!gSvcStatusHandle) {
     LOG(("RegisterServiceCtrlHandler failed (%d)\n", GetLastError()));
     return; 
   } 
 
   // These SERVICE_STATUS members remain as set here
--- a/toolkit/mozapps/update/common/uachelper.cpp
+++ b/toolkit/mozapps/update/common/uachelper.cpp
@@ -32,19 +32,63 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include <windows.h>
 #include "uachelper.h"
+#include "updatelogging.h"
 
 typedef BOOL (WINAPI *LPWTSQueryUserToken)(ULONG, PHANDLE);
 
+// See the MSDN documentation with title: Privilege Constants
+// At the time of this writing, this documentation is located at: 
+// http://msdn.microsoft.com/en-us/library/windows/desktop/bb530716%28v=vs.85%29.aspx
+LPCTSTR UACHelper::PrivsToDisable[] = { 
+  SE_ASSIGNPRIMARYTOKEN_NAME,
+  SE_AUDIT_NAME,
+  SE_BACKUP_NAME,
+  // From testing ReadDirectoryChanges still succeeds even with a low
+  // integrity process with the following privilege disabled.
+  SE_CHANGE_NOTIFY_NAME,
+  SE_CREATE_GLOBAL_NAME,
+  SE_CREATE_PAGEFILE_NAME,
+  SE_CREATE_PERMANENT_NAME,
+  SE_CREATE_SYMBOLIC_LINK_NAME,
+  SE_CREATE_TOKEN_NAME,
+  SE_DEBUG_NAME,
+  SE_ENABLE_DELEGATION_NAME,
+  SE_IMPERSONATE_NAME,
+  SE_INC_BASE_PRIORITY_NAME,
+  SE_INCREASE_QUOTA_NAME,
+  SE_INC_WORKING_SET_NAME,
+  SE_LOAD_DRIVER_NAME,
+  SE_LOCK_MEMORY_NAME,
+  SE_MACHINE_ACCOUNT_NAME,
+  SE_MANAGE_VOLUME_NAME,
+  SE_PROF_SINGLE_PROCESS_NAME,
+  SE_RELABEL_NAME,
+  SE_REMOTE_SHUTDOWN_NAME,
+  SE_RESTORE_NAME,
+  SE_SECURITY_NAME,
+  SE_SHUTDOWN_NAME,
+  SE_SYNC_AGENT_NAME,
+  SE_SYSTEM_ENVIRONMENT_NAME,
+  SE_SYSTEM_PROFILE_NAME,
+  SE_SYSTEMTIME_NAME,
+  SE_TAKE_OWNERSHIP_NAME,
+  SE_TCB_NAME,
+  SE_TIME_ZONE_NAME,
+  SE_TRUSTED_CREDMAN_ACCESS_NAME,
+  SE_UNDOCK_NAME,
+  SE_UNSOLICITED_INPUT_NAME
+};
+
 /**
  * Determines if the OS is vista or later
  *
  * @return TRUE if the OS is vista or later.
  */
 BOOL
 UACHelper::IsVistaOrLater() 
 {
@@ -94,8 +138,104 @@ UACHelper::OpenLinkedToken(HANDLE token)
   DWORD len;
   if (GetTokenInformation(token, (TOKEN_INFORMATION_CLASS)TokenLinkedToken, 
                           &tlt, sizeof(TOKEN_LINKED_TOKEN), &len)) {
     token = tlt.LinkedToken;
     hNewLinkedToken = token;
   }
   return hNewLinkedToken;
 }
+
+
+/**
+ * Enables or disables a privilege for the specified token.
+ *
+ * @param  token  The token to adjust the privilege on.
+ * @param  priv   The privilege to adjust.
+ * @param  enable Whether to enable or disable it
+ * @return TRUE if the token was adjusted to the specified value.
+ */
+BOOL 
+UACHelper::SetPrivilege(HANDLE token, LPCTSTR priv, BOOL enable)
+{
+  LUID luidOfPriv;
+  if (!LookupPrivilegeValue(NULL, priv, &luidOfPriv)) {
+    return FALSE; 
+  }
+
+  TOKEN_PRIVILEGES tokenPriv;
+  tokenPriv.PrivilegeCount = 1;
+  tokenPriv.Privileges[0].Luid = luidOfPriv;
+  tokenPriv.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0;
+
+  SetLastError(ERROR_SUCCESS);
+  if (!AdjustTokenPrivileges(token, false, &tokenPriv,
+                             sizeof(tokenPriv), NULL, NULL)) {
+    return FALSE; 
+  } 
+
+  return GetLastError() == ERROR_SUCCESS;
+}
+
+/**
+ * For each privilege that is specified, an attempt will be made to 
+ * drop the privilege. 
+ * 
+ * @param  token         The token to adjust the privilege on. 
+ *         Pass NULL for current token.
+ * @param  unneededPrivs An array of unneeded privileges.
+ * @param  count         The size of the array
+ * @return TRUE if there were no errors
+ */
+BOOL
+UACHelper::DisableUnneededPrivileges(HANDLE token, 
+                                     LPCTSTR *unneededPrivs, 
+                                     size_t count)
+{
+  HANDLE obtainedToken = NULL;
+  if (!token) {
+    // Note: This handle is a pseudo-handle and need not be closed
+    HANDLE process = GetCurrentProcess();
+    if (!OpenProcessToken(process, TOKEN_ALL_ACCESS_P, &obtainedToken)) {
+      LOG(("Could not obtain token for current process, no "
+           "privileges changed. (%d)\n", GetLastError()));
+      return FALSE;
+    }
+    token = obtainedToken;
+  }
+
+  BOOL result = TRUE;
+  for (size_t i = 0; i < count; i++) {
+    if (SetPrivilege(token, unneededPrivs[i], FALSE)) {
+      LOG(("Disabled unneeded token privilege: %s.\n", 
+           unneededPrivs[i]));
+    } else {
+      LOG(("Could not disable token privilege value: %s. (%d)\n", 
+           unneededPrivs[i], GetLastError()));
+      result = FALSE;
+    }
+  }
+
+  if (obtainedToken) {
+    CloseHandle(obtainedToken);
+  }
+  return result;
+}
+
+/**
+ * Disables privileges for the specified token.
+ * The privileges to disable are in PrivsToDisable.
+ * In the future there could be new privs and we are not sure if we should
+ * explicitly disable these or not. 
+ * 
+ * @param  token The token to drop the privilege on.
+ *         Pass NULL for current token.
+ * @return TRUE if there were no errors
+ */
+BOOL
+UACHelper::DisablePrivileges(HANDLE token)
+{
+  static const size_t PrivsToDisableSize = 
+    sizeof(UACHelper::PrivsToDisable) / sizeof(UACHelper::PrivsToDisable[0]);
+
+  return DisableUnneededPrivileges(token, UACHelper::PrivsToDisable, 
+                                   PrivsToDisableSize);
+}
--- a/toolkit/mozapps/update/common/uachelper.h
+++ b/toolkit/mozapps/update/common/uachelper.h
@@ -39,11 +39,18 @@
 #define _UACHELPER_H_
 
 class UACHelper
 {
 public:
   static BOOL IsVistaOrLater();
   static HANDLE OpenUserToken(DWORD sessionID);
   static HANDLE OpenLinkedToken(HANDLE token);
+  static BOOL DisablePrivileges(HANDLE token);
+
+private:
+  static BOOL SetPrivilege(HANDLE token, LPCTSTR privs, BOOL enable);
+  static BOOL DisableUnneededPrivileges(HANDLE token, 
+                                        LPCTSTR *unneededPrivs, size_t count);
+  static LPCTSTR PrivsToDisable[];  
 };
 
 #endif
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -1586,16 +1586,20 @@ int NS_main(int argc, NS_tchar **argv)
   if (NS_tchdir(argv[2]) != 0) {
     return 1;
   }
 
   // The directory containing the update information.
   gSourcePath = argv[1];
 
 #ifdef XP_WIN
+  // Disable every privilege we don't need. Processes started using
+  // CreateProcess will use the same token as this process.
+  UACHelper::DisablePrivileges(NULL);
+
   bool useService = false;
   // We never want the service to be used unless we build with
   // the maintenance service.
 #ifdef MOZ_MAINTENANCE_SERVICE
   IsUpdateStatusPending(useService);
 #endif
 #endif