From ff30aec759bdc4de78912d91f650ec8fd95ff6bc Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Fri, 17 Mar 2017 11:14:01 +0200
Subject: [PATCH] Fix and simplify check for whether we're running as Windows
 service.

If the process token contains SECURITY_SERVICE_RID, but it has been
disabled by the SE_GROUP_USE_FOR_DENY_ONLY attribute, win32_is_service()
would incorrectly report that we're running as a service. That situation
arises, e.g. if postmaster is launched with a restricted security token,
with the "Log in as Service" privilege explicitly removed.

Replace the broken code with CheckProcessTokenMembership(), which does
this correctly. Also replace similar code in win32_is_admin(), even
though it got this right, for simplicity and consistency.

Per bug #13755, reported by Breen Hagan. Back-patch to all supported
versions. Patch by Takayuki Tsunakawa, reviewed by Michael Paquier.

Discussion: https://www.postgresql.org/message-id/20151104062315.2745.67143%40wrigleys.postgresql.org
---
 src/port/win32security.c | 180 +++++++++------------------------------
 1 file changed, 38 insertions(+), 142 deletions(-)

diff --git a/src/port/win32security.c b/src/port/win32security.c
index 777a2e5b992..93739e80b99 100644
--- a/src/port/win32security.c
+++ b/src/port/win32security.c
@@ -18,11 +18,6 @@
 #endif
 
 
-static BOOL pgwin32_get_dynamic_tokeninfo(HANDLE token,
-							  TOKEN_INFORMATION_CLASS class,
-							  char **InfoBuffer, char *errbuf, int errsize);
-
-
 /*
  * Utility wrapper for frontend and backend when reporting an error
  * message.
@@ -53,33 +48,11 @@ log_error(const char *fmt,...)
 int
 pgwin32_is_admin(void)
 {
-	HANDLE		AccessToken;
-	char	   *InfoBuffer = NULL;
-	char		errbuf[256];
-	PTOKEN_GROUPS Groups;
 	PSID		AdministratorsSid;
 	PSID		PowerUsersSid;
 	SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
-	UINT		x;
-	BOOL		success;
-
-	if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &AccessToken))
-	{
-		log_error(_("could not open process token: error code %lu\n"),
-				  GetLastError());
-		exit(1);
-	}
-
-	if (!pgwin32_get_dynamic_tokeninfo(AccessToken, TokenGroups,
-									   &InfoBuffer, errbuf, sizeof(errbuf)))
-	{
-		log_error("%s", errbuf);
-		exit(1);
-	}
-
-	Groups = (PTOKEN_GROUPS) InfoBuffer;
-
-	CloseHandle(AccessToken);
+	BOOL		IsAdministrators;
+	BOOL		IsPowerUsers;
 
 	if (!AllocateAndInitializeSid(&NtAuthority, 2,
 								  SECURITY_BUILTIN_DOMAIN_RID,
@@ -101,34 +74,35 @@ pgwin32_is_admin(void)
 		exit(1);
 	}
 
-	success = FALSE;
-
-	for (x = 0; x < Groups->GroupCount; x++)
+	if (!CheckTokenMembership(NULL, AdministratorsSid, &IsAdministrators) ||
+		!CheckTokenMembership(NULL, PowerUsersSid, &IsPowerUsers))
 	{
-		if ((EqualSid(AdministratorsSid, Groups->Groups[x].Sid) &&
-			 (Groups->Groups[x].Attributes & SE_GROUP_ENABLED)) ||
-			(EqualSid(PowerUsersSid, Groups->Groups[x].Sid) &&
-			 (Groups->Groups[x].Attributes & SE_GROUP_ENABLED)))
-		{
-			success = TRUE;
-			break;
-		}
+		log_error(_("could not check access token membership: error code %lu\n"),
+				  GetLastError());
+		exit(1);
 	}
 
-	free(InfoBuffer);
 	FreeSid(AdministratorsSid);
 	FreeSid(PowerUsersSid);
-	return success;
+
+	if (IsAdministrators || IsPowerUsers)
+		return 1;
+	else
+		return 0;
 }
 
 /*
  * We consider ourselves running as a service if one of the following is
  * true:
  *
- * 1) We are running as Local System (only used by services)
+ * 1) We are running as LocalSystem (only used by services)
  * 2) Our token contains SECURITY_SERVICE_RID (automatically added to the
  *	  process token by the SCM when starting a service)
  *
+ * The check for LocalSystem is needed, because surprisingly, if a service
+ * is running as LocalSystem, it does not have SECURITY_SERVICE_RID in its
+ * process token.
+ *
  * Return values:
  *	 0 = Not service
  *	 1 = Service
@@ -143,140 +117,62 @@ int
 pgwin32_is_service(void)
 {
 	static int	_is_service = -1;
-	HANDLE		AccessToken;
-	char	   *InfoBuffer = NULL;
-	char		errbuf[256];
-	PTOKEN_GROUPS Groups;
-	PTOKEN_USER User;
+	BOOL		IsMember;
 	PSID		ServiceSid;
 	PSID		LocalSystemSid;
 	SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
-	UINT		x;
 
 	/* Only check the first time */
 	if (_is_service != -1)
 		return _is_service;
 
