OpenVPN
platform.c
Go to the documentation of this file.
1/*
2 * OpenVPN -- An application to securely tunnel IP networks
3 * over a single TCP/UDP port, with support for SSL/TLS-based
4 * session authentication and key exchange,
5 * packet encryption, packet authentication, and
6 * packet compression.
7 *
8 * Copyright (C) 2002-2024 OpenVPN Inc <sales@openvpn.net>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 */
23
24#ifdef HAVE_CONFIG_H
25#include "config.h"
26#endif
27
28#include "syshead.h"
29
30#include "openvpn.h"
31#include "options.h"
32
33#include "buffer.h"
34#include "crypto.h"
35#include "error.h"
36#include "misc.h"
37#include "win32.h"
38
39#include "memdbg.h"
40
41#include "platform.h"
42
43#if _WIN32
44#include <direct.h>
45#endif
46
47#ifdef HAVE_LIBCAPNG
48#include <cap-ng.h>
49#include <sys/prctl.h>
50#endif
51
52/* Redefine the top level directory of the filesystem
53 * to restrict access to files for security */
54void
55platform_chroot(const char *path)
56{
57 if (path)
58 {
59#ifdef HAVE_CHROOT
60 const char *top = "/";
61 if (chroot(path))
62 {
63 msg(M_ERR, "chroot to '%s' failed", path);
64 }
65 if (platform_chdir(top))
66 {
67 msg(M_ERR, "cd to '%s' failed", top);
68 }
69 msg(M_INFO, "chroot to '%s' and cd to '%s' succeeded", path, top);
70#else /* ifdef HAVE_CHROOT */
71 msg(M_FATAL, "Sorry but I can't chroot to '%s' because this operating system doesn't appear to support the chroot() system call", path);
72#endif
73 }
74}
75
76/* Get/Set UID of process */
77
78bool
79platform_user_get(const char *username, struct platform_state_user *state)
80{
81 bool ret = false;
82 CLEAR(*state);
83 if (username)
84 {
85#if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID)
86 state->uid = -1;
87 const struct passwd *pw = getpwnam(username);
88 if (!pw)
89 {
90 msg(M_ERR, "failed to find UID for user %s", username);
91 }
92 else
93 {
94 state->uid = pw->pw_uid;
95 }
96 state->username = username;
97 ret = true;
98#else /* if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID) */
99 msg(M_FATAL, "cannot get UID for user %s -- platform lacks getpwname() or setuid() system calls", username);
100#endif
101 }
102 return ret;
103}
104
105static void
107{
108#if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID)
109 if (state->username && state->uid >= 0)
110 {
111 if (setuid(state->uid))
112 {
113 msg(M_ERR, "setuid('%s') failed", state->username);
114 }
115 msg(M_INFO, "UID set to %s", state->username);
116 }
117#endif
118}
119
120/* Get/Set GID of process */
121
122bool
123platform_group_get(const char *groupname, struct platform_state_group *state)
124{
125 bool ret = false;
126 CLEAR(*state);
127 if (groupname)
128 {
129#if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID)
130 state->gid = -1;
131 const struct group *gr = getgrnam(groupname);
132 if (!gr)
133 {
134 msg(M_ERR, "failed to find GID for group %s", groupname);
135 }
136 else
137 {
138 state->gid = gr->gr_gid;
139 }
140 state->groupname = groupname;
141 ret = true;
142#else /* if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID) */
143 msg(M_FATAL, "cannot get GID for group %s -- platform lacks getgrnam() or setgid() system calls", groupname);
144#endif
145 }
146 return ret;
147}
148
149static void
151{
152#if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID)
153 if (state->groupname && state->gid >= 0)
154 {
155 if (setgid(state->gid))
156 {
157 msg(M_ERR, "setgid('%s') failed", state->groupname);
158 }
159 msg(M_INFO, "GID set to %s", state->groupname);
160#ifdef HAVE_SETGROUPS
161 {
162 gid_t gr_list[1];
163 gr_list[0] = state->gid;
164 if (setgroups(1, gr_list))
165 {
166 msg(M_ERR, "setgroups('%s') failed", state->groupname);
167 }
168 }
169#endif
170 }
171#endif
172}
173
174/*
175 * Determine if we need to retain process capabilities. DCO and SITNL need it.
176 * Enforce it for DCO, but only try and soft-fail for SITNL to keep backwards compat.
177 *
178 * Returns the tri-state expected by platform_user_group_set.
179 * -1: try to keep caps, but continue if impossible
180 * 0: don't keep caps
181 * 1: keep caps, fail hard if impossible
182 */
183static int
185{
186 if (!c)
187 {
188 return -1;
189 }
190
191 if (dco_enabled(&c->options))
192 {
193#ifdef TARGET_LINUX
194 /* DCO on Linux does not work at all without CAP_NET_ADMIN */
195 return 1;
196#else
197 /* Windows/BSD/... has no equivalent capability mechanism */
198 return -1;
199#endif
200 }
201
202#ifdef ENABLE_SITNL
203 return -1;
204#else
205 return 0;
206#endif
207}
208
209/* Set user and group, retaining neccesary capabilities required by the platform.
210 *
211 * The keep_caps argument has 3 possible states:
212 * >0: Retain capabilities, and fail hard on failure to do so.
213 * ==0: Don't attempt to retain any capabilities, just sitch user/group.
214 * <0: Try to retain capabilities, but continue on failure.
215 */
216void
218 const struct platform_state_group *group_state,
219 struct context *c)
220{
221 int keep_caps = need_keep_caps(c);
222 unsigned int err_flags = (keep_caps > 0) ? M_FATAL : M_NONFATAL;
223#ifdef HAVE_LIBCAPNG
224 int new_gid = -1, new_uid = -1;
225 int res;
226
227 if (keep_caps == 0)
228 {
229 goto fallback;
230 }
231
232 /*
233 * new_uid/new_gid defaults to -1, which will not make
234 * libcap-ng change the UID/GID unless configured
235 */
236 if (group_state->groupname && group_state->gid >= 0)
237 {
238 new_gid = group_state->gid;
239 }
240 if (user_state->username && user_state->uid >= 0)
241 {
242 new_uid = user_state->uid;
243 }
244
245 /* Prepare capabilities before dropping UID/GID */
246 capng_clear(CAPNG_SELECT_BOTH);
247 res = capng_update(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED, CAP_NET_ADMIN);
248 if (res < 0)
249 {
250 msg(err_flags, "capng_update(CAP_NET_ADMIN) failed: %d", res);
251 goto fallback;
252 }
253
254 /* Change to new UID/GID.
255 * capng_change_id() internally calls capng_apply() to apply prepared capabilities.
256 */
257 res = capng_change_id(new_uid, new_gid, CAPNG_DROP_SUPP_GRP);
258 if (res == -4 || res == -6)
259 {
260 /* -4 and -6 mean failure of setuid/gid respectively.
261 * There is no point for us to continue if those failed. */
262 msg(M_ERR, "capng_change_id('%s','%s') failed: %d",
263 user_state->username, group_state->groupname, res);
264 }
265 else if (res == -3)
266 {
267 msg(M_NONFATAL | M_ERRNO, "capng_change_id() failed applying capabilities");
268 msg(err_flags, "NOTE: previous error likely due to missing capability CAP_SETPCAP.");
269 goto fallback;
270 }
271 else if (res < 0)
272 {
273 msg(err_flags | M_ERRNO, "capng_change_id('%s','%s') failed retaining capabilities: %d",
274 user_state->username, group_state->groupname, res);
275 goto fallback;
276 }
277
278 if (new_uid >= 0)
279 {
280 msg(M_INFO, "UID set to %s", user_state->username);
281 }
282 if (new_gid >= 0)
283 {
284 msg(M_INFO, "GID set to %s", group_state->groupname);
285 }
286
287 msg(M_INFO, "Capabilities retained: CAP_NET_ADMIN");
288 return;
289
290fallback:
291 /* capng_change_id() can leave this flag clobbered on failure
292 * This is working around a bug in libcap-ng, which can leave the flag set
293 * on failure: https://github.com/stevegrubb/libcap-ng/issues/33 */
294 if (prctl(PR_GET_KEEPCAPS) && prctl(PR_SET_KEEPCAPS, 0) < 0)
295 {
296 msg(M_ERR, "Clearing KEEPCAPS flag failed");
297 }
298#endif /* HAVE_LIBCAPNG */
299
300 if (keep_caps)
301 {
302 msg(err_flags, "Unable to retain capabilities");
303 }
304
305 platform_group_set(group_state);
306 platform_user_set(user_state);
307}
308
309/* Change process priority */
310void
311platform_nice(int niceval)
312{
313 if (niceval)
314 {
315#ifdef HAVE_NICE
316 errno = 0;
317 if (nice(niceval) < 0 && errno != 0)
318 {
319 msg(M_WARN | M_ERRNO, "WARNING: nice %d failed", niceval);
320 }
321 else
322 {
323 msg(M_INFO, "nice %d succeeded", niceval);
324 }
325#else /* ifdef HAVE_NICE */
326 msg(M_WARN, "WARNING: nice %d failed (function not implemented)", niceval);
327#endif
328 }
329}
330
331/* Get current PID */
332unsigned int
334{
335#ifdef _WIN32
336 return (unsigned int) GetCurrentProcessId();
337#else
338 return (unsigned int) getpid();
339#endif
340}
341
342/* Disable paging */
343void
344platform_mlockall(bool print_msg)
345{
346#ifdef HAVE_MLOCKALL
347
348#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_MEMLOCK)
349#define MIN_LOCKED_MEM_MB 100
350 struct rlimit rl;
351 if (getrlimit(RLIMIT_MEMLOCK, &rl) < 0)
352 {
353 msg(M_WARN | M_ERRNO, "WARNING: getrlimit(RLIMIT_MEMLOCK) failed");
354 }
355 else
356 {
357 msg(M_INFO, "mlock: MEMLOCK limit: soft=%ld KB, hard=%ld KB",
358 ((long int) rl.rlim_cur) / 1024, ((long int) rl.rlim_max) / 1024);
359 if (rl.rlim_cur < MIN_LOCKED_MEM_MB*1024*1024)
360 {
361 msg(M_INFO, "mlock: RLIMIT_MEMLOCK < %d MB, increase limit",
362 MIN_LOCKED_MEM_MB);
363 rl.rlim_cur = MIN_LOCKED_MEM_MB*1024*1024;
364 if (rl.rlim_max < rl.rlim_cur)
365 {
366 rl.rlim_max = rl.rlim_cur;
367 }
368 if (setrlimit(RLIMIT_MEMLOCK, &rl) < 0)
369 {
370 msg(M_FATAL | M_ERRNO, "ERROR: setrlimit() failed");
371 }
372 }
373 }
374#endif /* if defined(HAVE_GETRLIMIT) && defined(RLIMIT_MEMLOCK) */
375
376 if (mlockall(MCL_CURRENT | MCL_FUTURE))
377 {
378 msg(M_WARN | M_ERRNO, "WARNING: mlockall call failed");
379 }
380 else if (print_msg)
381 {
382 msg(M_INFO, "mlockall call succeeded");
383 }
384#else /* ifdef HAVE_MLOCKALL */
385 msg(M_WARN, "WARNING: mlockall call failed (function not implemented)");
386#endif /* ifdef HAVE_MLOCKALL */
387}
388
389/*
390 * Wrapper for chdir library function
391 */
392int
393platform_chdir(const char *dir)
394{
395#ifdef _WIN32
396 int res;
397 struct gc_arena gc = gc_new();
398 res = _wchdir(wide_string(dir, &gc));
399 gc_free(&gc);
400 return res;
401#else /* ifdef _WIN32 */
402#ifdef HAVE_CHDIR
403 return chdir(dir);
404#else /* ifdef HAVE_CHDIR */
405 return -1;
406#endif
407#endif
408}
409
410/*
411 * convert execve() return into a success/failure value
412 */
413bool
415{
416#ifdef _WIN32
417 return stat == 0;
418#else
419 return stat != -1 && WIFEXITED(stat) && WEXITSTATUS(stat) == 0;
420#endif
421}
422
423#ifdef _WIN32
424int
426{
427 if (stat >= 0 && stat < 255)
428 {
429 return stat;
430 }
431 else
432 {
433 return -1;
434 }
435}
436#else /* ifdef _WIN32 */
437int
438platform_ret_code(int stat)
439{
440 if (!WIFEXITED(stat) || stat == -1)
441 {
442 return -1;
443 }
444
445 int status = WEXITSTATUS(stat);
446 if (status >= 0 && status < 255)
447 {
448 return status;
449 }
450 else
451 {
452 return -1;
453 }
454}
455#endif /* ifdef _WIN32 */
456
457int
458platform_access(const char *path, int mode)
459{
460#ifdef _WIN32
461 struct gc_arena gc = gc_new();
462 int ret = _waccess(wide_string(path, &gc), mode & ~X_OK);
463 gc_free(&gc);
464 return ret;
465#else
466 return access(path, mode);
467#endif
468}
469
470/*
471 * Go to sleep for n milliseconds.
472 */
473void
475{
476#ifdef _WIN32
477 Sleep(n);
478#else
479 struct timeval tv;
480 tv.tv_sec = n / 1000;
481 tv.tv_usec = (n % 1000) * 1000;
482 select(0, NULL, NULL, NULL, &tv);
483#endif
484}
485
486/* delete a file, return true if succeeded */
487bool
488platform_unlink(const char *filename)
489{
490#if defined(_WIN32)
491 struct gc_arena gc = gc_new();
492 BOOL ret = DeleteFileW(wide_string(filename, &gc));
493 gc_free(&gc);
494 return (ret != 0);
495#else
496 return (unlink(filename) == 0);
497#endif
498}
499
500FILE *
501platform_fopen(const char *path, const char *mode)
502{
503#ifdef _WIN32
504 struct gc_arena gc = gc_new();
505 FILE *f = _wfopen(wide_string(path, &gc), wide_string(mode, &gc));
506 gc_free(&gc);
507 return f;
508#else
509 return fopen(path, mode);
510#endif
511}
512
513int
514platform_open(const char *path, int flags, int mode)
515{
516#ifdef _WIN32
517 struct gc_arena gc = gc_new();
518 int fd = _wopen(wide_string(path, &gc), flags, mode);
519 gc_free(&gc);
520 return fd;
521#else
522 return open(path, flags, mode);
523#endif
524}
525
526int
527platform_stat(const char *path, platform_stat_t *buf)
528{
529#ifdef _WIN32
530 struct gc_arena gc = gc_new();
531 int res = _wstat(wide_string(path, &gc), buf);
532 gc_free(&gc);
533 return res;
534#else
535 return stat(path, buf);
536#endif
537}
538
539/* create a temporary filename in directory */
540const char *
541platform_create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc)
542{
543 int fd;
544 const char *retfname = NULL;
545 unsigned int attempts = 0;
546 char fname[256] = { 0 };
547 const char *fname_fmt = PACKAGE "_%.*s_%08lx%08lx.tmp";
548 const int max_prefix_len = sizeof(fname) - (sizeof(PACKAGE) + 7 + (2 * 8));
549
550 while (attempts < 6)
551 {
552 ++attempts;
553
554 if (!snprintf(fname, sizeof(fname), fname_fmt, max_prefix_len,
555 prefix, (unsigned long) get_random(),
556 (unsigned long) get_random()))
557 {
558 msg(M_WARN, "ERROR: temporary filename too long");
559 return NULL;
560 }
561
562 retfname = platform_gen_path(directory, fname, gc);
563 if (!retfname)
564 {
565 msg(M_WARN, "Failed to create temporary filename and path");
566 return NULL;
567 }
568
569 /* Atomically create the file. Errors out if the file already
570 * exists. */
571 fd = platform_open(retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
572 if (fd != -1)
573 {
574 close(fd);
575 return retfname;
576 }
577 else if (fd == -1 && errno != EEXIST)
578 {
579 /* Something else went wrong, no need to retry. */
580 msg(M_WARN | M_ERRNO, "Could not create temporary file '%s'",
581 retfname);
582 return NULL;
583 }
584 }
585
586 msg(M_WARN, "Failed to create temporary file after %i attempts", attempts);
587 return NULL;
588}
589
590/*
591 * Put a directory and filename together.
592 */
593const char *
594platform_gen_path(const char *directory, const char *filename,
595 struct gc_arena *gc)
596{
597#ifdef _WIN32
598 const int CC_PATH_RESERVED = CC_LESS_THAN|CC_GREATER_THAN|CC_COLON
600#else
601 const int CC_PATH_RESERVED = CC_SLASH;
602#endif
603
604 if (!gc)
605 {
606 return NULL; /* Would leak memory otherwise */
607 }
608
609 const char *safe_filename = string_mod_const(filename, CC_PRINT, CC_PATH_RESERVED, '_', gc);
610
611 if (safe_filename
612 && strcmp(safe_filename, ".")
613 && strcmp(safe_filename, "..")
614#ifdef _WIN32
615 && win_safe_filename(safe_filename)
616#endif
617 )
618 {
619 const size_t outsize = strlen(safe_filename) + (directory ? strlen(directory) : 0) + 16;
620 struct buffer out = alloc_buf_gc(outsize, gc);
621 char dirsep[2];
622
624 dirsep[1] = '\0';
625
626 if (directory)
627 {
628 buf_printf(&out, "%s%s", directory, dirsep);
629 }
630 buf_printf(&out, "%s", safe_filename);
631
632 return BSTR(&out);
633 }
634 else
635 {
636 return NULL;
637 }
638}
639
640bool
642{
643 if (pathname)
644 {
645 const int c = pathname[0];
646#ifdef _WIN32
647 return c == '\\' || (isalpha(c) && pathname[1] == ':' && pathname[2] == '\\');
648#else
649 return c == '/';
650#endif
651 }
652 else
653 {
654 return false;
655 }
656}
657
658/* return true if filename can be opened for read */
659bool
660platform_test_file(const char *filename)
661{
662 bool ret = false;
663 if (filename)
664 {
665 FILE *fp = platform_fopen(filename, "r");
666 if (fp)
667 {
668 fclose(fp);
669 ret = true;
670 }
671 else
672 {
673 if (errno == EACCES)
674 {
675 msg( M_WARN | M_ERRNO, "Could not access file '%s'", filename);
676 }
677 }
678 }
679
680 dmsg(D_TEST_FILE, "TEST FILE '%s' [%d]",
681 filename ? filename : "UNDEF",
682 ret);
683
684 return ret;
685}
bool buf_printf(struct buffer *buf, const char *format,...)
Definition buffer.c:240
struct buffer alloc_buf_gc(size_t size, struct gc_arena *gc)
Definition buffer.c:88
const char * string_mod_const(const char *str, const unsigned int inclusive, const unsigned int exclusive, const char replace, struct gc_arena *gc)
Returns a copy of a string with certain classes of characters of it replaced with a specified charact...
Definition buffer.c:1090
#define CC_DOUBLE_QUOTE
double quote
Definition buffer.h:908
#define CC_PIPE
pipe
Definition buffer.h:914
#define BSTR(buf)
Definition buffer.h:129
#define CC_COLON
colon
Definition buffer.h:905
#define CC_ASTERISK
asterisk
Definition buffer.h:916
#define CC_BACKSLASH
backslash
Definition buffer.h:900
#define CC_LESS_THAN
less than sign
Definition buffer.h:912
#define CC_SLASH
slash
Definition buffer.h:906
#define CC_GREATER_THAN
greater than sign
Definition buffer.h:913
static void gc_free(struct gc_arena *a)
Definition buffer.h:1033
#define CC_PRINT
printable (>= 32, != 127)
Definition buffer.h:891
#define CC_QUESTION_MARK
question mark
Definition buffer.h:915
static struct gc_arena gc_new(void)
Definition buffer.h:1025
long int get_random(void)
Definition crypto.c:1757
Data Channel Cryptography Module.
#define D_TEST_FILE
Definition errlevel.h:137
#define M_INFO
Definition errlevel.h:55
static SERVICE_STATUS status
Definition interactive.c:53
#define CLEAR(x)
Definition basic.h:33
#define M_FATAL
Definition error.h:89
#define M_NONFATAL
Definition error.h:90
#define dmsg(flags,...)
Definition error.h:148
#define M_ERR
Definition error.h:105
#define msg(flags,...)
Definition error.h:144
#define M_WARN
Definition error.h:91
#define M_ERRNO
Definition error.h:94
static bool dco_enabled(const struct options *o)
Returns whether the current configuration has dco enabled.
Definition options.h:929
bool platform_test_file(const char *filename)
Return true if filename can be opened for read.
Definition platform.c:660
void platform_sleep_milliseconds(unsigned int n)
Definition platform.c:474
unsigned int platform_getpid(void)
Definition platform.c:333
const char * platform_create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc)
Create a temporary file in directory, returns the filename of the created file.
Definition platform.c:541
int platform_ret_code(int stat)
Return an exit code if valid and between 0 and 255, -1 otherwise.
Definition platform.c:425
void platform_user_group_set(const struct platform_state_user *user_state, const struct platform_state_group *group_state, struct context *c)
Definition platform.c:217
void platform_nice(int niceval)
Definition platform.c:311
const char * platform_gen_path(const char *directory, const char *filename, struct gc_arena *gc)
Put a directory and filename together.
Definition platform.c:594
int platform_access(const char *path, int mode)
Definition platform.c:458
bool platform_user_get(const char *username, struct platform_state_user *state)
Definition platform.c:79
static void platform_user_set(const struct platform_state_user *state)
Definition platform.c:106
bool platform_unlink(const char *filename)
Definition platform.c:488
static void platform_group_set(const struct platform_state_group *state)
Definition platform.c:150
FILE * platform_fopen(const char *path, const char *mode)
Definition platform.c:501
bool platform_absolute_pathname(const char *pathname)
Return true if pathname is absolute.
Definition platform.c:641
int platform_chdir(const char *dir)
Definition platform.c:393
void platform_mlockall(bool print_msg)
Definition platform.c:344
void platform_chroot(const char *path)
Definition platform.c:55
static int need_keep_caps(struct context *c)
Definition platform.c:184
bool platform_group_get(const char *groupname, struct platform_state_group *state)
Definition platform.c:123
int platform_open(const char *path, int flags, int mode)
Definition platform.c:514
bool platform_system_ok(int stat)
interpret the status code returned by execve()
Definition platform.c:414
int platform_stat(const char *path, platform_stat_t *buf)
Definition platform.c:527
struct _stat platform_stat_t
Definition platform.h:142
Wrapper structure for dynamically allocated memory.
Definition buffer.h:61
int len
Length in bytes of the actual content within the allocated memory.
Definition buffer.h:66
Contains all state information for one tunnel.
Definition openvpn.h:474
struct options options
Options loaded from command line or configuration file.
Definition openvpn.h:475
Garbage collection arena used to keep track of dynamically allocated memory.
Definition buffer.h:117
#define PATH_SEPARATOR
Definition syshead.h:426
struct gc_arena gc
Definition test_ssl.c:155
bool win_safe_filename(const char *fn)
Definition win32-util.c:118
WCHAR * wide_string(const char *utf8, struct gc_arena *gc)
Definition win32-util.c:41