1/*-------------------------------------------------------------------------
2 *
3 * restricted_token.c
4 * helper routine to ensure restricted token on Windows
5 *
6 *
7 * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
8 * Portions Copyright (c) 1994, Regents of the University of California
9 *
10 *
11 * IDENTIFICATION
12 * src/common/restricted_token.c
13 *
14 *-------------------------------------------------------------------------
15 */
16
17#ifndef FRONTEND
18#error "This file is not expected to be compiled for backend code"
19#endif
20
21#include "postgres_fe.h"
22
23#include "common/logging.h"
24#include "common/restricted_token.h"
25
26#ifdef WIN32
27
28/* internal vars */
29char *restrict_env;
30
31typedef BOOL (WINAPI * __CreateRestrictedToken) (HANDLE, DWORD, DWORD, PSID_AND_ATTRIBUTES, DWORD, PLUID_AND_ATTRIBUTES, DWORD, PSID_AND_ATTRIBUTES, PHANDLE);
32
33/* Windows API define missing from some versions of MingW headers */
34#ifndef DISABLE_MAX_PRIVILEGE
35#define DISABLE_MAX_PRIVILEGE 0x1
36#endif
37
38/*
39 * Create a restricted token and execute the specified process with it.
40 *
41 * Returns restricted token on success and 0 on failure.
42 *
43 * On NT4, or any other system not containing the required functions, will
44 * NOT execute anything.
45 */
46HANDLE
47CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo)
48{
49 BOOL b;
50 STARTUPINFO si;
51 HANDLE origToken;
52 HANDLE restrictedToken;
53 SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
54 SID_AND_ATTRIBUTES dropSids[2];
55 __CreateRestrictedToken _CreateRestrictedToken = NULL;
56 HANDLE Advapi32Handle;
57
58 ZeroMemory(&si, sizeof(si));
59 si.cb = sizeof(si);
60
61 Advapi32Handle = LoadLibrary("ADVAPI32.DLL");
62 if (Advapi32Handle != NULL)
63 {
64 _CreateRestrictedToken = (__CreateRestrictedToken) GetProcAddress(Advapi32Handle, "CreateRestrictedToken");
65 }
66
67 if (_CreateRestrictedToken == NULL)
68 {
69 pg_log_warning("cannot create restricted tokens on this platform");
70 if (Advapi32Handle != NULL)
71 FreeLibrary(Advapi32Handle);
72 return 0;
73 }
74
75 /* Open the current token to use as a base for the restricted one */
76 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &origToken))
77 {
78 pg_log_error("could not open process token: error code %lu", GetLastError());
79 return 0;
80 }
81
82 /* Allocate list of SIDs to remove */
83 ZeroMemory(&dropSids, sizeof(dropSids));
84 if (!AllocateAndInitializeSid(&NtAuthority, 2,
85 SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0,
86 0, &dropSids[0].Sid) ||
87 !AllocateAndInitializeSid(&NtAuthority, 2,
88 SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_POWER_USERS, 0, 0, 0, 0, 0,
89 0, &dropSids[1].Sid))
90 {
91 pg_log_error("could not allocate SIDs: error code %lu", GetLastError());
92 return 0;
93 }
94
95 b = _CreateRestrictedToken(origToken,
96 DISABLE_MAX_PRIVILEGE,
97 sizeof(dropSids) / sizeof(dropSids[0]),
98 dropSids,
99 0, NULL,
100 0, NULL,
101 &restrictedToken);
102
103 FreeSid(dropSids[1].Sid);
104 FreeSid(dropSids[0].Sid);
105 CloseHandle(origToken);
106 FreeLibrary(Advapi32Handle);
107
108 if (!b)
109 {
110 pg_log_error("could not create restricted token: error code %lu", GetLastError());
111 return 0;
112 }
113
114#ifndef __CYGWIN__
115 AddUserToTokenDacl(restrictedToken);
116#endif
117
118 if (!CreateProcessAsUser(restrictedToken,
119 NULL,
120 cmd,
121 NULL,
122 NULL,
123 TRUE,
124 CREATE_SUSPENDED,
125 NULL,
126 NULL,
127 &si,
128 processInfo))
129
130 {
131 pg_log_error("could not start process for command \"%s\": error code %lu", cmd, GetLastError());
132 return 0;
133 }
134
135 ResumeThread(processInfo->hThread);
136 return restrictedToken;
137}
138#endif
139
140/*
141 * On Windows make sure that we are running with a restricted token,
142 * On other platforms do nothing.
143 */
144void
145get_restricted_token(void)
146{
147#ifdef WIN32
148 HANDLE restrictedToken;
149
150 /*
151 * Before we execute another program, make sure that we are running with a
152 * restricted token. If not, re-execute ourselves with one.
153 */
154
155 if ((restrict_env = getenv("PG_RESTRICT_EXEC")) == NULL
156 || strcmp(restrict_env, "1") != 0)
157 {
158 PROCESS_INFORMATION pi;
159 char *cmdline;
160
161 ZeroMemory(&pi, sizeof(pi));
162
163 cmdline = pg_strdup(GetCommandLine());
164
165 putenv("PG_RESTRICT_EXEC=1");
166
167 if ((restrictedToken = CreateRestrictedProcess(cmdline, &pi)) == 0)
168 {
169 pg_log_error("could not re-execute with restricted token: error code %lu", GetLastError());
170 }
171 else
172 {
173 /*
174 * Successfully re-execed. Now wait for child process to capture
175 * exitcode.
176 */
177 DWORD x;
178
179 CloseHandle(restrictedToken);
180 CloseHandle(pi.hThread);
181 WaitForSingleObject(pi.hProcess, INFINITE);
182
183 if (!GetExitCodeProcess(pi.hProcess, &x))
184 {
185 pg_log_error("could not get exit code from subprocess: error code %lu", GetLastError());
186 exit(1);
187 }
188 exit(x);
189 }
190 }
191#endif
192}
193