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#ifdef _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 CLEAR(*state);
83 if (username)
84 {
85#if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID)
86 const struct passwd *pw = getpwnam(username);
87 if (!pw)
88 {
89 msg(M_ERR, "failed to find UID for user %s", username);
90 }
91 else
92 {
93 state->uid = pw->pw_uid;
94 state->user_valid = true;
95 }
96 state->username = username;
97#else /* if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID) */
99 "cannot get UID for user %s -- platform lacks getpwname() or setuid() system calls",
100 username);
101#endif
102 }
103 return state->user_valid;
104}
105
106static void
108{
109#if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID)
110 if (state->username && state->user_valid)
111 {
112 if (setuid(state->uid))
113 {
114 msg(M_ERR, "setuid('%s') failed", state->username);
115 }
116 msg(M_INFO, "UID set to %s", state->username);
117 }
118#endif
119}
120
121/* Get/Set GID of process */
122
123bool
124platform_group_get(const char *groupname, struct platform_state_group *state)
125{
126 CLEAR(*state);
127 if (groupname)
128 {
129#if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID)
130 const struct group *gr = getgrnam(groupname);
131 if (!gr)
132 {
133 msg(M_ERR, "failed to find GID for group %s", groupname);
134 }
135 else
136 {
137 state->gid = gr->gr_gid;
138 state->group_valid = true;
139 }
140 state->groupname = groupname;
141#else /* if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID) */
142 msg(M_FATAL,
143 "cannot get GID for group %s -- platform lacks getgrnam() or setgid() system calls",
144 groupname);
145#endif
146 }
147 return state->group_valid;
148}
149
150static void
152{
153#if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID)
154 if (state->groupname && state->group_valid)
155 {
156 if (setgid(state->gid))
157 {
158 msg(M_ERR, "setgid('%s') failed", state->groupname);
159 }
160 msg(M_INFO, "GID set to %s", state->groupname);
161#ifdef HAVE_SETGROUPS
162 {
163 gid_t gr_list[1];
164 gr_list[0] = state->gid;
165 if (setgroups(1, gr_list))
166 {
167 msg(M_ERR, "setgroups('%s') failed", state->groupname);
168 }
169 }
170#endif
171 }
172#endif
173}
174
175/*
176 * Determine if we need to retain process capabilities. DCO and SITNL need it.
177 * Enforce it for DCO, but only try and soft-fail for SITNL to keep backwards compat.
178 *
179 * Returns the tri-state expected by platform_user_group_set.
180 * -1: try to keep caps, but continue if impossible
181 * 0: don't keep caps
182 * 1: keep caps, fail hard if impossible
183 */
184static int
186{
187 if (!c)
188 {
189 return -1;
190 }
191
192 if (dco_enabled(&c->options))
193 {
194#ifdef TARGET_LINUX
195 /* DCO on Linux does not work at all without CAP_NET_ADMIN */
196 return 1;
197#else
198 /* Windows/BSD/... has no equivalent capability mechanism */
199 return -1;
200#endif
201 }
202
203#ifdef ENABLE_SITNL
204 return -1;
205#else
206 return 0;
207#endif
208}
209
210/* Set user and group, retaining neccesary capabilities required by the platform.
211 *
212 * The keep_caps argument has 3 possible states:
213 * >0: Retain capabilities, and fail hard on failure to do so.
214 * ==0: Don't attempt to retain any capabilities, just sitch user/group.
215 * <0: Try to retain capabilities, but continue on failure.
216 */
217void
219 const struct platform_state_group *group_state, 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->group_valid)
237 {
238 new_gid = (int)group_state->gid;
239 }
240 if (user_state->username && user_state->user_valid)
241 {
242 new_uid = (int)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", user_state->username,
263 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", MIN_LOCKED_MEM_MB);
362 rl.rlim_cur = MIN_LOCKED_MEM_MB * 1024 * 1024;
363 if (rl.rlim_max < rl.rlim_cur)
364 {
365 rl.rlim_max = rl.rlim_cur;
366 }
367 if (setrlimit(RLIMIT_MEMLOCK, &rl) < 0)
368 {
369 msg(M_FATAL | M_ERRNO, "ERROR: setrlimit() failed");
370 }
371 }
372 }
373#endif /* if defined(HAVE_GETRLIMIT) && defined(RLIMIT_MEMLOCK) */
374
375 if (mlockall(MCL_CURRENT | MCL_FUTURE))
376 {
377 msg(M_WARN | M_ERRNO, "WARNING: mlockall call failed");
378 }
379 else if (print_msg)
380 {
381 msg(M_INFO, "mlockall call succeeded");
382 }
383#else /* ifdef HAVE_MLOCKALL */
384 msg(M_WARN, "WARNING: mlockall call failed (function not implemented)");
385#endif /* ifdef HAVE_MLOCKALL */
386}
387
388/*
389 * Wrapper for chdir library function
390 */
391int
392platform_chdir(const char *dir)
393{
394#ifdef _WIN32
395 int res;
396 struct gc_arena gc = gc_new();
397 res = _wchdir(wide_string(dir, &gc));
398 gc_free(&gc);
399 return res;
400#else /* ifdef _WIN32 */
401#ifdef HAVE_CHDIR
402 return chdir(dir);
403#else /* ifdef HAVE_CHDIR */
404 return -1;
405#endif
406#endif
407}
408
409/*
410 * convert execve() return into a success/failure value
411 */
412bool
414{
415#ifdef _WIN32
416 return stat == 0;
417#else
418 return stat != -1 && WIFEXITED(stat) && WEXITSTATUS(stat) == 0;
419#endif
420}
421
422#ifdef _WIN32
423int
425{
426 if (stat >= 0 && stat < 255)
427 {
428 return stat;
429 }
430 else
431 {
432 return -1;
433 }
434}
435#else /* ifdef _WIN32 */
436int
437platform_ret_code(int stat)
438{
439 if (!WIFEXITED(stat) || stat == -1)
440 {
441 return -1;
442 }
443
444 int status = WEXITSTATUS(stat);
445 if (status >= 0 && status < 255)
446 {
447 return status;
448 }
449 else
450 {
451 return -1;
452 }
453}
454#endif /* ifdef _WIN32 */
455
456int
457platform_access(const char *path, int mode)
458{
459#ifdef _WIN32
460 struct gc_arena gc = gc_new();
461 int ret = _waccess(wide_string(path, &gc), mode & ~X_OK);
462 gc_free(&gc);
463 return ret;
464#else
465 return access(path, mode);
466#endif
467}
468
469/*
470 * Go to sleep for n milliseconds.
471 */
472void
474{
475#ifdef _WIN32
476 Sleep(n);
477#else
478 struct timeval tv;
479 tv.tv_sec = n / 1000;
480 tv.tv_usec = (n % 1000) * 1000;
481 select(0, NULL, NULL, NULL, &tv);
482#endif
483}
484
485/* delete a file, return true if succeeded */
486bool
487platform_unlink(const char *filename)
488{
489#if defined(_WIN32)
490 struct gc_arena gc = gc_new();
491 BOOL ret = DeleteFileW(wide_string(filename, &gc));
492 gc_free(&gc);
493 return (ret != 0);
494#else
495 return (unlink(filename) == 0);
496#endif
497}
498
499FILE *
500platform_fopen(const char *path, const char *mode)
501{
502#ifdef _WIN32
503 struct gc_arena gc = gc_new();
504 FILE *f = _wfopen(wide_string(path, &gc), wide_string(mode, &gc));
505 gc_free(&gc);
506 return f;
507#else
508 return fopen(path, mode);
509#endif
510}
511
512int
513platform_open(const char *path, int flags, int mode)
514{
515#ifdef _WIN32
516 struct gc_arena gc = gc_new();
517 int fd = _wopen(wide_string(path, &gc), flags, mode);
518 gc_free(&gc);
519 return fd;
520#else
521 return open(path, flags, mode);
522#endif
523}
524
525int
526platform_stat(const char *path, platform_stat_t *buf)
527{
528#ifdef _WIN32
529 struct gc_arena gc = gc_new();
530 int res = _wstat(wide_string(path, &gc), buf);
531 gc_free(&gc);
532 return res;
533#else
534 return stat(path, buf);
535#endif
536}
537
538/* create a temporary filename in directory */
539const char *
540platform_create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc)
541{
542 int fd;
543 const char *retfname = NULL;
544 unsigned int attempts = 0;
545 char fname[256] = { 0 };
546 const char *fname_fmt = PACKAGE "_%.*s_%08lx%08lx.tmp";
547 const int max_prefix_len = sizeof(fname) - (sizeof(PACKAGE) + 7 + (2 * 8));
548
549 while (attempts < 6)
550 {
551 ++attempts;
552
553 if (!snprintf(fname, sizeof(fname), fname_fmt, max_prefix_len, prefix,
554 (unsigned long)get_random(), (unsigned long)get_random()))
555 {
556 msg(M_WARN, "ERROR: temporary filename too long");
557 return NULL;
558 }
559
560 retfname = platform_gen_path(directory, fname, gc);
561 if (!retfname)
562 {
563 msg(M_WARN, "Failed to create temporary filename and path");
564 return NULL;
565 }
566
567 /* Atomically create the file. Errors out if the file already
568 * exists. */
569 fd = platform_open(retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
570 if (fd != -1)
571 {
572 close(fd);
573 return retfname;
574 }
575 else if (fd == -1 && errno != EEXIST)
576 {
577 /* Something else went wrong, no need to retry. */
578 msg(M_WARN | M_ERRNO, "Could not create temporary file '%s'", retfname);
579 return NULL;
580 }
581 }
582
583 msg(M_WARN, "Failed to create temporary file after %i attempts", attempts);
584 return NULL;
585}
586
587/*
588 * Put a directory and filename together.
589 */
590const char *
591platform_gen_path(const char *directory, const char *filename, struct gc_arena *gc)
592{
593#ifdef _WIN32
594 const int CC_PATH_RESERVED = CC_LESS_THAN | CC_GREATER_THAN | CC_COLON | CC_DOUBLE_QUOTE
596 | CC_ASTERISK;
597#else
598 const int CC_PATH_RESERVED = CC_SLASH;
599#endif
600
601 if (!gc)
602 {
603 return NULL; /* Would leak memory otherwise */
604 }
605
606 const char *safe_filename = string_mod_const(filename, CC_PRINT, CC_PATH_RESERVED, '_', gc);
607
608 if (safe_filename && strcmp(safe_filename, ".") && strcmp(safe_filename, "..")
609#ifdef _WIN32
610 && win_safe_filename(safe_filename)
611#endif
612 )
613 {
614 const size_t outsize = strlen(safe_filename) + (directory ? strlen(directory) : 0) + 16;
615 struct buffer out = alloc_buf_gc(outsize, gc);
616 char dirsep[2];
617
619 dirsep[1] = '\0';
620
621 if (directory)
622 {
623 buf_printf(&out, "%s%s", directory, dirsep);
624 }
625 buf_printf(&out, "%s", safe_filename);
626
627 return BSTR(&out);
628 }
629 else
630 {
631 return NULL;
632 }
633}
634
635bool
637{
638 if (pathname)
639 {
640 const int c = pathname[0];
641#ifdef _WIN32
642 return c == '\\' || (isalpha(c) && pathname[1] == ':' && pathname[2] == '\\');
643#else
644 return c == '/';
645#endif
646 }
647 else
648 {
649 return false;
650 }
651}
652
653/* return true if filename can be opened for read */
654bool
655platform_test_file(const char *filename)
656{
657 bool ret = false;
658 if (filename)
659 {
660 FILE *fp = platform_fopen(filename, "r");
661 if (fp)
662 {
663 fclose(fp);
664 ret = true;
665 }
666 else
667 {
668 if (errno == EACCES)
669 {
670 msg(M_WARN | M_ERRNO, "Could not access file '%s'", filename);
671 }
672 }
673 }
674
675 dmsg(D_TEST_FILE, "TEST FILE '%s' [%d]", filename ? filename : "UNDEF", ret);
676
677 return ret;
678}
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:1096
#define CC_DOUBLE_QUOTE
double quote
Definition buffer.h:902
#define CC_PIPE
pipe
Definition buffer.h:908
#define BSTR(buf)
Definition buffer.h:128
#define CC_COLON
colon
Definition buffer.h:899
#define CC_ASTERISK
asterisk
Definition buffer.h:910
#define CC_BACKSLASH
backslash
Definition buffer.h:894
#define CC_LESS_THAN
less than sign
Definition buffer.h:906
#define CC_SLASH
slash
Definition buffer.h:900
#define CC_GREATER_THAN
greater than sign
Definition buffer.h:907
static void gc_free(struct gc_arena *a)
Definition buffer.h:1025
#define CC_PRINT
printable (>= 32, != 127)
Definition buffer.h:885
#define CC_QUESTION_MARK
question mark
Definition buffer.h:909
static struct gc_arena gc_new(void)
Definition buffer.h:1017
long int get_random(void)
Definition crypto.c:1718
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:90
#define M_NONFATAL
Definition error.h:91
#define dmsg(flags,...)
Definition error.h:172
#define M_ERR
Definition error.h:106
#define msg(flags,...)
Definition error.h:152
#define M_WARN
Definition error.h:92
#define M_ERRNO
Definition error.h:95
static bool dco_enabled(const struct options *o)
Returns whether the current configuration has dco enabled.
Definition options.h:1008
bool platform_test_file(const char *filename)
Return true if filename can be opened for read.
Definition platform.c:655
void platform_sleep_milliseconds(unsigned int n)
Definition platform.c:473
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:540
int platform_ret_code(int stat)
Return an exit code if valid and between 0 and 255, -1 otherwise.
Definition platform.c:424
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:218
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:591
int platform_access(const char *path, int mode)
Definition platform.c:457
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:107
bool platform_unlink(const char *filename)
Definition platform.c:487
static void platform_group_set(const struct platform_state_group *state)
Definition platform.c:151
FILE * platform_fopen(const char *path, const char *mode)
Definition platform.c:500
bool platform_absolute_pathname(const char *pathname)
Return true if pathname is absolute.
Definition platform.c:636
int platform_chdir(const char *dir)
Definition platform.c:392
void platform_mlockall(bool print_msg)
Definition platform.c:344
void platform_chroot(const char *path)
Definition platform.c:54
static int need_keep_caps(struct context *c)
Definition platform.c:185
bool platform_group_get(const char *groupname, struct platform_state_group *state)
Definition platform.c:124
int platform_open(const char *path, int flags, int mode)
Definition platform.c:513
bool platform_system_ok(int stat)
interpret the status code returned by execve()
Definition platform.c:413
int platform_stat(const char *path, platform_stat_t *buf)
Definition platform.c:526
struct _stat platform_stat_t
Definition platform.h:118
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:131
bool win_safe_filename(const char *fn)
Definition win32-util.c:116
WCHAR * wide_string(const char *utf8, struct gc_arena *gc)
Definition win32-util.c:40