Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * exec.c
4 : : * Functions for finding and validating executable files
5 : : *
6 : : *
7 : : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
8 : : * Portions Copyright (c) 1994, Regents of the University of California
9 : : *
10 : : *
11 : : * IDENTIFICATION
12 : : * src/common/exec.c
13 : : *
14 : : *-------------------------------------------------------------------------
15 : : */
16 : :
17 : : /*
18 : : * On macOS, "man realpath" avers:
19 : : * Defining _DARWIN_C_SOURCE or _DARWIN_BETTER_REALPATH before including
20 : : * stdlib.h will cause the provided implementation of realpath() to use
21 : : * F_GETPATH from fcntl(2) to discover the path.
22 : : * This should be harmless everywhere else.
23 : : */
24 : : #define _DARWIN_BETTER_REALPATH
25 : :
26 : : #ifndef FRONTEND
27 : : #include "postgres.h"
28 : : #else
29 : : #include "postgres_fe.h"
30 : : #endif
31 : :
32 : : #include <signal.h>
33 : : #include <sys/stat.h>
34 : : #include <sys/wait.h>
35 : : #include <unistd.h>
36 : :
37 : : #ifdef EXEC_BACKEND
38 : : #if defined(HAVE_SYS_PERSONALITY_H)
39 : : #include <sys/personality.h>
40 : : #elif defined(HAVE_SYS_PROCCTL_H)
41 : : #include <sys/procctl.h>
42 : : #endif
43 : : #endif
44 : :
45 : : #include "common/string.h"
46 : :
47 : : /* Inhibit mingw CRT's auto-globbing of command line arguments */
48 : : #if defined(WIN32) && !defined(_MSC_VER)
49 : : extern int _CRT_glob;
50 : : int _CRT_glob = 0; /* 0 turns off globbing; 1 turns it on */
51 : : #endif
52 : :
53 : : /*
54 : : * Hacky solution to allow expressing both frontend and backend error reports
55 : : * in one macro call. First argument of log_error is an errcode() call of
56 : : * some sort (ignored if FRONTEND); the rest are errmsg_internal() arguments,
57 : : * i.e. message string and any parameters for it.
58 : : *
59 : : * Caller must provide the gettext wrapper around the message string, if
60 : : * appropriate, so that it gets translated in the FRONTEND case; this
61 : : * motivates using errmsg_internal() not errmsg(). We handle appending a
62 : : * newline, if needed, inside the macro, so that there's only one translatable
63 : : * string per call not two.
64 : : */
65 : : #ifndef FRONTEND
66 : : #define log_error(errcodefn, ...) \
67 : : ereport(LOG, (errcodefn, errmsg_internal(__VA_ARGS__)))
68 : : #else
69 : : #define log_error(errcodefn, ...) \
70 : : (fprintf(stderr, __VA_ARGS__), fputc('\n', stderr))
71 : : #endif
72 : :
73 : : static int normalize_exec_path(char *path);
74 : : static char *pg_realpath(const char *fname);
75 : :
76 : : #ifdef WIN32
77 : : static BOOL GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser);
78 : : #endif
79 : :
80 : : /*
81 : : * validate_exec -- validate "path" as an executable file
82 : : *
83 : : * returns 0 if the file is found and no error is encountered.
84 : : * -1 if the regular file "path" does not exist or cannot be executed.
85 : : * -2 if the file is otherwise valid but cannot be read.
86 : : * in the failure cases, errno is set appropriately
87 : : */
88 : : int
7826 bruce@momjian.us 89 :CBC 18581 : validate_exec(const char *path)
90 : : {
91 : : struct stat buf;
92 : : int is_r;
93 : : int is_x;
94 : :
95 : : #ifdef WIN32
96 : : char path_exe[MAXPGPATH + sizeof(".exe") - 1];
97 : :
98 : : /* Win32 requires a .exe suffix for stat() */
99 : : if (strlen(path) < strlen(".exe") ||
100 : : pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
101 : : {
102 : : strlcpy(path_exe, path, sizeof(path_exe) - 4);
103 : : strcat(path_exe, ".exe");
104 : : path = path_exe;
105 : : }
106 : : #endif
107 : :
108 : : /*
109 : : * Ensure that the file exists and is a regular file.
110 : : *
111 : : * XXX if you have a broken system where stat() looks at the symlink
112 : : * instead of the underlying file, you lose.
113 : : */
10277 114 [ - + ]: 18581 : if (stat(path, &buf) < 0)
9918 bruce@momjian.us 115 :UBC 0 : return -1;
116 : :
6419 tgl@sss.pgh.pa.us 117 [ - + ]:CBC 18581 : if (!S_ISREG(buf.st_mode))
118 : : {
119 : : /*
120 : : * POSIX offers no errno code that's simply "not a regular file". If
121 : : * it's a directory we can use EISDIR. Otherwise, it's most likely a
122 : : * device special file, and EPERM (Operation not permitted) isn't too
123 : : * horribly off base.
124 : : */
1203 tgl@sss.pgh.pa.us 125 [ # # ]:UBC 0 : errno = S_ISDIR(buf.st_mode) ? EISDIR : EPERM;
9918 bruce@momjian.us 126 : 0 : return -1;
127 : : }
128 : :
129 : : /*
130 : : * Ensure that the file is both executable and readable (required for
131 : : * dynamic loading).
132 : : */
133 : : #ifndef WIN32
5765 tgl@sss.pgh.pa.us 134 :CBC 18581 : is_r = (access(path, R_OK) == 0);
135 : 18581 : is_x = (access(path, X_OK) == 0);
136 : : /* access() will set errno if it returns -1 */
137 : : #else
138 : : is_r = buf.st_mode & S_IRUSR;
139 : : is_x = buf.st_mode & S_IXUSR;
140 : : errno = EACCES; /* appropriate thing if we return nonzero */
141 : : #endif
142 [ + - + - ]: 18581 : return is_x ? (is_r ? 0 : -2) : -1;
143 : : }
144 : :
145 : :
146 : : /*
147 : : * find_my_exec -- find an absolute path to this program's executable
148 : : *
149 : : * argv0 is the name passed on the command line
150 : : * retpath is the output area (must be of size MAXPGPATH)
151 : : * Returns 0 if OK, -1 if error.
152 : : *
153 : : * The reason we have to work so hard to find an absolute path is that
154 : : * on some platforms we can't do dynamic loading unless we know the
155 : : * executable's location. Also, we need an absolute path not a relative
156 : : * path because we may later change working directory. Finally, we want
157 : : * a true path not a symlink location, so that we can locate other files
158 : : * that are part of our installation relative to the executable.
159 : : */
160 : : int
7830 bruce@momjian.us 161 : 17460 : find_my_exec(const char *argv0, char *retpath)
162 : : {
163 : : char *path;
164 : :
165 : : /*
166 : : * If argv0 contains a separator, then PATH wasn't used.
167 : : */
949 tgl@sss.pgh.pa.us 168 : 17460 : strlcpy(retpath, argv0, MAXPGPATH);
169 [ + + ]: 17460 : if (first_dir_separator(retpath) != NULL)
170 : : {
7830 bruce@momjian.us 171 [ + - ]: 13026 : if (validate_exec(retpath) == 0)
949 tgl@sss.pgh.pa.us 172 : 13026 : return normalize_exec_path(retpath);
173 : :
2575 tgl@sss.pgh.pa.us 174 [ # # ]:UBC 0 : log_error(errcode(ERRCODE_WRONG_OBJECT_TYPE),
175 : : _("invalid binary \"%s\": %m"), retpath);
7660 176 : 0 : return -1;
177 : : }
178 : :
179 : : #ifdef WIN32
180 : : /* Win32 checks the current directory first for names without slashes */
181 : : if (validate_exec(retpath) == 0)
182 : : return normalize_exec_path(retpath);
183 : : #endif
184 : :
185 : : /*
186 : : * Since no explicit path was supplied, the user must have been relying on
187 : : * PATH. We'll search the same PATH.
188 : : */
7826 bruce@momjian.us 189 [ + - + - ]:CBC 4434 : if ((path = getenv("PATH")) && *path)
190 : : {
7729 191 : 4434 : char *startp = NULL,
192 : 4434 : *endp = NULL;
193 : :
194 : : do
195 : : {
7826 196 [ + - ]: 4434 : if (!startp)
197 : 4434 : startp = path;
198 : : else
7826 bruce@momjian.us 199 :UBC 0 : startp = endp + 1;
200 : :
5381 bruce@momjian.us 201 :CBC 4434 : endp = first_path_var_separator(startp);
7826 202 [ - + ]: 4434 : if (!endp)
7729 bruce@momjian.us 203 :UBC 0 : endp = startp + strlen(startp); /* point to end */
204 : :
949 tgl@sss.pgh.pa.us 205 :CBC 4434 : strlcpy(retpath, startp, Min(endp - startp + 1, MAXPGPATH));
206 : :
207 : 4434 : join_path_components(retpath, retpath, argv0);
7830 bruce@momjian.us 208 : 4434 : canonicalize_path(retpath);
209 : :
210 [ + - - - ]: 4434 : switch (validate_exec(retpath))
211 : : {
3050 tgl@sss.pgh.pa.us 212 : 4434 : case 0: /* found ok */
949 213 : 4434 : return normalize_exec_path(retpath);
10276 bruce@momjian.us 214 :UBC 0 : case -1: /* wasn't even a candidate, keep looking */
7660 tgl@sss.pgh.pa.us 215 : 0 : break;
10276 bruce@momjian.us 216 : 0 : case -2: /* found but disqualified */
2575 tgl@sss.pgh.pa.us 217 [ # # ]: 0 : log_error(errcode(ERRCODE_WRONG_OBJECT_TYPE),
218 : : _("could not read binary \"%s\": %m"),
219 : : retpath);
7660 220 : 0 : break;
221 : : }
7826 bruce@momjian.us 222 [ # # ]: 0 : } while (*endp);
223 : : }
224 : :
2575 tgl@sss.pgh.pa.us 225 [ # # ]: 0 : log_error(errcode(ERRCODE_UNDEFINED_FILE),
226 : : _("could not find a \"%s\" to execute"), argv0);
9918 bruce@momjian.us 227 : 0 : return -1;
228 : : }
229 : :
230 : :
231 : : /*
232 : : * normalize_exec_path - resolve symlinks and convert to absolute path
233 : : *
234 : : * Given a path that refers to an executable, chase through any symlinks
235 : : * to find the real file location; then convert that to an absolute path.
236 : : *
237 : : * On success, replaces the contents of "path" with the absolute path.
238 : : * ("path" is assumed to be of size MAXPGPATH.)
239 : : * Returns 0 if OK, -1 if error.
240 : : */
241 : : static int
949 tgl@sss.pgh.pa.us 242 :CBC 17460 : normalize_exec_path(char *path)
243 : : {
244 : : /*
245 : : * We used to do a lot of work ourselves here, but now we just let
246 : : * realpath(3) do all the heavy lifting.
247 : : */
248 : 17460 : char *abspath = pg_realpath(path);
249 : :
250 [ - + ]: 17460 : if (abspath == NULL)
251 : : {
2575 tgl@sss.pgh.pa.us 252 [ # # ]:UBC 0 : log_error(errcode_for_file_access(),
253 : : _("could not resolve path \"%s\" to absolute form: %m"),
254 : : path);
7660 255 : 0 : return -1;
256 : : }
949 tgl@sss.pgh.pa.us 257 :CBC 17460 : strlcpy(path, abspath, MAXPGPATH);
258 : 17460 : free(abspath);
259 : :
260 : : #ifdef WIN32
261 : : /* On Windows, be sure to convert '\' to '/' */
262 : : canonicalize_path(path);
263 : : #endif
264 : :
265 : 17460 : return 0;
266 : : }
267 : :
268 : :
269 : : /*
270 : : * pg_realpath() - realpath(3) with POSIX.1-2008 semantics
271 : : *
272 : : * This is equivalent to realpath(fname, NULL), in that it returns a
273 : : * malloc'd buffer containing the absolute path equivalent to fname.
274 : : * On error, returns NULL with errno set.
275 : : *
276 : : * On Windows, what you get is spelled per platform conventions,
277 : : * so you probably want to apply canonicalize_path() to the result.
278 : : *
279 : : * For now, this is needed only here so mark it static. If you choose to
280 : : * move it into its own file, move the _DARWIN_BETTER_REALPATH #define too!
281 : : */
282 : : static char *
283 : 17460 : pg_realpath(const char *fname)
284 : : {
285 : : char *path;
286 : :
287 : : #ifndef WIN32
288 : 17460 : path = realpath(fname, NULL);
289 : : #else /* WIN32 */
290 : :
291 : : /*
292 : : * Microsoft is resolutely non-POSIX, but _fullpath() does the same thing.
293 : : * The documentation claims it reports errors by setting errno, which is a
294 : : * bit surprising for Microsoft, but we'll believe that until it's proven
295 : : * wrong. Clear errno first, though, so we can at least tell if a failure
296 : : * occurs and doesn't set it.
297 : : */
298 : : errno = 0;
299 : : path = _fullpath(NULL, fname, 0);
300 : : #endif
301 : :
302 : 17460 : return path;
303 : : }
304 : :
305 : :
306 : : /*
307 : : * Find another program in our binary's directory,
308 : : * then make sure it is the proper version.
309 : : */
310 : : int
7660 311 : 883 : find_other_exec(const char *argv0, const char *target,
312 : : const char *versionstr, char *retpath)
313 : : {
314 : : char cmd[MAXPGPATH];
315 : : char *line;
316 : :
317 [ - + ]: 883 : if (find_my_exec(argv0, retpath) < 0)
7660 tgl@sss.pgh.pa.us 318 :UBC 0 : return -1;
319 : :
320 : : /* Trim off program name and keep just directory */
7660 tgl@sss.pgh.pa.us 321 :CBC 883 : *last_dir_separator(retpath) = '\0';
322 : 883 : canonicalize_path(retpath);
323 : :
324 : : /* Now append the other program's name */
325 : 883 : snprintf(retpath + strlen(retpath), MAXPGPATH - strlen(retpath),
326 : : "/%s%s", target, EXE);
327 : :
328 [ - + ]: 883 : if (validate_exec(retpath) != 0)
7660 tgl@sss.pgh.pa.us 329 :UBC 0 : return -1;
330 : :
4840 tgl@sss.pgh.pa.us 331 :CBC 883 : snprintf(cmd, sizeof(cmd), "\"%s\" -V", retpath);
332 : :
626 dgustafsson@postgres 333 [ - + ]: 883 : if ((line = pipe_read_line(cmd)) == NULL)
7660 tgl@sss.pgh.pa.us 334 :UBC 0 : return -1;
335 : :
7660 tgl@sss.pgh.pa.us 336 [ - + ]:CBC 883 : if (strcmp(line, versionstr) != 0)
337 : : {
626 dgustafsson@postgres 338 :UBC 0 : pfree(line);
7660 tgl@sss.pgh.pa.us 339 : 0 : return -2;
340 : : }
341 : :
626 dgustafsson@postgres 342 :CBC 883 : pfree(line);
7660 tgl@sss.pgh.pa.us 343 : 883 : return 0;
344 : : }
345 : :
346 : :
347 : : /*
348 : : * Execute a command in a pipe and read the first line from it. The returned
349 : : * string is palloc'd (malloc'd in frontend code), the caller is responsible
350 : : * for freeing.
351 : : */
352 : : char *
626 dgustafsson@postgres 353 : 1122 : pipe_read_line(char *cmd)
354 : : {
355 : : FILE *pipe_cmd;
356 : : char *line;
357 : :
1155 tgl@sss.pgh.pa.us 358 : 1122 : fflush(NULL);
359 : :
4840 360 : 1122 : errno = 0;
626 dgustafsson@postgres 361 [ - + ]: 1122 : if ((pipe_cmd = popen(cmd, "r")) == NULL)
362 : : {
598 dgustafsson@postgres 363 [ # # ]:UBC 0 : log_error(errcode(ERRCODE_SYSTEM_ERROR),
364 : : _("could not execute command \"%s\": %m"), cmd);
7763 bruce@momjian.us 365 : 0 : return NULL;
366 : : }
367 : :
368 : : /* Make sure popen() didn't change errno */
4840 tgl@sss.pgh.pa.us 369 :CBC 1122 : errno = 0;
626 dgustafsson@postgres 370 : 1122 : line = pg_get_line(pipe_cmd, NULL);
371 : :
372 [ - + ]: 1122 : if (line == NULL)
373 : : {
626 dgustafsson@postgres 374 [ # # ]:UBC 0 : if (ferror(pipe_cmd))
375 [ # # ]: 0 : log_error(errcode_for_file_access(),
376 : : _("could not read from command \"%s\": %m"), cmd);
377 : : else
598 378 [ # # ]: 0 : log_error(errcode(ERRCODE_NO_DATA),
379 : : _("no data was returned by command \"%s\""), cmd);
380 : : }
381 : :
626 dgustafsson@postgres 382 :CBC 1122 : (void) pclose_check(pipe_cmd);
383 : :
7763 bruce@momjian.us 384 : 1122 : return line;
385 : : }
386 : :
387 : :
388 : : /*
389 : : * pclose() plus useful error reporting
390 : : */
391 : : int
7832 392 : 1223 : pclose_check(FILE *stream)
393 : : {
394 : : int exitstatus;
395 : : char *reason;
396 : :
397 : 1223 : exitstatus = pclose(stream);
398 : :
399 [ + + ]: 1223 : if (exitstatus == 0)
7729 400 : 1220 : return 0; /* all is well */
401 : :
7832 402 [ - + ]: 3 : if (exitstatus == -1)
403 : : {
404 : : /* pclose() itself failed, and hopefully set errno */
2575 tgl@sss.pgh.pa.us 405 [ # # ]:UBC 0 : log_error(errcode(ERRCODE_SYSTEM_ERROR),
406 : : _("%s() failed: %m"), "pclose");
407 : : }
408 : : else
409 : : {
4625 heikki.linnakangas@i 410 :CBC 3 : reason = wait_result_to_str(exitstatus);
2575 tgl@sss.pgh.pa.us 411 [ # # ]: 3 : log_error(errcode(ERRCODE_SYSTEM_ERROR),
412 : : "%s", reason);
4625 heikki.linnakangas@i 413 : 3 : pfree(reason);
414 : : }
415 : 3 : return exitstatus;
416 : : }
417 : :
418 : : /*
419 : : * set_pglocale_pgservice
420 : : *
421 : : * Set application-specific locale and service directory
422 : : *
423 : : * This function takes the value of argv[0] rather than a full path.
424 : : *
425 : : * (You may be wondering why this is in exec.c. It requires this module's
426 : : * services and doesn't introduce any new dependencies, so this seems as
427 : : * good as anyplace.)
428 : : */
429 : : void
6986 tgl@sss.pgh.pa.us 430 : 14956 : set_pglocale_pgservice(const char *argv0, const char *app)
431 : : {
432 : : char path[MAXPGPATH];
433 : : char my_exec_path[MAXPGPATH];
434 : :
435 : : /* don't set LC_ALL in the backend */
6164 peter_e@gmx.net 436 [ + + ]: 14956 : if (strcmp(app, PG_TEXTDOMAIN("postgres")) != 0)
437 : : {
6986 tgl@sss.pgh.pa.us 438 : 13112 : setlocale(LC_ALL, "");
439 : :
440 : : /*
441 : : * One could make a case for reproducing here PostmasterMain()'s test
442 : : * for whether the process is multithreaded. Unlike the postmaster,
443 : : * no frontend program calls sigprocmask() or otherwise provides for
444 : : * mutual exclusion between signal handlers. While frontends using
445 : : * fork(), if multithreaded, are formally exposed to undefined
446 : : * behavior, we have not witnessed a concrete bug. Therefore,
447 : : * complaining about multithreading here may be mere pedantry.
448 : : */
449 : : }
450 : :
451 [ - + ]: 14956 : if (find_my_exec(argv0, my_exec_path) < 0)
6986 tgl@sss.pgh.pa.us 452 :UBC 0 : return;
453 : :
454 : : #ifdef ENABLE_NLS
6986 tgl@sss.pgh.pa.us 455 :CBC 14956 : get_locale_path(my_exec_path, path);
456 : 14956 : bindtextdomain(app, path);
457 : 14956 : textdomain(app);
458 : : /* set for libpq to use, but don't override existing setting */
1762 459 : 14956 : setenv("PGLOCALEDIR", path, 0);
460 : : #endif
461 : :
6986 462 [ + + ]: 14956 : if (getenv("PGSYSCONFDIR") == NULL)
463 : : {
464 : 11082 : get_etc_path(my_exec_path, path);
465 : : /* set for libpq to use */
1762 466 : 11082 : setenv("PGSYSCONFDIR", path, 0);
467 : : }
468 : : }
469 : :
470 : : #ifdef EXEC_BACKEND
471 : : /*
472 : : * For the benefit of PostgreSQL developers testing EXEC_BACKEND on Unix
473 : : * systems (code paths normally exercised only on Windows), provide a way to
474 : : * disable address space layout randomization, if we know how on this platform.
475 : : * Otherwise, backends may fail to attach to shared memory at the fixed address
476 : : * chosen by the postmaster. (See also the macOS-specific hack in
477 : : * sysv_shmem.c.)
478 : : */
479 : : int
480 : : pg_disable_aslr(void)
481 : : {
482 : : #if defined(HAVE_SYS_PERSONALITY_H)
483 : : return personality(ADDR_NO_RANDOMIZE);
484 : : #elif defined(HAVE_SYS_PROCCTL_H) && defined(PROC_ASLR_FORCE_DISABLE)
485 : : int data = PROC_ASLR_FORCE_DISABLE;
486 : :
487 : : return procctl(P_PID, 0, PROC_ASLR_CTL, &data);
488 : : #else
489 : : errno = ENOSYS;
490 : : return -1;
491 : : #endif
492 : : }
493 : : #endif
494 : :
495 : : #ifdef WIN32
496 : :
497 : : /*
498 : : * AddUserToTokenDacl(HANDLE hToken)
499 : : *
500 : : * This function adds the current user account to the restricted
501 : : * token used when we create a restricted process.
502 : : *
503 : : * This is required because of some security changes in Windows
504 : : * that appeared in patches to XP/2K3 and in Vista/2008.
505 : : *
506 : : * On these machines, the Administrator account is not included in
507 : : * the default DACL - you just get Administrators + System. For
508 : : * regular users you get User + System. Because we strip Administrators
509 : : * when we create the restricted token, we are left with only System
510 : : * in the DACL which leads to access denied errors for later CreatePipe()
511 : : * and CreateProcess() calls when running as Administrator.
512 : : *
513 : : * This function fixes this problem by modifying the DACL of the
514 : : * token the process will use, and explicitly re-adding the current
515 : : * user account. This is still secure because the Administrator account
516 : : * inherits its privileges from the Administrators group - it doesn't
517 : : * have any of its own.
518 : : */
519 : : BOOL
520 : : AddUserToTokenDacl(HANDLE hToken)
521 : : {
522 : : int i;
523 : : ACL_SIZE_INFORMATION asi;
524 : : ACCESS_ALLOWED_ACE *pace;
525 : : DWORD dwNewAclSize;
526 : : DWORD dwSize = 0;
527 : : DWORD dwTokenInfoLength = 0;
528 : : PACL pacl = NULL;
529 : : PTOKEN_USER pTokenUser = NULL;
530 : : TOKEN_DEFAULT_DACL tddNew;
531 : : TOKEN_DEFAULT_DACL *ptdd = NULL;
532 : : TOKEN_INFORMATION_CLASS tic = TokenDefaultDacl;
533 : : BOOL ret = FALSE;
534 : :
535 : : /* Figure out the buffer size for the DACL info */
536 : : if (!GetTokenInformation(hToken, tic, (LPVOID) NULL, dwTokenInfoLength, &dwSize))
537 : : {
538 : : if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
539 : : {
540 : : ptdd = (TOKEN_DEFAULT_DACL *) LocalAlloc(LPTR, dwSize);
541 : : if (ptdd == NULL)
542 : : {
543 : : log_error(errcode(ERRCODE_OUT_OF_MEMORY),
544 : : _("out of memory"));
545 : : goto cleanup;
546 : : }
547 : :
548 : : if (!GetTokenInformation(hToken, tic, (LPVOID) ptdd, dwSize, &dwSize))
549 : : {
550 : : log_error(errcode(ERRCODE_SYSTEM_ERROR),
551 : : "could not get token information: error code %lu",
552 : : GetLastError());
553 : : goto cleanup;
554 : : }
555 : : }
556 : : else
557 : : {
558 : : log_error(errcode(ERRCODE_SYSTEM_ERROR),
559 : : "could not get token information buffer size: error code %lu",
560 : : GetLastError());
561 : : goto cleanup;
562 : : }
563 : : }
564 : :
565 : : /* Get the ACL info */
566 : : if (!GetAclInformation(ptdd->DefaultDacl, (LPVOID) &asi,
567 : : (DWORD) sizeof(ACL_SIZE_INFORMATION),
568 : : AclSizeInformation))
569 : : {
570 : : log_error(errcode(ERRCODE_SYSTEM_ERROR),
571 : : "could not get ACL information: error code %lu",
572 : : GetLastError());
573 : : goto cleanup;
574 : : }
575 : :
576 : : /* Get the current user SID */
577 : : if (!GetTokenUser(hToken, &pTokenUser))
578 : : goto cleanup; /* callee printed a message */
579 : :
580 : : /* Figure out the size of the new ACL */
581 : : dwNewAclSize = asi.AclBytesInUse + sizeof(ACCESS_ALLOWED_ACE) +
582 : : GetLengthSid(pTokenUser->User.Sid) - sizeof(DWORD);
583 : :
584 : : /* Allocate the ACL buffer & initialize it */
585 : : pacl = (PACL) LocalAlloc(LPTR, dwNewAclSize);
586 : : if (pacl == NULL)
587 : : {
588 : : log_error(errcode(ERRCODE_OUT_OF_MEMORY),
589 : : _("out of memory"));
590 : : goto cleanup;
591 : : }
592 : :
593 : : if (!InitializeAcl(pacl, dwNewAclSize, ACL_REVISION))
594 : : {
595 : : log_error(errcode(ERRCODE_SYSTEM_ERROR),
596 : : "could not initialize ACL: error code %lu", GetLastError());
597 : : goto cleanup;
598 : : }
599 : :
600 : : /* Loop through the existing ACEs, and build the new ACL */
601 : : for (i = 0; i < (int) asi.AceCount; i++)
602 : : {
603 : : if (!GetAce(ptdd->DefaultDacl, i, (LPVOID *) &pace))
604 : : {
605 : : log_error(errcode(ERRCODE_SYSTEM_ERROR),
606 : : "could not get ACE: error code %lu", GetLastError());
607 : : goto cleanup;
608 : : }
609 : :
610 : : if (!AddAce(pacl, ACL_REVISION, MAXDWORD, pace, ((PACE_HEADER) pace)->AceSize))
611 : : {
612 : : log_error(errcode(ERRCODE_SYSTEM_ERROR),
613 : : "could not add ACE: error code %lu", GetLastError());
614 : : goto cleanup;
615 : : }
616 : : }
617 : :
618 : : /* Add the new ACE for the current user */
619 : : if (!AddAccessAllowedAceEx(pacl, ACL_REVISION, OBJECT_INHERIT_ACE, GENERIC_ALL, pTokenUser->User.Sid))
620 : : {
621 : : log_error(errcode(ERRCODE_SYSTEM_ERROR),
622 : : "could not add access allowed ACE: error code %lu",
623 : : GetLastError());
624 : : goto cleanup;
625 : : }
626 : :
627 : : /* Set the new DACL in the token */
628 : : tddNew.DefaultDacl = pacl;
629 : :
630 : : if (!SetTokenInformation(hToken, tic, (LPVOID) &tddNew, dwNewAclSize))
631 : : {
632 : : log_error(errcode(ERRCODE_SYSTEM_ERROR),
633 : : "could not set token information: error code %lu",
634 : : GetLastError());
635 : : goto cleanup;
636 : : }
637 : :
638 : : ret = TRUE;
639 : :
640 : : cleanup:
641 : : if (pTokenUser)
642 : : LocalFree((HLOCAL) pTokenUser);
643 : :
644 : : if (pacl)
645 : : LocalFree((HLOCAL) pacl);
646 : :
647 : : if (ptdd)
648 : : LocalFree((HLOCAL) ptdd);
649 : :
650 : : return ret;
651 : : }
652 : :
653 : : /*
654 : : * GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser)
655 : : *
656 : : * Get the users token information from a process token.
657 : : *
658 : : * The caller of this function is responsible for calling LocalFree() on the
659 : : * returned TOKEN_USER memory.
660 : : */
661 : : static BOOL
662 : : GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser)
663 : : {
664 : : DWORD dwLength;
665 : :
666 : : *ppTokenUser = NULL;
667 : :
668 : : if (!GetTokenInformation(hToken,
669 : : TokenUser,
670 : : NULL,
671 : : 0,
672 : : &dwLength))
673 : : {
674 : : if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
675 : : {
676 : : *ppTokenUser = (PTOKEN_USER) LocalAlloc(LPTR, dwLength);
677 : :
678 : : if (*ppTokenUser == NULL)
679 : : {
680 : : log_error(errcode(ERRCODE_OUT_OF_MEMORY),
681 : : _("out of memory"));
682 : : return FALSE;
683 : : }
684 : : }
685 : : else
686 : : {
687 : : log_error(errcode(ERRCODE_SYSTEM_ERROR),
688 : : "could not get token information buffer size: error code %lu",
689 : : GetLastError());
690 : : return FALSE;
691 : : }
692 : : }
693 : :
694 : : if (!GetTokenInformation(hToken,
695 : : TokenUser,
696 : : *ppTokenUser,
697 : : dwLength,
698 : : &dwLength))
699 : : {
700 : : LocalFree(*ppTokenUser);
701 : : *ppTokenUser = NULL;
702 : :
703 : : log_error(errcode(ERRCODE_SYSTEM_ERROR),
704 : : "could not get token information: error code %lu",
705 : : GetLastError());
706 : : return FALSE;
707 : : }
708 : :
709 : : /* Memory in *ppTokenUser is LocalFree():d by the caller */
710 : : return TRUE;
711 : : }
712 : :
713 : : #endif
|