-	if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &AccessToken))
-	{
-		fprintf(stderr, "could not open process token: error code %lu\n",
-				GetLastError());
-		return -1;
-	}
-
-	/* First check for local system */
-	if (!pgwin32_get_dynamic_tokeninfo(AccessToken, TokenUser, &InfoBuffer,
-									   errbuf, sizeof(errbuf)))
-	{
-		fprintf(stderr, "%s", errbuf);
-		return -1;
-	}
-
-	User = (PTOKEN_USER) InfoBuffer;
-
+	/* First check for LocalSystem */
 	if (!AllocateAndInitializeSid(&NtAuthority, 1,
 							  SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0,
 								  &LocalSystemSid))
 	{
 		fprintf(stderr, "could not get SID for local system account\n");
-		CloseHandle(AccessToken);
 		return -1;
 	}
 
-	if (EqualSid(LocalSystemSid, User->User.Sid))
+	if (!CheckTokenMembership(NULL, LocalSystemSid, &IsMember))
 	{
+		fprintf(stderr, "could not check access token membership: error code %lu\n",
+				GetLastError());
 		FreeSid(LocalSystemSid);
-		free(InfoBuffer);
-		CloseHandle(AccessToken);
-		_is_service = 1;
-		return _is_service;
+		return -1;
 	}
-
 	FreeSid(LocalSystemSid);
-	free(InfoBuffer);
 
-	/* Now check for group SID */
-	if (!pgwin32_get_dynamic_tokeninfo(AccessToken, TokenGroups, &InfoBuffer,
-									   errbuf, sizeof(errbuf)))
+	if (IsMember)
 	{
-		fprintf(stderr, "%s", errbuf);
-		return -1;
+		_is_service = 1;
+		return _is_service;
 	}
 
-	Groups = (PTOKEN_GROUPS) InfoBuffer;
-
+	/* Check for service group membership */
 	if (!AllocateAndInitializeSid(&NtAuthority, 1,
 								  SECURITY_SERVICE_RID, 0, 0, 0, 0, 0, 0, 0,
 								  &ServiceSid))
 	{
-		fprintf(stderr, "could not get SID for service group\n");
-		free(InfoBuffer);
-		CloseHandle(AccessToken);
+		fprintf(stderr, "could not get SID for service group: error code %lu\n",
+				GetLastError());
 		return -1;
 	}
 
-	_is_service = 0;
-	for (x = 0; x < Groups->GroupCount; x++)
+	if (!CheckTokenMembership(NULL, ServiceSid, &IsMember))
 	{
-		if (EqualSid(ServiceSid, Groups->Groups[x].Sid))
-		{
-			_is_service = 1;
-			break;
-		}
+		fprintf(stderr, "could not check access token membership: error code %lu\n",
+				GetLastError());
+		FreeSid(ServiceSid);
+		return -1;
 	}
-
-	free(InfoBuffer);
 	FreeSid(ServiceSid);
 
-	CloseHandle(AccessToken);
+	if (IsMember)
+		_is_service = 1;
+	else
+		_is_service = 0;
 
 	return _is_service;
 }
-
-
-/*
- * Call GetTokenInformation() on a token and return a dynamically sized
- * buffer with the information in it. This buffer must be free():d by
- * the calling function!
- */
-static BOOL
-pgwin32_get_dynamic_tokeninfo(HANDLE token, TOKEN_INFORMATION_CLASS class,
-							  char **InfoBuffer, char *errbuf, int errsize)
-{
-	DWORD		InfoBufferSize;
-
-	if (GetTokenInformation(token, class, NULL, 0, &InfoBufferSize))
-	{
-		snprintf(errbuf, errsize,
-			 "could not get token information buffer size: got zero size\n");
-		return FALSE;
-	}
-
-	if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
-	{
-		snprintf(errbuf, errsize,
-			 "could not get token information buffer size: error code %lu\n",
-				 GetLastError());
-		return FALSE;
-	}
-
-	*InfoBuffer = malloc(InfoBufferSize);
-	if (*InfoBuffer == NULL)
-	{
-		snprintf(errbuf, errsize,
-				 "could not allocate %d bytes for token information\n",
-				 (int) InfoBufferSize);
-		return FALSE;
-	}
-
-	if (!GetTokenInformation(token, class, *InfoBuffer,
-							 InfoBufferSize, &InfoBufferSize))
-	{
-		snprintf(errbuf, errsize,
-				 "could not get token information: error code %lu\n",
-				 GetLastError());
-		return FALSE;
-	}
-
-	return TRUE;
-}
-- 
GitLab