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